Graphic LCD Analogue Clock

In a previous article about the 192×64 pixel Graphics LCD Module that I have been playing with, I created a simple analogue clock to exercise some of the text and graphics functions of the fully-featured openGLCD library. I have now added to the functions of the clock to include moon-phases and local times of both sunrise and sunset, all of which are displayed using a combination of bitmaps and text. In addition, I have also added the sun’s elevation, in degrees from the horizontal, and sun’s azimuth, displayed in both polar degrees (North=0°) and text-based cardinal direction.

Analogue Clock with moon phase, sunset, sunrise and sun elevation and azimuth

Analogue Clock with moon phase, sunset, sunrise and sun elevation and azimuth

REVISION [150507]: I have now added the times of moon rise and moon set, and marks on the outer ring of the analogue clock for sun elevation (tick mark) and compass-based azimuth (open circle). However, these additions came at a price – removal of all bitmap icons.

Revision of the Analogue Clock with sun elevation and azimuth, moon phase, and times of sunrise, solar noon, sunset, moon rise and moon set

Revision of the Analogue Clock with sun elevation and azimuth, moon phase and state, and times of sunrise, solar noon, sunset, moon rise and moon set.

REVISION [150517]: Added icons for moon phase (shown as waning crescent) and made minor alterations to clock face. On the clock face, the inner filled circle is the seconds marker, the outer filled circle is the sun’s cardinal azimuth (shown as 101° East) and the tick mark (close to 2 o’clock) is the sun’s elevation to the horizontal (shown as 37°). The revisions to the Arduino code are included below.

Analogue clock with additional moon phase icons

Analogue clock with additional moon phase icons

Take your pick!

The openGLCD library includes a utility to create bitmaps from image files (jpg, png and gif). The bitmap can them be shown at a particular x,y pixel position on the graphic display. I found 27 simple black and white images for the full range of moon phases, as well as a couple of images for sunrise and sunset. I edited the moon phase images to be 25 x 25 pixels in size and used the openGLCD library processing application “glcdMakeBitmap” to create equivalent bitmaps. When your sketch is uploaded, the openGLCD library loads these bitmaps into program rather than data memory so they do not eat up valuable SRAM. Useful!

To determine moon phase, I started from an algorithm found at www.ben-daglish.net and scaled it so that its output would cycle through the 27 moon phase icons and accompanying text appropriately. I tried several libraries to determine sunrise, sunset, elevation and azimuth and eventually settled for the “sundata.h” library from DataPanik. This library seemed most complete and its operation easy to follow. In addition, it also appeared to be the most accurate when compared against a variety of online applications. To use the library to display local information, I edited in my geo-coordinates and time offset from GMT offset. The latter was determined using the simple daylight savings time algorithm that was the topic of a previous blog.  I plan to create a simple graphic to display both elevation and azimuth but for now they are displayed numerically.

Here’s the original (older) Arduino code with the moon phase bitmaps….

[codesyntax lang=”php” title=”Analogue Clock with moon phases, sunrise and sunset” blockstate=”collapsed”]

//**//**************************************************************************************************
//                                        ANALOGUE CLOCK
//                                   Adrian Jones, May 2015
//
//**************************************************************************************************

// Build 1
//   r1 150315 - initial build fwith graphic LCD
//   r2 150430 - minor additions to clock face & added moon phase
//   r3 150502 - added sunrise and sunset, azimuth and elevation; change to sundata.h library
//********************************************************************************************
#define build 1
#define revision 3
//********************************************************************************************

#include <avr/pgmspace.h>          // for memory storage in program space

#include <sundata.h>
sundata vdm = sundata(45.66167, -75.64316, -4);                         //create local object with latitude and longtitude declared in degrees and time difference from Greenwhich

// Array of character strings stored in PROGMEM
const char me0[] PROGMEM = "  full moon  ";
const char me1[] PROGMEM = "waxing gibous";
const char me2[] PROGMEM = "first quarter";
const char me3[] PROGMEM = "waxing cresc.";
const char me4[] PROGMEM = "  new moon   ";
const char me5[] PROGMEM = "waning cresc.";
const char me6[] PROGMEM = "last quarter ";
const char me7[] PROGMEM = "waning gibous";
const char * const me [] PROGMEM = { me0, me1, me2, me3, me4, me5, me6, me7 };

// cardinal directions
const char card0[]  PROGMEM = "N";
const char card1[]  PROGMEM = "NNE";
const char card2[]  PROGMEM = "NE";
const char card3[]  PROGMEM = "ENE";
const char card4[]  PROGMEM = "E";
const char card5[]  PROGMEM = "ESE";
const char card6[]  PROGMEM = "SE";
const char card7[]  PROGMEM = "SSE";
const char card8[]  PROGMEM = "S";
const char card9[]  PROGMEM = "SSW";
const char cardA[]  PROGMEM = "SW";
const char cardB[]  PROGMEM = "WSW";
const char cardC[]  PROGMEM = "W";
const char cardD[]  PROGMEM = "WNW";
const char cardE[]  PROGMEM = "NW";
const char cardF[]  PROGMEM = "NNW";
const char * const cards[] PROGMEM = { card0,card1,card2,card3,card4,card5,card6,card7,card8,card9,cardA,cardB,cardC,cardD,cardE,cardF };

char cb[18];                 // character buffer for character strings. Set dimension to length of longest string

// RTC
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 RTC;
byte hr, mn, se, osec=61, omin=61, ohr=30, dy, ody=0, mo, yr, dw;
int phase, ophase=-1, ms;

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

// graphics LCD
#include <openGLCD.h>             // Graphics LCD library
/*******************************************************************************************
//                            openGLCD library functions
/*******************************************************************************************

   GLCD.Init(invert)     initialize the library for normal or inverted drawing. If invert is false, drawing sets pixels, if true pixels are cleared when drawn (see also SetInverted method) 
   GLCD.GotoXY(x,y)      locate the graphic cursor at positions x and y, 0,0 is upper left corner
   GLCD.ClearScreen()    clear the LCD screen
   GLCD.OnDisplay(); GLCD.OffDisplay(); // turn on, off pixels

 // Graphic Drawing Functions (color WHITE clears pixels, BLACK sets pixels)
   GLCD.DrawCircle(x, y, radius, color)     draw circle with center at x,y
   GLCD.DrawLine(x1, y1, x2, y2, color)     draw line from x1,y1 to x2,y2 
   GLCD.DrawVertLine(x, y, length, color)   draw vertical line
   GLCD.DrawHoriLine(x, y, length, color)   draw horizontal line  
   GLCD.DrawRect(x, y, width, height, color) draw rectangle
   GLCD.DrawRoundRect(x, y, width, height, radius, color) as above with rounded edges
   GLCD.FillRect(x, y, width, height, color) draw filled rectangle
   GLCD.InvertRect(x, y, width, height)     invert pixels within given rectangle
   GLCD.SetDisplayMode(NON_INVERTED); GLCD.SetDisplayMode(INVERTED);                set drawing mode to normal / inverted 
   GLCD.SetDot(x, y, color);                draw a dot in the given color at the given location
   GLCD.DrawBitmap(bitmap, x, y, color);    draw the bitmap at the given x,y position

 // Font Functions 
   GLCD.SelectFont(font, color )            select font, defaults color to black if not specified
   GLCD.PutChar(character)                  print given character to screen at current cursor location
   GLCD.Puts(string)                        print given string to screen at current cursor location
   GLCD.Puts_P(string)                      print string from program memory to screen at current cursor location
   GLCD.PrintNumber(number)                 print the decimal value of the given number at current cursor location
   GLCD.CursorTo(x, y);                     0 based coordinates for fixed width fonts (i.e. the supplied system font)

/*******************************************************************************************/


//*****************************************************************************************//
//                                      Initial Setup
//*****************************************************************************************//
void setup() {
  GLCD.Init();
  GLCD.SelectFont(SystemFont5x7);
 
  Serial.begin(57600);
    
  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
  // RTC.adjust(DateTime(__DATE__, __TIME__)); 
  // RTC.adjust(DateTime(2015, 1, 18, 1, 59, 50 ));      // test
  // RTC.adjust(DateTime(2015, 3, 8, 1, 59, 50 ));      // start of 2015 DST
  // RTC.adjust(DateTime(2015, 11, 1, 1, 59, 50 ));     // end of 2015 DST
  
  getTime();
  if(IsDST()) sundata vdm = sundata(45.66167, -75.64316, -4); else sundata vdm = sundata(45.66167, -75.64316, -5);                         //creat val-des-monts object with latitude and longtitude declared in degrees and time difference from Greenwhich
 
  drawFace();
  restoreSettings();                      // restore EEPROM settings 
 }
 
//*****************************************************************************************//
//                                      MAIN LOOP
//*****************************************************************************************//
void loop() {
  getTime();
  if(se != osec) {
    doDSTAdjust();
    
    Serial.print(F("DateTime: ")); Serial.print(yr); Serial.print(F("/"));
    if(mo<10) Serial.print(F("0")); Serial.print(mo); Serial.print(F("/"));
    if(dy<10) Serial.print(F("0")); Serial.print(dy); Serial.print(F(" "));
    if(hr<10) Serial.print(F("0")); Serial.print(hr); Serial.print(F(":"));
    if(mn<10) Serial.print(F("0")); Serial.print(mn); Serial.print(F(":"));
    if(se<10) Serial.print(F("0")); Serial.print(se); Serial.print(F("\t"));
    Serial.print(F("Moonphase: ")); Serial.print(moonphase());
 
    vdm.time(2000+yr, mo, dy, hr, mn, se);                  // current year, month, day, hour, minutes and seconds
    vdm.calculations();                                     // update calculations for current time
    float el_deg = vdm.elevation_deg();                     // store sun's elevation in degrees
    float az_deg = vdm.azimuth_deg();                       // store sun's azimuth in degrees
    float sunup  = vdm.sunrise_time();                     // store sunrise time in decimal form
    float sundn  = vdm.sunset_time();                      // store sunset time in decimal form
 
    Serial.print(F("\tSun Elevation: "));
    Serial.print(el_deg); Serial.print(" deg.");

    Serial.print(F("\tSun Azimuth: "));
    Serial.print(az_deg); Serial.print(" deg. ");
    Serial.print(getCardinal(az_deg));
  
    Serial.print(F("\tSunrise: "));
    Serial.print(int(sunup)); Serial.print(":"); if(int((sunup - int(sunup))*60.0) <10) Serial.print("0"); Serial.print(int((sunup - int(sunup))*60.0));
  
    Serial.print(F("\tSunset: ")); 
    Serial.print(int(sundn)); Serial.print(":"); if(int((sundn - int(sundn))*60.0) <10) Serial.print("0");  Serial.print(int((sundn - int(sundn))*60.0));
    Serial.println();

    GLCD.SelectFont(Callibri15);
    GLCD.CursorToXY(1, 0);
    if(hr<10) GLCD.print(F("0")); GLCD.print(hr); GLCD.print(F(" : "));
    if(mn<10) GLCD.print(F("0")); GLCD.print(mn); GLCD.print(F(" : "));
    if(se<10) GLCD.print(F("0")); GLCD.print(se); GLCD.print(F(" "));

     GLCD.CursorToXY(135, 0);
    GLCD.print(yr); GLCD.print(F("/"));
    if(mo<10) GLCD.print(F("0")); GLCD.print(mo); GLCD.print(F("/"));
    if(dy<10) GLCD.print(F("0")); GLCD.print(dy); GLCD.print(F(" "));

    GLCD.SelectFont(SystemFont5x7);
    GLCD.CursorToXY(1, 17);
    GLCD.print(F("A ")); GLCD.print(az_deg,0); GLCD.print(F(" ")); GLCD.print(getCardinal(az_deg));
 
    GLCD.CursorToXY(1, 26);
    GLCD.print(F("E ")); GLCD.print(el_deg);
 
    drawHands();
    drawMoon(150, 20);   
    
    if(dy != ody) {
      GLCD.DrawBitmap(sunrise, 7, 40, BLACK);
      GLCD.CursorToXY(1, 56);
      if(int(sunup) <10) GLCD.print("0"); GLCD.print(int(sunup)); GLCD.print(F(":"));
      if(int((sunup - int(sunup))*60.0) <10) GLCD.print("0"); GLCD.print(int((sunup - int(sunup))*60.0));

      GLCD.DrawBitmap(sunset, 40, 40, BLACK);
      GLCD.CursorToXY(34, 56);
      GLCD.print(int(sundn)); GLCD.print(F(":"));
      if(int((sundn - int(sundn))*60.0) <10) GLCD.print("0");  GLCD.print(int((sundn - int(sundn))*60.0));
     
      strcpy_P(cb, (char*) pgm_read_word(&me[ms] ) );       // read string into buffer
      GLCD.CursorToXY(130, 48);
      GLCD.SelectFont(Callibri10);
      GLCD.Puts(cb);
      ody = dy;
    }

    osec=se;
    omin=mn;
    ohr=hr%12;
  }
  delay(200);
}

// ********************************************************************************** //
//                                 DRAW MOON ROUTINES
// ********************************************************************************** //

void drawMoon(byte x, byte y) {      
    phase = moonphase();             // get moon phase (0-26)
    phase = (27 + 13 - phase)%27;    // adjust for correct start & direction
    if(ophase == phase) return;
    switch(phase) {
      case 0:  GLCD.DrawBitmap(m00, x, y, BLACK); ms=1; break;   
      case 1:  GLCD.DrawBitmap(m01, x, y, BLACK); ms=1; break;     
      case 2:  GLCD.DrawBitmap(m02, x, y, BLACK); ms=1; break;
      case 3:  GLCD.DrawBitmap(m03, x, y, BLACK); ms=1; break;    // waxing gibous
      case 4:  GLCD.DrawBitmap(m04, x, y, BLACK); ms=1; break;     
      case 5:  GLCD.DrawBitmap(m05, x, y, BLACK); ms=2; break;
      case 6:  GLCD.DrawBitmap(m06, x, y, BLACK); ms=2; break;    // first quarter
      case 7:  GLCD.DrawBitmap(m07, x, y, BLACK); ms=3; break;
      case 8:  GLCD.DrawBitmap(m08, x, y, BLACK); ms=3; break;    
      case 9:  GLCD.DrawBitmap(m09, x, y, BLACK); ms=3; break;    // waxing crescent
      case 10: GLCD.DrawBitmap(m10, x, y, BLACK); ms=3; break;
      case 11: GLCD.DrawBitmap(m11, x, y, BLACK); ms=4; break;
      case 12: GLCD.DrawBitmap(m12, x, y, BLACK); ms=4; break;    // new moon
      case 13: GLCD.DrawBitmap(m13, x, y, BLACK); ms=4; break;
      case 14: GLCD.DrawBitmap(m14, x, y, BLACK); ms=5; break;
      case 15: GLCD.DrawBitmap(m15, x, y, BLACK); ms=5; break;
      case 16: GLCD.DrawBitmap(m16, x, y, BLACK); ms=5; break;    // waning gibous
      case 17: GLCD.DrawBitmap(m17, x, y, BLACK); ms=5; break;
      case 18: GLCD.DrawBitmap(m18, x, y, BLACK); ms=5; break;
      case 19: GLCD.DrawBitmap(m19, x, y, BLACK); ms=6; break;    // last quarter
      case 20: GLCD.DrawBitmap(m20, x, y, BLACK); ms=6; break;
      case 21: GLCD.DrawBitmap(m21, x, y, BLACK); ms=7; break;
      case 22: GLCD.DrawBitmap(m22, x, y, BLACK); ms=7; break;    // waning crescent
      case 23: GLCD.DrawBitmap(m23, x, y, BLACK); ms=7; break;  
      case 24: GLCD.DrawBitmap(m24, x, y, BLACK); ms=7; break;
      case 25: GLCD.DrawBitmap(m25, x, y, BLACK); ms=7; break;
      case 26: GLCD.DrawBitmap(m26, x, y, BLACK); ms=0; break;    // full moon

      default: GLCD.DrawBitmap(m12, x, y, BLACK); break;
    }
    ophase = phase;
    
}


// ********************************************************************************** //
//                                 DRAW HANDS ROUTINES
// ********************************************************************************** //

void drawHands() {
  drawSec(22, osec, WHITE);       drawSec(22, se, BLACK);
  drawMin(23, omin, WHITE);       drawMin(23, mn, BLACK);
  drawHour(16, ohr, omin, WHITE); drawHour(16, hr%12, mn, BLACK);
}

void drawSec(int len, int num, int col) {
  float s = (float) ((num+30)%60)/60*2.0*PI;
  float x = len * -sin(s);
  float y = len * cos(s);
  if(hr>12 && hr<24) {
    GLCD.DrawCircle(GLCD.CenterX+x, GLCD.CenterY+y, 2, col);
  } else {
    GLCD.FillCircle(GLCD.CenterX+x, GLCD.CenterY+y, 2, col);
  }
}

void drawMin(int len, int num, int col) {
  float s = (float) ((num+30)%60)/60 *2.0*PI;
  float x = len * -sin(s);
  float y = len * cos(s);
  GLCD.DrawLine(GLCD.CenterX,GLCD.CenterY, GLCD.CenterX+x, GLCD.CenterY+y, col);
}

void drawHour(int len, int num, int nd, int col) {
  float s = (float) ((num*30 + nd/2) / 360.0 + 0.5)*2.0*PI;
  float x = len * -sin(s);
  float y = len * cos(s);
  GLCD.DrawLine(GLCD.CenterX, GLCD.CenterY, GLCD.CenterX+x, GLCD.CenterY+y, col);
}

void drawFace() {
  GLCD.DrawCircle(GLCD.CenterX,GLCD.CenterY, GLCD.Bottom/2);
  GLCD.DrawCircle(GLCD.CenterX,GLCD.CenterY, GLCD.Bottom/2+1);
  GLCD.FillRect(GLCD.CenterX-1, GLCD.CenterY-1, 3, 3);
  for(int i=0; i<360 ; i+=30) {
    float s = (float) (i/360.0)*2.0*PI;
    float x = 27 * -sin(s);
    float y = 27 * cos(s);
    if(i==180) {
      GLCD.FillRect(GLCD.CenterX+x-2, GLCD.CenterY+y-2, 4, 4);
    } else {
      GLCD.DrawCircle(GLCD.CenterX+x, GLCD.CenterY+y, 1);
    }
  }
}

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


/* ********************************************************************************** //
//                                       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.
}

void getTime() {
  DateTime now = RTC.now();
  hr = now.hour(); if(hr==0) hr=24;
  mn = now.minute();
  se = now.second();
  yr = now.year()-2000;
  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
}


// 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. new scaling for 0-26 (i.e. 27 unique) bitmaps
  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, 26);                      // map to 0-26
  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(&(cards[(int) (h / 22.5)]) ) );
  return cb;  
}

// 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]

Click on the images link for the sun and moon images used.

REVISION [150517]: I have made further revisions to the software to include the times of moon rise and moon set and add marks to illustrate the sun’s elevation and its azimuth on the clock face. However, the addition of the complex mathematics for the moon times exceeded the program storage available on the Arduino Nano. To free up sufficient space I removed the moon phase bitmaps and went back to using just one size font. I also replaced the icons used for sunrise, sunset and 8 moon phases with newly created characters added to the system font file (see below for additions to the “System5x7.h” file) to indicate sun and moon rise (up arrow), solar noon (filled circle) and sun and moon set (down arrow). The moon phase is also displayed using  8 moon phase icons, numerically and textually. While perhaps not so visually pleasing, this now contains all of the  information that I wanted to display.

The moon phase and other icons were created and added to the “System5x7.h” font file used in the latest revision of the software. You will need to locate the file in the fonts folder and edit in the following additions to the bottom of the file.

    0x00, 0x06, 0x09, 0x09, 0x06, // 80H degree symbol    
    0x08, 0x0C, 0x7E, 0x0C, 0x08, // 81H up
    0x10, 0x30, 0x7E, 0x30, 0x10, // 82H down 
    0x1C, 0x3E, 0x3E, 0x3E, 0x1C, // 83H full sun
    0x00, 0x1C, 0x3E, 0x63, 0x41, // 84H moon
    0x00, 0x1C, 0x7F, 0x3E, 0x1C, // 85H waxing gibous
    0x1C, 0x3E, 0x7F, 0x3E, 0x1C, // 86H full moon 
    0x1C, 0x3E, 0x7F, 0x1C, 0x00, // 87H waning gibous
    0x1C, 0x3E, 0x7F, 0x00, 0x00, // 88H waning quarter
    0x1C, 0x22, 0x41, 0x00, 0x00, // 89H waning crescent
    0x1C, 0x22, 0x41, 0x22, 0x1C, // 8AH new moon
    0x00, 0x00, 0x41, 0x22, 0x1C, // 8BH waxing crescent
    0x00, 0x00, 0x7F, 0x3E, 0x1C  // 8CH waxing quarter

You will also have to edit the character count (at the top of the file) to 0x6C to add in the additional characters in the font set.

Here’s the revised Arduino code with the moon times and font-based icons:

[codesyntax lang=”php” title=”Analogue Clock with Sun and Moon Information” blockstate=”collapsed”]

//**************************************************************************************************
//                                        ANALOGUE CLOCK
//                                   Adrian Jones, May 2015
//
//**************************************************************************************************

// Build 1
//   r1 150315 - initial build fwith graphic LCD
//   r2 150430 - minor additions to clock face & added moon phase
//   r3 150502 - added sunrise and sunset, azimuth and elevation; change to sundata.h library
//   r4 150506 - added azimuth and elevation indications to analogue clock face, moon rise, moon set and state to display
//********************************************************************************************
#define build 1
#define revision 4
//********************************************************************************************

#include <avr/pgmspace.h>          // for memory storage in program space

#include <sundata.h>
#define LONGITUDE -75.64316
#define LATITUDE  45.66167
sundata vdm(0,0,0);

// Array of character strings stored in PROGMEM
const char me0[] PROGMEM = "full  ";
const char me1[] PROGMEM = "waxing";
const char me2[] PROGMEM = "first ";
const char me3[] PROGMEM = "new   ";
const char me4[] PROGMEM = "waning";
const char me5[] PROGMEM = "last  ";
const char me6[] PROGMEM = "moon    ";
const char me7[] PROGMEM = "gibous  ";
const char me8[] PROGMEM = "quarter ";
const char me9[] PROGMEM = "crescent";
const char * const me [] PROGMEM = { me0, me1, me2, me3, me4, me5, me6, me7, me8, me9};

// 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[10];                 // character buffer for character strings. Set dimension to length of longest string
float el_deg, az_deg, sunup, sundn;
int phase, azi,oazi=400,ele,oele=400;
byte ms,mt,mi;
float s,x,y,x1,y1;

// https://raw.githubusercontent.com/slackmasterstan/Moon-in-my-room/master/Moon.ino
// http://slackworld.org/blog/archives/311

static float Dec[3] = { 0.0, 0.0, 0.0 };
static float RAn[3] = { 0.0, 0.0, 0.0 };
static float VHz[3] = { 0.0, 0.0, 0.0 };
static float Sky[3] = { 0.0, 0.0, 0.0 };
const static float DR = M_PI / 180;

static int Rise_time[2] = {0.0, 0.0}; 
static int Set_time[2] = {0.0, 0.0};

static bool Moonrise, Moonset;
static int TZ;


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

// 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 serEn false
#define lcdEn true
//***************************



#if(lcdEn)

// graphics LCD
#include <openGLCD.h>             // Graphics LCD library
/*******************************************************************************************
//                            openGLCD library functions
/*******************************************************************************************

   GLCD.Init(invert)     initialize the library for normal or inverted drawing. If invert is false, drawing sets pixels, if true pixels are cleared when drawn (see also SetInverted method) 
   GLCD.GotoXY(x,y)      locate the graphic cursor at positions x and y, 0,0 is upper left corner
   GLCD.ClearScreen()    clear the LCD screen
   GLCD.OnDisplay(); GLCD.OffDisplay(); // turn on, off pixels

 // Graphic Drawing Functions (color WHITE clears pixels, BLACK sets pixels)
   GLCD.DrawCircle(x, y, radius, color)     draw circle with center at x,y
   GLCD.DrawLine(x1, y1, x2, y2, color)     draw line from x1,y1 to x2,y2 
   GLCD.DrawVertLine(x, y, length, color)   draw vertical line
   GLCD.DrawHoriLine(x, y, length, color)   draw horizontal line  
   GLCD.DrawRect(x, y, width, height, color) draw rectangle
   GLCD.DrawRoundRect(x, y, width, height, radius, color) as above with rounded edges
   GLCD.FillRect(x, y, width, height, color) draw filled rectangle
   GLCD.InvertRect(x, y, width, height)     invert pixels within given rectangle
   GLCD.SetDisplayMode(NON_INVERTED); GLCD.SetDisplayMode(INVERTED);                set drawing mode to normal / inverted 
   GLCD.SetDot(x, y, color);                draw a dot in the given color at the given location
   GLCD.DrawBitmap(bitmap, x, y, color);    draw the bitmap at the given x,y position

 // Font Functions 
   GLCD.SelectFont(font, color )            select font, defaults color to black if not specified
   GLCD.PutChar(character)                  print given character to screen at current cursor location
   GLCD.Puts(string)                        print given string to screen at current cursor location
   GLCD.Puts_P(string)                      print string from program memory to screen at current cursor location
   GLCD.PrintNumber(number)                 print the decimal value of the given number at current cursor location
   GLCD.CursorTo(x, y);                     0 based coordinates for fixed width fonts (i.e. the supplied system font)

/*******************************************************************************************/
#endif

//*****************************************************************************************//
//                                      Initial Setup
//*****************************************************************************************//
void setup() {

#if(lcdEn)
  GLCD.Init();
  GLCD.SelectFont(SystemFont5x7);
#endif
  
#if(serEn) 
  Serial.begin(57600);
  Serial.println(F("ANALOGUE CLOCK by Adrian Jones"));
  Serial.print(F("Build ")); Serial.print(build); Serial.print(F(".")); Serial.println(revision);
  Serial.print(F("Free RAM: "));  Serial.print(freeRam()); Serial.println(F("B"));
  Serial.println(F("****************"));
#endif
  
  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()) {
    Serial.println(F("RTC reset"));
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
  // RTC.adjust(DateTime(__DATE__, __TIME__));          // reset to system time
  // RTC.adjust(DateTime(2015, 1, 18, 1, 59, 50 ));     // test
  
  getTime();
  TZ = (IsDST)?-4:-5;
  vdm = sundata(LATITUDE, LONGITUDE, TZ);
  restoreSettings();                      // restore EEPROM settings 

#if(lcdEn)
  drawFace();                             // draw analogue clock face 
#endif
 }
 
//*****************************************************************************************//
//                                      MAIN LOOP
//*****************************************************************************************//
void loop() {
  getTime();
  if(se != osec) {
    doDSTAdjust();
    
    vdm.time(yr, mo, dy, hr, mn, se);           // current year, month, day, hour, minutes and seconds
    vdm.calculations();                              // update calculations for current time
    el_deg = vdm.elevation_deg();                    // store sun's elevation in degrees
    az_deg = vdm.azimuth_deg();                      // store sun's azimuth in degrees
    sunup  = vdm.sunrise_time();                     // store sunrise time in decimal form
    sundn  = vdm.sunset_time();                      // store sunset time in decimal form
    riseset(LATITUDE, LONGITUDE, dy, mo, yr, TZ);  // moon rise and set information

#if(serEn) 
    Serial.print(yr-2000); Serial.print(F("/"));
    if(mo<10) Serial.print(F("0")); Serial.print(mo); Serial.print(F("/"));
    if(dy<10) Serial.print(F("0")); Serial.print(dy); Serial.print(F(" "));
    if(hr<10) Serial.print(F("0")); Serial.print(hr); Serial.print(F(":"));
    if(mn<10) Serial.print(F("0")); Serial.print(mn); Serial.print(F(":"));
    if(se<10) Serial.print(F("0")); Serial.print(se); 

    Serial.print(F("\tE: ")); Serial.print(el_deg); Serial.print(" deg.");
    Serial.print(F("\tA: "));  Serial.print(az_deg); Serial.print(" deg. "); Serial.print(getCardinal(az_deg));
    Serial.print(F("\tRise: ")); Serial.print(int(sunup)); Serial.print(F(":")); if(int((sunup - int(sunup))*60.0) <10) Serial.print(F("0")); Serial.print(int((sunup - int(sunup))*60.0));
    Serial.print(F("\tSet: "));  Serial.print(int(sundn)); Serial.print(F(":")); if(int((sundn - int(sundn))*60.0) <10) Serial.print(F("0")); Serial.print(int((sundn - int(sundn))*60.0));
     
    Serial.print(F("\tMoon P: ")); Serial.print(moonphase());
    Serial.print(F("\tRise: "));  if(Rise_time[0]==0 && Rise_time[1] ==0) { Serial.print(F("none")); } else { Serial.print(Rise_time[0]); Serial.print(F(":")); Serial.print(Rise_time[1]); } 
    Serial.print(F("\tSet: "));   Serial.print(Set_time[0]);  Serial.print(F(":")); Serial.print(Set_time[1]); 
    Serial.println(); 
#endif

#if(lcdEn)
// ********************************************************************************** //
//                                   GRAPHICS DISPLAY
// ********************************************************************************** //

    // time and date
    //GLCD.SelectFont(Callibri15);
    GLCD.CursorToXY(1, 0);      // time
    if(hr<10) GLCD.print(F("0")); GLCD.print(hr); GLCD.print(F(":"));
    if(mn<10) GLCD.print(F("0")); GLCD.print(mn); GLCD.print(F(":"));
    if(se<10) GLCD.print(F("0")); GLCD.print(se);

    GLCD.CursorToXY(143, 0);    // date
    GLCD.print(yr-2000); GLCD.print(F("/"));
    if(mo<10) GLCD.print(F("0")); GLCD.print(mo); GLCD.print(F("/"));
    if(dy<10) GLCD.print(F("0")); GLCD.print(dy);

    drawHands();            // draw clock hands
    
    // azimuth and elevation
    GLCD.CursorToXY(1, 47); GLCD.print(F("E ")); GLCD.print(el_deg,2); GLCD.PutChar(0x80); GLCD.print(F(" ")); 
    doElevation();

    GLCD.CursorToXY(1, 56); GLCD.print(F("A ")); GLCD.print(az_deg,0); GLCD.PutChar(0x80); GLCD.print(F(" ")); GLCD.print(getCardinal(az_deg)); GLCD.print(F(" ")); 
    doAzimuth();
    doMoonInfo(141, 12, 0x81, Rise_time[0], Rise_time[1]);                                // moon rise
    GLCD.CursorToXY(141, 21); GLCD.PutChar(0x84); GLCD.print(moon_up()?"  up ":" down");  // moon state
    doMoonInfo(141, 30, 0x82, Set_time[0],  Set_time[1] );                                // moon set

    // sunrise, sunset, solar noon and moon information    
    if(dy != ody) {                                // gets updates once a day    
      doSunInfo(1, 17, 0x81, sunup);               // sunrise
      doSunInfo(1, 26, 0x83, (sunup+sundn)/2);     // solar noon
      doSunInfo(1, 35, 0x82, sundn);               // sunset
      GLCD.CursorToXY(141, 39); GLCD.print(F("p "));  GLCD.print(moonphase());GLCD.print(F(" "));  // moon phase
      drawMoon(153, 47);                           // moon phase text
      ody = dy;
    }
#endif

    osec=se;
    omin=mn;
    ohr=hr%12;
  }
  delay(200);
}

#if(lcdEn)
// ********************************************************************************** //
//                                 MOON PHASE ROUTINES
// ********************************************************************************** //

void drawMoon(byte tx, byte ty) {
    phase = moonphase();             // get moon phase (0-26)
    phase = (27 + 13 - phase)%27;    // adjust for correct start & direction
    switch(phase) {
      case 0:  case 1:  case 2:  case 3:  case 4:  ms=1; mt=7; mi=0x85; break;    // waxing gibous
      case 5:  case 6:  ms=2; mt=8; mi=0x8C; break;                               // first quarter
      case 7:  case 8:  case 9:  case 10: ms=1; mt=9; mi=0x8B; break;             // waxing crescent
      case 11: case 12: case 13: ms=3; mt=6; mi=0x8A; break;                      // new moon
      case 14: case 15: case 16: case 17: case 18: ms=4; mt=9; mi=0x89; break;    // waning crescent
      case 19: case 20: ms=5; mt=8; mi=0x88; break;                               // last quarter
      case 21: case 22: case 23: case 24: case 25: ms=4; mt=7; mi=0x87; break;    // waning gibous
      case 26: ms=0; mt=6; mi=0x86; break;                                        // full moon
   }
    GLCD.CursorToXY(tx-12, ty);  GLCD.PutChar(mi);
    GLCD.CursorToXY(tx, ty);   strcpy_P(cb, (char*) pgm_read_word(&me[ms])); GLCD.print(cb);
    GLCD.CursorToXY(tx-12, ty+8); strcpy_P(cb, (char*) pgm_read_word(&me[mt])); GLCD.print(cb);
}


// ********************************************************************************** //
//                            SUN & MOON INFORMATION ROUTINES
// ********************************************************************************** //

void doSunInfo(byte px, byte py, byte pc, float si) {
  GLCD.CursorToXY(px, py);
  GLCD.PutChar(pc); GLCD.print(F(" ")); if(int(si) <10) GLCD.print("0"); GLCD.print(int(si)); GLCD.print(F(":"));
  if(int((si - int(si))*60.0) <10) GLCD.print("0"); GLCD.print(int((si - int(si))*60.0));
}

void doMoonInfo(byte mx, byte my, byte mc, byte mh, byte mm) {
  GLCD.CursorToXY(mx, my);
  GLCD.PutChar(mc); GLCD.print(F(" ")); 
  if(mh==0 && mm ==0) { 
    GLCD.print(F("none")); 
  } else { 
    if(mh <10) GLCD.print("0"); GLCD.print(mh); GLCD.print(F(":")); if(mm <10) GLCD.print("0"); GLCD.print(mm);
  }
}


// ********************************************************************************** //
//                                 DRAW HANDS ROUTINES
// ********************************************************************************** //

void drawHands() {
  drawSec(16, osec, WHITE);       drawSec(16, se, BLACK);
  drawMin(23, omin, WHITE);       drawMin(23, mn, BLACK);
  drawHour(17, ohr, omin, WHITE); drawHour(17, hr%12, mn, BLACK);
}

void drawSec(int len, int num, int col) {
  if(num<0) return;
  s = (float) ((num+30)%60)/60*2.0*PI;
  x = len * -sin(s);
  y = len * cos(s);
  if(hr>12 && hr<24) {
    GLCD.DrawCircle(GLCD.CenterX+x, GLCD.CenterY+y, 2, col);
  } else {
    GLCD.FillCircle(GLCD.CenterX+x, GLCD.CenterY+y, 2, col);
  }
}

void drawMin(int len, int num, int col) {
  if(num<0) return;
  s = (float) ((num+30)%60)/60 *2.0*PI;
  x = len * -sin(s);
  y = len * cos(s);
  GLCD.DrawLine(GLCD.CenterX,GLCD.CenterY, GLCD.CenterX+x, GLCD.CenterY+y, col);
}

void drawHour(int len, int num, int nd, int col) {
  if(num<0) return;
  s = (float) ((num*30 + nd/2) / 360.0 + 0.5)*2.0*PI;
  x = len * -sin(s);
  y = len * cos(s);
  GLCD.DrawLine(GLCD.CenterX, GLCD.CenterY, GLCD.CenterX+x, GLCD.CenterY+y, col);
}

void drawFace() {
  GLCD.DrawCircle(GLCD.CenterX,GLCD.CenterY, GLCD.Bottom/2+1);
  for(int i=0; i<360 ; i+=30) {
    s = (float) (i/360.0)*2.0*PI;
    x = 28 * -sin(s);
    y = 28 * cos(s);
    if(i==180) {
      GLCD.FillRect(GLCD.CenterX+x-2, GLCD.CenterY+y-2, 4, 4);
    } else {
      GLCD.DrawRoundRect(GLCD.CenterX+x-1, GLCD.CenterY+y-1, 2, 2, 1, BLACK);
      //GLCD.DrawCircle(GLCD.CenterX+x, GLCD.CenterY+y, 1);
    }
  }
}

// ********************************************************************************** //
//                            AZIMUTH & ELEVATION INDICATORS
// ********************************************************************************** //


void doAzimuth() {
  azi = int(az_deg);
  if(azi == oazi) return;
  azi = (azi+180)%360;
  if(oazi < 360) doAzCircle(oazi, 23, WHITE);
  doAzCircle(azi, 23, BLACK);
  oazi=azi;  
}

void doAzCircle(int d, byte l, byte col) {
  x = -l * sin(PI * d/180);
  y =  l * cos(PI * d/180);
  GLCD.FillCircle(GLCD.CenterX+x, GLCD.CenterY+y, 2, col);
}

void doElevation() {
  ele = int(el_deg);
  if(ele == oele) return;
  ele = (270-ele)%360;
  if(oele < 360) drawSeg(oele, 26, 21, WHITE);
  drawSeg(ele, 26, 21, BLACK);
  oele=ele;  
}

void drawSeg(int d, byte l1, byte l2, byte col) {
    x = -l1 * sin(PI * d/180.0);
    y =  l1 * cos(PI * d/180.0);
    x1 = -l2 * sin(PI * d/180.0);
    y1 =  l2 * cos(PI * d/180.0);
    GLCD.DrawLine(GLCD.CenterX+x, GLCD.CenterY+y, GLCD.CenterX+x1, GLCD.CenterY+y1, col);
}

#endif

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


/* ********************************************************************************** //
//                                       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
}

// ********************************************************************************** //
//                         MOON PHASE AND MOON RISE/ SET 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. new scaling for 0-26 (i.e. 27 unique) bitmaps
  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, 26);                      // map to 0-26
  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;  
}

// returns value for sign of argument
float sgn(float x) {
    float rv;
    if (x > 0.0)
    rv = 1;
    else if (x < 0.0)
    rv = -1;
    else
    rv = 0;
    return rv;
}

// determine Julian day from calendar date: Jean Meeus, "Astronomical Algorithms", Willmann-Bell, 1991
float julian_day(const int day_const, const int month_const, const int year_const) {
    float a, b, jd;
    bool gregorian;

    int month = month_const;
    int day = day_const;
    float year = (float) year_const;

    gregorian = (year < 1583) ? 0 : 1;

    if ((month == 1) || (month == 2)) {
    year = year - 1;
    month = month + 12;
    }

    a = floor(year / 100);
    if (gregorian)
    b = 2.0 - a + floor(a / 4.0);
    else
    b = 0.0;

    jd = floor(365.25 * (float)(year + 4716.0))
    + floor(30.6001 * (float)(month + 1))
    + day + b - 1524.5;

    return jd;
}

// moon's position using fundamental arguments by Van Flandern & Pulkkinen, 1979
void moonpos(float jd) {

  float d, f, g, h, m, n, s, u, v, w;

    h = 0.606434 + 0.03660110129 * jd;
    m = 0.374897 + 0.03629164709 * jd;
    f = 0.259091 + 0.0367481952 * jd;
    d = 0.827362 + 0.03386319198 * jd;
    n = 0.347343 - 0.00014709391 * jd;
    g = 0.993126 + 0.0027377785 * jd;

    h = h - floor(h);
    m = m - floor(m);
    f = f - floor(f);
    d = d - floor(d);
    n = n - floor(n);
    g = g - floor(g);

    h = h * 2 * M_PI;
    m = m * 2 * M_PI;
    f = f * 2 * M_PI;
    d = d * 2 * M_PI;
    n = n * 2 * M_PI;
    g = g * 2 * M_PI;

    v = 0.39558 * sin(f + n);
    v = v + 0.082 * sin(f);
    v = v + 0.03257 * sin(m - f - n);
    v = v + 0.01092 * sin(m + f + n);
    v = v + 0.00666 * sin(m - f);
    v = v - 0.00644 * sin(m + f - 2 * d + n);
    v = v - 0.00331 * sin(f - 2 * d + n);
    v = v - 0.00304 * sin(f - 2 * d);
    v = v - 0.0024 * sin(m - f - 2 * d - n);
    v = v + 0.00226 * sin(m + f);
    v = v - 0.00108 * sin(m + f - 2 * d);
    v = v - 0.00079 * sin(f - n);
    v = v + 0.00078 * sin(f + 2 * d + n);

    u = 1 - 0.10828 * cos(m);
    u = u - 0.0188 * cos(m - 2 * d);
    u = u - 0.01479 * cos(2 * d);
    u = u + 0.00181 * cos(2 * m - 2 * d);
    u = u - 0.00147 * cos(2 * m);
    u = u - 0.00105 * cos(2 * d - g);
    u = u - 0.00075 * cos(m - 2 * d + g);

    w = 0.10478 * sin(m);
    w = w - 0.04105 * sin(2 * f + 2 * n);
    w = w - 0.0213 * sin(m - 2 * d);
    w = w - 0.01779 * sin(2 * f + n);
    w = w + 0.01774 * sin(n);
    w = w + 0.00987 * sin(2 * d);
    w = w - 0.00338 * sin(m - 2 * f - 2 * n);
    w = w - 0.00309 * sin(g);
    w = w - 0.0019 * sin(2 * f);
    w = w - 0.00144 * sin(m + n);
    w = w - 0.00144 * sin(m - 2 * f - n);
    w = w - 0.00113 * sin(m + 2 * f + 2 * n);
    w = w - 0.00094 * sin(m - 2 * d + g);
    w = w - 0.00092 * sin(2 * m - 2 * d);

    s = w / sqrt(u - v * v);    // compute moon's right ascension ...  
    Sky[0] = h + atan(s / sqrt(1 - s * s));

    s = v / sqrt(u);        // declination ...
    Sky[1] = atan(s / sqrt(1 - s * s));

    Sky[2] = 60.40974 * sqrt(u);    // and parallax
}

// test an hour for an event
float test_moon(int k, float t0, float lat, float plx) {

const static float K1 = 15 * M_PI * 1.0027379 / 180;
static float Rise_az = 0.0, Set_az = 0.0;

    float ha[3] = { 0.0, 0.0, 0.0 };
    float a, b, c, d, e, s, z;
    float hr, min, time;
    float az, hz, nz, dz;

    if (RAn[2] < RAn[0])
    RAn[2] = RAn[2] + 2 * M_PI;

    ha[0] = t0 - RAn[0] + (k * K1);
    ha[2] = t0 - RAn[2] + (k * K1) + K1;

    ha[1] = (ha[2] + ha[0]) / 2;    // hour angle at half hour
    Dec[1] = (Dec[2] + Dec[0]) / 2;    // declination at half hour

    s = sin(DR * lat);
    c = cos(DR * lat);

    // refraction + sun semidiameter at horizon + parallax correction
    z = cos(DR * (90.567 - 41.685 / plx));

    if (k <= 0)            // first call of function
    VHz[0] = s * sin(Dec[0]) + c * cos(Dec[0]) * cos(ha[0]) - z;

    VHz[2] = s * sin(Dec[2]) + c * cos(Dec[2]) * cos(ha[2]) - z;

    if (sgn(VHz[0]) == sgn(VHz[2]))
    return VHz[2];        // no event this hour

    VHz[1] = s * sin(Dec[1]) + c * cos(Dec[1]) * cos(ha[1]) - z;

    a = 2 * VHz[2] - 4 * VHz[1] + 2 * VHz[0];
    b = 4 * VHz[1] - 3 * VHz[0] - VHz[2];
    d = b * b - 4 * a * VHz[0];

    if (d < 0)
    return VHz[2];        // no event this hour

    d = sqrt(d);
    e = (-b + d) / (2 * a);

    if ((e > 1) || (e < 0))
    e = (-b - d) / (2 * a);

    time = ((float) k) + e + 1 / 120;    // time of an event + round up
    hr = floor(time);
    min = floor((time - hr) * 60);

    hz = ha[0] + e * (ha[2] - ha[0]);    // azimuth of the moon at the event
    nz = -cos(Dec[1]) * sin(hz);
    dz = c * sin(Dec[1]) - s * cos(Dec[1]) * cos(hz);
    az = atan2(nz, dz) / DR;
    if (az < 0)
    az = az + 360;

    if ((VHz[0] < 0) && (VHz[2] > 0)) {
    Rise_time[0] = (int) hr;
    Rise_time[1] = (int) min;
    Rise_az = az;
    Moonrise = 1;
    }

    if ((VHz[0] > 0) && (VHz[2] < 0)) {
    Set_time[0] = (int) hr;
    Set_time[1] = (int) min;
    Set_az = az;
    Moonset = 1;
    }

    return VHz[2];
}

// Local Sidereal Time for zone
float lst(const float lon, const float jd, const float z) {
    float s =
    24110.5 + 8640184.812999999 * jd / 36525 + 86636.6 * z +
    86400 * lon;
    s = s / 86400;
    s = s - floor(s);
    return s * 360 * DR;
}

// 3-point interpolation
float interpolate(const float f0, const float f1, const float f2, const float p) {
    float a = f1 - f0;
    float b = f2 - f1 - a;
    float f = f0 + p * (2 * a + b * (2 * p - 1));
    return f;
}


// calculate moonrise and moonset times
void riseset(const float lat, const float lon, const int day, const int month, const int year, const int TimezoneOffset) {
    int i, j, k;
    float ph;
    // guido: julian day has been converted to int from float
//    float jd = (julian_day(day, month, year)) - 2451545;    // Julian day relative to Jan 1.5, 2000
    float jd = (julian_day(day, month, year)) - 2451545;    // Julian day relative to Jan 1.5, 2000
    float mp[3][3];
    float lon_local = lon;

    for (i = 0; i < 3; i++) {
    for (j = 0; j < 3; j++)
        mp[i][j] = 0.0;
    }

    lon_local = lon / 360;
    float tz = -((float)TimezoneOffset) / 24;
    float t0 = lst(lon_local, jd, tz);    // local sidereal time

    jd = jd + tz;        // get moon position at start of day

    for (k = 0; k < 3; k++) {
    moonpos(jd);
    mp[k][0] = Sky[0];
    mp[k][1] = Sky[1];
    mp[k][2] = Sky[2];
    jd = jd + 0.5;
    }

    if (mp[1][0] <= mp[0][0])
    mp[1][0] = mp[1][0] + 2 * M_PI;

    if (mp[2][0] <= mp[1][0])
    mp[2][0] = mp[2][0] + 2 * M_PI;

    RAn[0] = mp[0][0];
    Dec[0] = mp[0][1];

    Moonrise = 0;        // initialize
    Moonset = 0;

    for (k = 0; k < 24; k++)    // check each hour of this day
    {
    ph = ((float) (k + 1)) / 24;

    RAn[2] = interpolate(mp[0][0], mp[1][0], mp[2][0], ph);
    Dec[2] = interpolate(mp[0][1], mp[1][1], mp[2][1], ph);

    VHz[2] = test_moon(k, t0, lat, mp[1][2]);

    RAn[0] = RAn[2];    // advance to next hour
    Dec[0] = Dec[2];
    VHz[0] = VHz[2];
    }
}

// check for no moonrise and/or no moonset
int moon_up() { 
  int riseMin=(Rise_time[0]*60)+Rise_time[1];
  int setMin=(Set_time[0]*60)+Set_time[1];
  int nowMin=(hr*60)+mn;
  if ((!Moonrise) && (!Moonset)) { // neither moonrise nor moonset
    if (VHz[2] < 0) return(0); // down all day
    else return(1); // up all day
    }
  
  if (Moonrise && Moonset) {
    if ((setMin > riseMin) && (riseMin < nowMin) && (nowMin < setMin)) return(1); // up
    if ((setMin < riseMin) && ((nowMin < setMin) || (nowMin > riseMin))) return(1); // up
  }
  
  if (Moonrise && (!Moonset)) { // Moonrise only
    if (nowMin > riseMin) return(1); 
  }
  
  if (Moonset && (!Moonrise)) { // Moonset only
    if (nowMin < setMin) return(1);
  }
  return(0); // if in doubt turn blow it out
}

// ********************************************************************************** //
//                                       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]

New Moon tomorrow!

2 thoughts on “Graphic LCD Analogue Clock

  1. Robert

    You could probably free up some more program space by getting rid of floating-point arithmetic. From what I understand, floating-point is a real killer.

    For starters, you could remove the floating-point from your Julian day calculation fairly easily.

    Trigonometry might require a lookup table, which would require PROGMEM, but it would probably still be a net win. In terms of calculation time, it would almost certainly be a win.

    You could replace the three-point interpolation with a simple binary search. With fast trig (from the lookup table), this would probably work reasonably well.

    Reply

Come on... leave a comment...