/* GRANDFATHER CLOCK
* David Henshaw -- November 2016 -
	November 2016 - basic code
	December 2016 - most clock features
	January 2017 - additional tweaks and error logic
	February 2017 - new stepper motors
	March 2017 - chime
*/
#pragma region Includes
#include <Adafruit_NeoPixel.h>
#include <DS1307RTC.h> // http://www.pjrc.com/teensy/td_libs_DS1307RTC.html
#include <TimeLib.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "avr/pgmspace.h"        //for flash memory storage
#include "EEPROM.h"              //for eeprom storage
#pragma endregion
#pragma region Menuing system
// Flash Storage: 20 chrs max       12345678901234567890 - these go into flash (program) memory
// prog_char is not valid for new Arduino compiler - now use const char
const char menuItem_0[]  PROGMEM = "Chime";     //menu items start here
const char menuItem_1[]  PROGMEM = "Secs sleep";
const char menuItem_2[]  PROGMEM = "Secs wake";
const char menuItem_3[]  PROGMEM = "TickTock sleep";
const char menuItem_4[]  PROGMEM = "TickTock wake";
const char menuItem_5[]  PROGMEM = "Chime sleep";
const char menuItem_6[]  PROGMEM = "Chime wake";
const char menuItem_7[]  PROGMEM = "Weekend wake";
const char menuItem_8[]  PROGMEM = "Weekend sleep";
const char menuItem_9[]  PROGMEM = "Hour";
const char menuItem_10[]  PROGMEM = "Time";
const char menuItem_11[]  PROGMEM = "Year";
const char menuItem_12[] PROGMEM = "Month";
const char menuItem_13[] PROGMEM = "Day";
const char menuItem_14[] PROGMEM = "Recalibrate";
 
const char menuItem_15[] PROGMEM = "Turn & push to set";  //status messages from this point on
const char menuItem_16[] PROGMEM = "Silent Mode"// ** remove if not used
const char menuItem_17[] PROGMEM = "Push to start";
// 20 characters maximum:           12345678901234567890
 
// Then set up a table to refer to the strings:
// changes from PROGMEM const char *menuTable[] = { to following due to compiler change
const char * const menuTable[] PROGMEM = {
	menuItem_0,  menuItem_1,  menuItem_2,  menuItem_3,  menuItem_4,  menuItem_5,  menuItem_6,  menuItem_7,
	menuItem_8,  menuItem_9,  menuItem_10,  menuItem_11,  menuItem_12,  menuItem_13,  menuItem_14,  menuItem_15,
	menuItem_16, menuItem_17 };
#pragma endregion
#pragma region Constants
const int stepperHoursStepPin = 20;					// Digital Pins via shift registers - stepper motor control - 4 pins start here
const int stepperMinutesStepPin = 16;				// See Shift Registers tab in Google Drive
const int stepperCalendarStepPin = 9;				// 
const int stepperMoonDialStepPin = 5;
const int stepperChimeStepPin = 1;
const int stepperSecondsStepPin = 25;
 
const int stepperDelay = 3;							//delay between step commands ** is this used?
 
const byte buttonPin = 13;							//this is the single button on the control panel
const byte encoderButtonPin = 12;					// button on rotary encoder
const byte encoderDelay = 200;						// delay used when repeatedly reading encoder dial
 
const int calendarHallEffectSensor = 11;				// the number of the hall effect sensor pin
const int hoursHallEffectSensor = 9;			
const int minutesHallEffectSensor = 8;
const int secondsHallEffectSensor = 10;
const int moonDialHallEffectSensor = 14;			// 14 = analog pin A0
const int chimeHallEffectSensor = 15;				// 15 = analog pin A1
 
const int maximumSecondsThisHour = 60 * 60;			// 60 seconds * 60 minutes
const word maximumSecondsThisHalfDay = 60 * 60 * 12;
const int maximumDaysThisMonth = 31;
const int maximumDaysThisLunarMonth = 59;			// sum of days in two lunar months (2 * 29.5)
 
const int maximumMotorSteps = 1000;					// maximum number of allowable turns for any motor
 
const byte errorCodeMaximumMotorSteps = 1;			// error codes
const byte errorCodeRTCStopped = 2;
const byte errorCodeRTCCircuitError = 3;
 
const byte chimeGong = 0;							// shift register index pin used to activate solenoid
const byte tickSolenoid = 13;
const byte tockSolenoid = 14;
const byte onTheHour = 0;
const byte quarterPast = 15;
const byte halfPast = 30;
const byte quarterTo = 45;
 
const byte neoPixelPin = 7;							// data wire for neopixels
const byte powerLight = 0;							// NeoPixel LEDs (start at 0 thru 11) 
const byte silentModeLight = 1;
const byte RTCLight = 2;
const byte secondHandLight = 3;
const byte minuteHandLight = 4; 
const byte hourHandLight = 5;
const byte calendarHandLight = 6;
const byte moonDialLight = 7;
const byte chimeLight = 8;
const byte gongLight = 9;
const byte tickTockLight = 11;
const byte chimeOnLight = 10;
 
const byte maxMenuItems = 14;						// how many menu items there are
const byte maxInformationMessageIndex = 5;			// max number of informational messages to toggle between
const byte msgUseDial = 15;							// "Turn dial"
const byte msgSilent = 16;
const byte msgPush = 17;
 
const byte westminsterChimesSetOne = 95;			// steps of rotation to play set one of Westminster chimes
const byte westminsterChimesSetTwo = 98;			// in general each set requires 64 steps
const byte westminsterChimesSetThree = 103;			// then there is "air space" between each set
const byte westminsterChimesSetFour = 105;
const byte westminsterChimesSetFive = 99;			// this one ends early to leave room for a mandatory recalibration
 
// Constants for EEPROM memory (refers to index location in EEPROM where we will store the value) using EEPROM.write(address, value) and EEPROM.read(address, value)
const byte chimeSwitch_EEPROM = 0;
const byte secondsSleepHour_EEPROM = 1;
const byte secondsWakeHour_EEPROM = 2;
const byte chimeSleepHour_EEPROM = 3;
const byte chimeWakeHour_EEPROM = 4;
const byte tickTockWakeHour_EEPROM = 5;
const byte tickTockSleepHour_EEPROM = 6;
const byte weekendSleepHour_EEPROM = 7;
const byte weekendWakeHour_EEPROM = 8;
 
#define number_of_74hc595s 4						// How many shift registers
#define numOfRegisterPins number_of_74hc595s * 8	// do not touch
#pragma endregion
#pragma region Variables
//boolean clockSleep = false;
//boolean priorClockSleep = false;
boolean chimeSleep = false;
boolean secondsSleep = false;
boolean tickTockSleep = false;
boolean silentMode = false;
boolean tickSound = false;
byte previousSeconds = 0;
byte previousDay = 0;
 
boolean atSecondHandRecalibrationPoint = false;		// flag to determine if second hand has reached recalibration sensor
 
int maximumStepsThisMinute = 512; // was 407
int maximumStepsThisHour = 513;						// motor steps in one hour for the minute hand ** if all are 512, make a single constant!
int maximumStepsThisHalfDay = 512;					// motor steps in one half day for the hour hand
int maximumStepsThisMonth = 512;					// motor steps in one half day for the calendar hand
int maximumStepsThisLunarMonth = 512;				// technically steps for 2 lunar months (moon dial covers two lunar months)
int maximumStepsThisChimingRotation = 512;
 
int currentStepCountForSecondHand = 0;
int currentStepCountForMinuteHand = 0;
int currentStepCountForHourHand = 0;
int currentStepCountForCalendarHand = 0;
int currentStepCountForMoonDial = 0;
int currentStepCountForChime = 0;
int previousStepCountForHourHand = maximumStepsThisHalfDay;
int previousStepCountForMinuteHand = maximumStepsThisHour;
int previousStepCountForSecondHand = maximumStepsThisMinute;
int previousStepCountForCalendarHand = maximumStepsThisMonth;
int previousStepCountForMoonDial = maximumStepsThisLunarMonth;
int previousStepCountForChime = currentStepCountForChime;
int targetSecondPartStepCountForSecondHand = 0;				// used for the second part of the seconds hand swing 
byte secondsStepSequenceNumber = 0;					// used to keep track of stepping sequence for seconds hand (1-4)
 
boolean previousMinutesHallEffectSensor = false;
boolean previousHoursHallEffectSensor = false;
boolean previousSecondsHallEffectSensor = false;
boolean previousCalendarHallEffectSensor = false;
boolean previousMoonDialHallEffectSensor = false;
boolean pauseMinuteHand = false;
boolean pauseHourHand = false;
boolean pauseSecondHand = false;
boolean pauseCalendarHand = false;
boolean pauseMoonDial = false;
 
byte moonDialSegment = 1;		// which half of moon dial is visible (1 or 2)
byte currentChimeSegment = 1;	// which chime segment do we think we're at? After recalibration, = 1
 
boolean errorStatus = false;	// used to denote any kind of error condition
boolean previousErrorStatus = false;
byte errorCode = 0;
 
//Variables for shift register
byte SER_Pin = 5;									// pin 14 on the 75HC595 Shift Register (Data)
byte RCLK_Pin = 6;									// pin 12 on the 75HC595 Shift Register (Latch)
byte SRCLK_Pin = 4;									// pin 11 on the 75HC595 Shift Register (Clock)
boolean registers[numOfRegisterPins];				// bit array for the total number of 595 registers 
 
// Variables for rotary encoder
byte encoderPin1 = 2;								// these DIGITAL pins can not be changed 2/3 are special pins 
byte encoderPin2 = 3;								// used for interrupts on rotary encoder
volatile byte lastEncoded = 0;						// for interrupt routine - do not change
volatile byte encoderValue = 1;						// for interrupt routine - do not change
byte lastEncoderValue = 1;
const byte encoderGap = 2;							// size of gap from encoder before we pay attention to it
 
// Variables for menuing system
 
char buffer[20];									// max size of message, and max size of lcd screen
byte activeMenuSelection = 0;						// Which menu item is active
byte selectedMenuItem = 99;
byte informationMessageIndex = 0;
 
boolean chimeSwitch = false;						// is chime on or off
byte secondsSleepHour;
byte secondsWakeHour;
byte chimeSleepHour;
byte chimeWakeHour;
byte tickTockWakeHour;
byte tickTockSleepHour;
byte weekendSleepHour;
byte weekendWakeHour;
 
tmElements_t tm;									// hours, minutes, seconds, etc. stored in tm.*
int stepperMotorLookup[8] = { B01000B01100B00100B00110B00010B00011B00001B01001 };	// table used to send bit pattern to stepper motors
#pragma endregion
 
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address - crazy 0x3F address!!
																// Parameter 1 = number of pixels in strip
																// Parameter 2 = pin number (most are valid)
																// Parameter 3 = pixel type flags, add together as needed:
																//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
																//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
																//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
																//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, neoPixelPin, NEO_GRB + NEO_KHZ800);
 
#pragma region Colour Codes
// Define the color codes by name to make it easier to call later on  ** remove unwanted codes
uint32_t red = strip.Color(200, 0, 0);
//uint32_t brown = strip.Color(102, 51, 0);
//uint32_t orange = strip.Color(204, 102, 0);
uint32_t yellow = strip.Color(255, 255, 0);
uint32_t green = strip.Color(95, 200, 0);
uint32_t dgreen = strip.Color(0, 255, 0);
uint32_t blue = strip.Color(0, 0, 255);
uint32_t white = strip.Color(255, 255, 255);
uint32_t black = strip.Color(0, 0, 0);
#pragma endregion
void setup() {
#pragma region Initialize shift registers
	pinMode(SER_Pin, OUTPUT);						// Shift Register pin
	pinMode(RCLK_Pin, OUTPUT);						// Shift Register pin
	pinMode(SRCLK_Pin, OUTPUT);						// Shift Register pin
	clearRegisters();								// reset all register pins
#pragma endregion
#pragma region Hall Effect Sensor Setup
	pinMode(hoursHallEffectSensor, INPUT);			// initialize the hall effect sensor pins as an input
	pinMode(minutesHallEffectSensor, INPUT);
	pinMode(secondsHallEffectSensor, INPUT);
	pinMode(calendarHallEffectSensor, INPUT);
	pinMode(moonDialHallEffectSensor, INPUT);
	pinMode(chimeHallEffectSensor, INPUT); 
#pragma endregion
#pragma region Initialize buttons
	pinMode(buttonPin, INPUT);						// set button on control panel to input
	digitalWrite(buttonPin, HIGH);					// turn on pullup resistors
	pinMode(encoderButtonPin, INPUT);				// set button on rotary encoder to input
	digitalWrite(encoderButtonPin, HIGH);			// turn on pullup resistors
#pragma endregion
#pragma region Configure rotary encoder
	pinMode(encoderPin1, INPUT);					// rotary encoder
	pinMode(encoderPin2, INPUT);					// interrupt pins
	digitalWrite(encoderPin1, HIGH);				// turn pullup resistor on
	digitalWrite(encoderPin2, HIGH);				// turn pullup resistor on
	attachInterrupt(0, updateEncoder, CHANGE);		// call updateEncoder() when any high/low changed seen
	attachInterrupt(1, updateEncoder, CHANGE);		// on interrupt 0 (pin 2), or interrupt 1 (pin 3) 
#pragma endregion
#pragma region NeoPixel wire setup
 
	strip.begin();
	strip.setBrightness(20);						// 0 - 255
	strip.show();									// Initialize all pixels to 'off'
	
#pragma endregion
#pragma region RTC Setup
	tm.Hour = 17;
	tm.Minute = 56;
	tm.Second = 04;
	tm.Month = 12;
	tm.Day = 27;
	tm.Year = CalendarYrToTm(2016);
	//RTC.write(tm);								// and configure the RTC with this info (** remove for production code)
#pragma endregion
#pragma region LCD setup
	lcd.begin(20, 4);								// initialize the lcd for 20 chars 4 lines, turn on backlight
	lcd.backlight();								// turn on the backlight
#pragma endregion
#pragma region Refresh variables from EEPROM
	chimeSwitch = EEPROM.read(chimeSwitch_EEPROM);
	secondsSleepHour = EEPROM.read(secondsSleepHour_EEPROM);
	secondsWakeHour = EEPROM.read(secondsWakeHour_EEPROM);
	chimeSleepHour = EEPROM.read(chimeSleepHour_EEPROM);
	chimeWakeHour = EEPROM.read(chimeWakeHour_EEPROM);
	tickTockSleepHour = EEPROM.read(tickTockSleepHour_EEPROM);
	tickTockWakeHour = EEPROM.read(tickTockWakeHour_EEPROM);
	weekendSleepHour = EEPROM.read(weekendSleepHour_EEPROM);
	weekendWakeHour = EEPROM.read(weekendWakeHour_EEPROM);
 
#pragma endregion
 
//recalibrateAllHands();							//** remove comment-out for production code
	recalibrateSecondsHand();
RTCCheck();											// check to make sure RTC is working 
waitForUserAction(); 
setStatusLight(powerLight, dgreen);					// all systems go!
}
void loop()
{
#pragma region debug code
	while (9 > 13) {	// enter debug code here
		
 
 
	}
#pragma endregion
 
	
#pragma region clock code
	
	RTC.read(tm);														// find out the time
	if (timeHasChanged(tm.Second, previousSeconds)) checkWakeSleepTimers();												// see if it's time to sleep or wake the clock or the chime
	updateMainMovement();												// move hands and update display	
 
	if ((errorStatus == true) && (previousErrorStatus == false)) {		// if we're in an error mode, and weren't the last time round
		displayErrorMessage();
	} else {															// not in error mode
		if (chimeSwitch == true && chimeSleep == false
			&& silentMode == false
			 && tm.Second == 0) 
				determineChime(tm.Hour, tm.Minute);						// we are not in chime sleep and not in clock sleep - check for chimes each 15 mins
		if (silentMode == false) secondPartOfSecondsHandMove(tm.Second);// executing this when time hasn't changed - set flag?
	
	}
 
	previousErrorStatus = errorStatus;
 
	//delay(20);														// wait a bit until looping; it's only a clock
	checkDial();														// update LCD if dial was turned
	setStatusLight(RTCLight, black);
	if (isEncoderButtonPressed()) menuSystem(activeMenuSelection);		// if button pressed, enter menuing system
	checkManualSleepButton();											// go to sleep if the button is pressed
	
#pragma endregion
}
 
void waitForUserAction() {
	// wait for user to push dial before continuing (allows for hands and dials to be adjusted)
	printStatusMessage(msgPush); // "push to start" message
	do {
		setStatusLight(powerLight, blue);
		delay(400);
		setStatusLight(powerLight, black);
		delay(400);
	} while (isEncoderButtonPressed() == false);					// exit when the button is pressed to set time       
	delay(encoderDelay);											// pause to allow time for button to be released
	clearLine(1);
}
void resetLEDs() {												// set all LEDs except first two to black - as long as they're not red (error)
	for (byte i = 2; i < 12; i++) {
		if (strip.getPixelColor(i) != red) setStatusLight(i, black);		
	}
 
	// set chimeLight to blue if applicable
	if (chimeSwitch == true && chimeSleep == false
		&& silentMode == false) setStatusLight(chimeLight, blue);
}
void stepOnce(byte stepperMotorPinint bitStream)			// set pins to ULN2003 high in sequence from 1 to 4
{															// thanks http://www.4tronix.co.uk/arduino/Stepper-Motors.php 
	setRegisterPin(stepperMotorPin + 0, bitRead(stepperMotorLookup[bitStream], 0));
	setRegisterPin(stepperMotorPin + 1, bitRead(stepperMotorLookup[bitStream], 1));
	setRegisterPin(stepperMotorPin + 2, bitRead(stepperMotorLookup[bitStream], 2));
	setRegisterPin(stepperMotorPin + 3, bitRead(stepperMotorLookup[bitStream], 3));
	writeRegisters();										// step!
}
void updateMainMovement() {
	if (timeHasChanged(tm.Second, previousSeconds)) {
		resetLEDs();
		setStatusLight(RTCLight, blue); 
		previousSeconds = tm.Second;
		displayTime();
		if (tickTockSleep == false && silentMode == false) tickTock();
		updateClockFaceHands(); 
		displayInformationMessages();
	}
 
	if (dayHasChanged(tm.Day, previousDay)) {
		if (tm.Day < previousDay) recalibrateCalendarHand();	// new month has started, so recalibrate to reset
		updateCalendarHand(tm.Day);								
		updateMoonDial(tmYearToCalendar(tm.Year), tm.Month, tm.Day);
		previousDay = tm.Day;
		// clear out registers so stepper motors don't receive electricity when idle
		clearRegisters();
	}
	
#pragma region Automatic recalibration
	// every morning, at 5:25:00am, recalibrate hour hand and minute hand
	if (tm.Hour == 5 && tm.Minute == 25 && tm.Second == 0) {
		recalibrateHoursHand();
		recalibrateMinutesHand();
	}
 
	// on the 10th of each month, at 5:17:00am, recalibrate calendar hand and lunar dial
	if (tm.Day == 10 && tm.Hour == 5 && tm.Minute == 17 && tm.Second == 0) {
		recalibrateCalendarHand();
		recalibrateMoonDial();
		previousDay = 0;	// force dial to update both calendar and moon dial
	}
#pragma endregion
 
}
void checkWakeSleepTimers() {
	// determine if it's chime wake or sleep mode
	if (chimeSleepHour > chimeWakeHour) {
		if (tm.Hour >= chimeWakeHour && tm.Hour < chimeSleepHour) {
			chimeSleep = false;	// we're in wake mode
		}
		else {
			chimeSleep = true;	// we're in sleep mode
		}
	}
	else {
		if (tm.Hour >= chimeSleepHour && tm.Hour <= chimeWakeHour) {
			chimeSleep = true;
		}
		else {
			chimeSleep = false;
		}
	}
 
 
	// determine if it's seconds wake or sleep mode
	if (secondsSleepHour > secondsWakeHour) {
		if (tm.Hour >= secondsWakeHour && tm.Hour < secondsSleepHour) {
			secondsSleep = false;	// we're in wake mode
		}
		else {
			secondsSleep = true;	// we're in sleep mode
		}
	}	else {
		if (tm.Hour >= secondsSleepHour && tm.Hour <= secondsWakeHour) {
			secondsSleep = true;
		}
		else {
			secondsSleep = false;
		}
	}
 
	// determine if it's ticker wake or sleep mode
	
	if (tickTockSleepHour > tickTockWakeHour) {
		if (tm.Hour >= tickTockWakeHour && tm.Hour < tickTockSleepHour) {
			tickTockSleep = false;	// we're in wake mode
		}
		else {
			tickTockSleep = true;	// we're in sleep mode
		}
	}
	else {
		if (tm.Hour >= tickTockSleepHour && tm.Hour <= tickTockWakeHour) {
			tickTockSleep = true;
		}
		else {
			tickTockSleep = false;
		}
	}
 
	// special consideration for weekend hours
 
	if (tm.Wday == 6) {	// Friday - stay up late
		if ((tm.Hour >= tickTockSleepHour || tm.Hour >= secondsSleepHour || tm.Hour >= chimeSleepHour) && tm.Hour < weekendSleepHour) {
			tickTockSleep = false;	// we're in wake mode
			secondsSleep = false;
			chimeSleep = false;
		}
	}
 
	if (tm.Wday == 7) { // Saturday - easiest rule to implement
			if (tm.Hour >= weekendWakeHour && tm.Hour < weekendSleepHour) {
				tickTockSleep = false;	// we're in wake mode
				secondsSleep = false;
				chimeSleep = false;
			}
		}
	
	if (tm.Wday == 1) {	// Sunday - wake up early
		if ((tm.Hour < tickTockWakeHour || tm.Hour < secondsWakeHour || tm.Hour < chimeWakeHour) && tm.Hour >= weekendWakeHour) {
			tickTockSleep = false;	// we're in wake mode
			secondsSleep = false;
			chimeSleep = false;
		}
	}
 
}
void checkManualSleepButton() {
	if (isManualButtonPressed() == true) {
		//clock goes to or from silent mode
		silentMode = !silentMode;
		if (silentMode == true) {
			setStatusLight(silentModeLight, blue);
			printStatusMessage(msgSilent); // "turn and push to set" message
			recalibrateSecondsHand();
		} else {
			setStatusLight(silentModeLight, black);
			clearLine(1);
		}
		delay(800); //** debounce
	}
}
void displayInformationMessages() {
	if (tm.Second % 5 == 0) {											// every 5 seconds do this
		informationMessageIndex++;										// let's display the next info message
		clearLine(3);
		if (informationMessageIndex > maxInformationMessageIndex) informationMessageIndex = 0; // reset if we went beyond
 
		switch (informationMessageIndex) {
		case 0:
			displayStepCounts(3, "H", currentStepCountForHourHand, maximumStepsThisHalfDay, previousStepCountForHourHand); // what are current motor step counts?
			break;
		case 1:
			displayStepCounts(3, "M", currentStepCountForMinuteHand, maximumStepsThisHour, previousStepCountForMinuteHand); // what are current motor step counts?
			break;
		case 2:
			displayStepCounts(3, "S", currentStepCountForSecondHand, maximumStepsThisMinute, previousStepCountForSecondHand); // what are current motor step counts?
			break;
		case 3:
			displayStepCounts(3, "C", currentStepCountForCalendarHand, maximumStepsThisMonth, previousStepCountForCalendarHand); // what are current motor step counts?
			break;
		case 4:
			displayStepCounts(3, "L", currentStepCountForMoonDial, maximumStepsThisLunarMonth, previousStepCountForMoonDial); // what are current motor step counts?
			lcd.print("D:");
			lcd.print(calculateMoonPhase(tmYearToCalendar(tm.Year), tm.Month, tm.Day));	// determine Lunar day (0-29.5));
			break;
		case 5:
			lcd.setCursor(0, 3);
			lcd.print("Chime ");
			if (chimeSwitch == true && chimeSleep == false) {
				printLCDOptionStatus(true);								// chime is currently on
			}
			else {
				printLCDOptionStatus(false);							// chime is currently off
			}
			lcd.print(" Set ");
			lcd.print(currentChimeSegment);
			break;
		}
	}
}
void displayErrorMessage() {
	lcd.setCursor(0, 2);
	lcd.print("ERROR ");
	lcd.print(errorCode);
	lcd.print(" @ ");
	printLCDDigits(tm.Hour, "");
	printLCDDigits(tm.Minute, ":");
	printLCDDigits(tm.Second, ":");
	setStatusLight(powerLight, red);
}
void displayStepCounts(byte linechartypeint currentint maximumint prior) {
	lcd.setCursor(0, line);
	lcd.print(type);
	lcd.print(":");
	lcd.print(current);
	lcd.print("/");
	lcd.print(maximum);
	lcd.print(" (");
	displayDeltaBetweenTwoValues(maximumprior);
	lcd.print(") ");
}
void displayDeltaBetweenTwoValues(int valueOneint valueTwo) {
	int difference = valueOne - valueTwo;
	if (difference > 0) lcd.print("+");
	lcd.print(difference);
}
boolean isManualButtonPressed() {
	boolean buttonState = false;
	if (digitalRead(buttonPin) == false)  buttonState = true// read button. If currently pressed, return true
	return buttonState;
}
boolean isEncoderButtonPressed() {
	boolean buttonState = false;
	if (digitalRead(encoderButtonPin) == false) {
		buttonState = true;											// read button. If currently pressed, return true
		delay(10);													// debounce time
	}
	return buttonState;
}
boolean dialHasTurned() { // has dial turned at all?
	boolean dialHasTurned = false;
	if (abs(lastEncoderValue - encoderValue) > encoderGap) dialHasTurned = true;
	return dialHasTurned;
}
void checkDial() {	// Has the dial been turned on the rotary encoder? set flag if dial was turned or switch set to on
	if (dialHasTurned()) {														// menu selection has changed
		if (encoderValue > lastEncoderValue) {									// figure out what the new menu selection is
			activeMenuSelection++;												// add 1 to menu
			if (activeMenuSelection > maxMenuItems) activeMenuSelection = 0;
		}
		else {
			activeMenuSelection--;												// subtract 1 from menu
			if (activeMenuSelection > 200) activeMenuSelection = maxMenuItems;
		}
		printLCDActiveMenu(activeMenuSelection);
		lastEncoderValue = encoderValue;
	}
}
void printLCDActiveMenu(byte menu) {	// display the currently-selected menu item
	clearLine(1);
	getMessage(menu);
	lcd.setCursor(0, 1);
	lcd.print("> ");
	lcd.print(buffer);
}
void getMessage(byte index) { //retrieve eeprom message and put it into buffer
	strcpy_P(buffer, (char*)pgm_read_word(&menuTable[index])); // this compiles despite failure in Visual Studio editor
}
void clearLine(byte i) {	//clear a specific line
	for (byte j = 0; j <20; j++) {
		lcd.setCursor(j, i);
		lcd.print(" ");
	}
}
void updateEncoder() {	// interrupt routine called whenever rotary dial is moved
	int MSB = digitalRead(encoderPin1); //MSB = most significant bit
	int LSB = digitalRead(encoderPin2); //LSB = least significant bit
 
	int encoded = (MSB << 1) | LSB; //converting the 2 pin value to single number
	int sum = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
 
	if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue++;
	if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue--;
 
	lastEncoded = encoded; //store this value for next time
 
	if (encoderValue < 1) encoderValue = 99; //prevent negative numbers on menu
	if (encoderValue > 99) encoderValue = 1; //prevent negative numbers on menu
}
void setStatusLight(byte lightuint32_t color) {
	strip.setPixelColor(lightcolor);
	strip.show();
}
void recalibrateChime() {
	currentStepCountForChime = 0;
	setStatusLight(chimeLight, yellow);
 
	// as long as sensor is not seeing black already, do this:
	// otherwise since 1 iteration of this loop would be executed, it inadvertently might do an entire recalibration
	if (digitalRead(chimeHallEffectSensor) == true) { // true = sensor seeing "white"
		do {
			stepChimeOnce();
			currentStepCountForChime++;
			if (motorHasExceededTurnCount(currentStepCountForChime)) {
				setStatusLight(chimeLight, red);
				break;
			}
		} while (digitalRead(chimeHallEffectSensor) == true);
 
		//Serial.print("steps pt.1 = ");
		//Serial.println(currentStepCountForChime);
	}
 
	// now keep going - slower - and get just past the sensor
	do {
		stepChimeOnce();
		delay(100);
		currentStepCountForChime++;
		if (motorHasExceededTurnCount(currentStepCountForChime)) {
			setStatusLight(chimeLight, red);
			break;
		}
	} while (digitalRead(chimeHallEffectSensor) == false);
 
	//Serial.print("steps pt.2 = ");
	//Serial.println(currentStepCountForChime);
 
	currentStepCountForChime = 0;
	currentChimeSegment = 1;	// because we've recalibrated, we must be at chime set 1 of 5
	clearRegisters();
}
void recalibrateMoonDial() {
	currentStepCountForMoonDial = 0;
	setStatusLight(moonDialLight, yellow);
 
	// as long as sensor is not seeing black already, do this:
	// otherwise since 1 iteration of this loop would be executed, it inadvertently might do an entire recalibration
 
	if (digitalRead(moonDialHallEffectSensor) == true) { // true = sensor seeing "white"
		do {
			stepMoonDialOnce();
			currentStepCountForMoonDial++;
			if (motorHasExceededTurnCount(currentStepCountForMoonDial)) {
				setStatusLight(moonDialLight, red);
				break;
			}
		} while (digitalRead(moonDialHallEffectSensor) == true);
 
		//Serial.print("steps pt.1 = ");
		//Serial.println(currentStepCountForMoonDial);
	}
 
	// now keep going - slower - and get just past the sensor
	do {
		stepMoonDialOnce();
		delay(100);
		currentStepCountForMoonDial++;
		if (motorHasExceededTurnCount(currentStepCountForMoonDial)) {
			setStatusLight(moonDialLight, red);
			break;
		}
	} while (digitalRead(moonDialHallEffectSensor) == false);
 
	//Serial.print("steps pt.2 = ");
	//Serial.println(currentStepCountForMoonDial);
 
	currentStepCountForMoonDial = 0;
	clearRegisters();
}
void recalibrateCalendarHand() {
	currentStepCountForCalendarHand = 0;
	setStatusLight(calendarHandLight, yellow);
 
	// as long as sensor is not seeing black already, do this:
	// otherwise since 1 iteration of this loop would be executed, it inadvertently might do an entire recalibration
 
	if (digitalRead(calendarHallEffectSensor) == true) { // true = sensor seeing "white"
		do { 
			for(byte i = 0; i <10; i++){ // do 10 steps before checking HE sensor
				stepCalendarOnce();
				currentStepCountForCalendarHand++;
			}
			if (motorHasExceededTurnCount(currentStepCountForCalendarHand)) {
				setStatusLight(calendarHandLight, red);
				break;
			
			}
		} while (digitalRead(calendarHallEffectSensor) == true);
 
		//Serial.print("steps pt.1 = ");
		//Serial.println(currentStepCountForCalendarHand);
	}
 
	// now keep going - slower - and get just past the sensor
	do {
		stepCalendarOnce();
		delay(100);
		currentStepCountForCalendarHand++;
		if (motorHasExceededTurnCount(currentStepCountForCalendarHand)) {
			setStatusLight(calendarHandLight, red);
			break;
		}
	} while (digitalRead(calendarHallEffectSensor) == false);
 
	//Serial.print("steps pt.2 = ");
	//Serial.println(currentStepCountForCalendarHand);
 
	currentStepCountForCalendarHand = 0;
	clearRegisters();
}
void recalibrateHoursHand() {
	currentStepCountForHourHand = 0;
	setStatusLight(hourHandLight, yellow);
 
	// as long as sensor is not seeing black already, do this:
	// otherwise since 1 iteration of this loop would be executed, it inadvertently might do an entire recalibration
 
	if (digitalRead(hoursHallEffectSensor) == true) { // true = sensor seeing "white"
		do {
			stepHoursOnce();
			currentStepCountForHourHand++;
			if (motorHasExceededTurnCount(currentStepCountForHourHand)) {
				setStatusLight(hourHandLight, red);
				break;
			}
		} while (digitalRead(hoursHallEffectSensor) == true);
 
		//Serial.print("steps pt.1 = ");
		//Serial.println(currentStepCountForHourHand);
	}
 
// now keep going - slower - and get just past the sensor
	do {
		stepHoursOnce();
		delay(100);
		currentStepCountForHourHand++;
		if (motorHasExceededTurnCount(currentStepCountForHourHand)) {
			setStatusLight(hourHandLight, red);
			break;
		}
	} while (digitalRead(hoursHallEffectSensor) == false);
 
	//Serial.print("steps pt.1 = ");
	//Serial.println(currentStepCountForHourHand);
 
	currentStepCountForHourHand = 0;
	clearRegisters();
	writeRegisters();
}
void recalibrateMinutesHand() {
	currentStepCountForMinuteHand = 0;
	setStatusLight(minuteHandLight, yellow);
	// as long as sensor is not seeing black already, do this:
	// otherwise since 1 iteration of this loop would be executed, it inadvertently might do an entire recalibration
 
	if (digitalRead(minutesHallEffectSensor) == true) { // true = sensor seeing "white"
 
		do {
			stepMinutesOnce();
			currentStepCountForMinuteHand++;
			if (motorHasExceededTurnCount(currentStepCountForMinuteHand)) {
				setStatusLight(minuteHandLight, red);
				break;
			}
		} while (digitalRead(minutesHallEffectSensor) == true);		// true = sensor seeing "white"
 
		//Serial.print("steps pt.1 = ");
		//Serial.println(currentStepCountForMinuteHand);
	}
 
	// now keep going - slower - and get just past the sensor
	do {
		stepMinutesOnce();
		delay(100);
		currentStepCountForMinuteHand++;
		if (motorHasExceededTurnCount(currentStepCountForMinuteHand)) {
			setStatusLight(minuteHandLight, red);
			break;
		}
	} while (digitalRead(minutesHallEffectSensor) == false);	// false = sensor seeing "black"
 
	//Serial.print("steps pt.2 = ");
	//Serial.println(currentStepCountForMinuteHand);
 
	currentStepCountForMinuteHand = 0;
	clearRegisters();
}
void recalibrateSecondsHand() {
	currentStepCountForSecondHand = 0;
	setStatusLight(secondHandLight, yellow);
	// as long as sensor is not seeing black already, do this:
	// otherwise since 1 iteration of this loop would be executed, it inadvertently might do an entire recalibration
 
	if (digitalRead(secondsHallEffectSensor) == true) { // true = sensor seeing "white"
 
		do {
			stepSecondsOnce();
			currentStepCountForSecondHand++;
			if (motorHasExceededTurnCount(currentStepCountForSecondHand)) {
				setStatusLight(secondHandLight, red);
				break;
			}
		} while (digitalRead(secondsHallEffectSensor) == true);		// true = sensor seeing "white"
 
		//Serial.print("steps pt.1 = ");
		//Serial.println(currentStepCountForSecondHand);
	}
 
	// now keep going - slower - and get just past the sensor
	do {
		stepSecondsOnce();
		delay(5);//**constant
		currentStepCountForSecondHand++;
		if (motorHasExceededTurnCount(currentStepCountForSecondHand)) { //** need to check if lights stay red
			setStatusLight(secondHandLight, red);
			break;
		}
	} while (digitalRead(secondsHallEffectSensor) == false);	// false = sensor seeing "black"
 
	//Serial.print("steps pt.2 = ");
	//Serial.println(currentStepCountForSecondHand);
 
	currentStepCountForSecondHand = 0;
	clearRegisters();
}
boolean motorHasExceededTurnCount(int count) {
	boolean error = false;
	if (count > maximumMotorSteps) {
		error = true;
		errorStatus = true;
		errorCode = errorCodeMaximumMotorSteps;
	}
	return error;
}
void stepChimeOnce() {
	for (int i = 0; i < 8; i++)							// do 8 stepper motor steps
	{
		stepOnce(stepperChimeStepPin, i);
		delay(4);
	}
}
void stepMoonDialOnce() {
	for (int i = 7; i >= 0; i--)							// do 8 stepper motor steps
	{
		stepOnce(stepperMoonDialStepPin, i);
		delay(15);
	}
}
void stepSecondsOnce() {
	for (int i = 0; i < 8; i++)							// do 8 stepper motor steps
	{
		stepOnce(stepperSecondsStepPin, i);
		delay(1);
	}
}
void stepCalendarOnce() {
	for (int i = 0; i < 8; i++)							// do 8 stepper motor steps
	{
		stepOnce(stepperCalendarStepPin, i);
		delay(5); 
	}
}
void stepHoursOnce() {
	for (int i = 0; i < 8; i++)								// do 8 stepper motor steps
	{
		stepOnce(stepperHoursStepPin, i);	
		delay(5); 
	}
}
void stepMinutesOnce() { 
	for 	(int i = 7; i >= 0; i--)						// do 8 stepper motor steps
	{
		stepOnce(stepperMinutesStepPin, i);
		delay(5); 
	}
}
 
void clearRegisters() {  //set all register pins to LOW
	for (int i = numOfRegisterPins - 1; i >= 0; i--) {
		registers[i] = LOW;
	}
	writeRegisters();
}
void writeRegisters() {  // Set and display registers - 595's
	digitalWrite(RCLK_Pin, LOW);
	for (int i = numOfRegisterPins - 1; i >= 0; i--) {
		digitalWrite(SRCLK_Pin, LOW);
		int val = registers[i];
		digitalWrite(SER_Pin, val);
		digitalWrite(SRCLK_Pin, HIGH);
	}
	digitalWrite(RCLK_Pin, HIGH);
}
void setRegisterPin(int indexint value) {  //set an individual pin HIGH or LOW
	registers[index] = value;
}
void RTCCheck() {
	if (RTC.read(tm)) {
		setStatusLight(RTCLight, green);
		//Serial.println("RTC OK");
		/*Serial.print("Time = ");
		print2digits(tm.Hour);
		Serial.write(':');
		print2digits(tm.Minute);
		Serial.write(':');
		print2digits(tm.Second);
		Serial.print(", Date (D/M/Y) = ");
		Serial.print(tm.Day);
		Serial.write('/');
		Serial.print(tm.Month);
		Serial.write('/');
		Serial.print(tmYearToCalendar(tm.Year));
		Serial.println();*/
	}
	else {
		setStatusLight(RTCLight, red);
		errorStatus = true;
		if (RTC.chipPresent()) {
			//Serial.println("RTC stopped");
			errorCode = errorCodeRTCStopped;
		}
		else {
			//Serial.println("RTC error");
			errorCode = errorCodeRTCCircuitError;
		}
	}
}
boolean timeHasChanged(byte secondsbyte previousSeconds) {
	boolean timeDidChange = false;
	if (seconds != previousSeconds) timeDidChange = true;
	return timeDidChange;
}
boolean dayHasChanged(byte daybyte previousDay) {
	boolean dayDidChange = false;
	if (day != previousDay) dayDidChange = true;
	return dayDidChange;
}
void updateClockFaceHands() {
	if (secondsSleep == false && silentMode == false) updateSecondsHand(tm.Second);
	//updateSecondsHand(tm.Second);	//** debug code when testing seconds hand (remove)
	updateHoursHand(tm.Hour, tm.Minute, tm.Second);
	updateMinutesHand(tm.Minute, tm.Second);
 
}
void updateSecondsHand(byte seconds) {
 
	if(atSecondHandRecalibrationPoint == true) {   // sometime very recently we were at recalibration point
		//Serial.print("Secs recal @ ");
		//Serial.print(tm.Second);
		//Serial.print("s : ");
 
		// if seconds = 0, all is good. No need to do anything because we re in sync
		// if seconds between 1 and 29: hand is moving too slow. Speed it up for the next minute.
		if (tm.Second >= 1 && tm.Second <= 29) {
			maximumStepsThisMinute++; //??
			//Serial.print("speed up");
		}
 
		// if seconds between 30 and 59: hand is moving too fast. Slow it down for the next minute.
		if (tm.Second >= 30 && tm.Second <= 59) {
			maximumStepsThisMinute--; //??
			//Serial.print("slow down");
		}
		//Serial.print(" to ");
		//Serial.println(maximumStepsThisMinute);
 
		atSecondHandRecalibrationPoint = false;              // now reset this flag (ensure it doesn t get triggered multiple times in one second?)
	}
	
	int target = targetStepCountForSecondHand(seconds, maximumStepsThisMinute); // what step count should we be at in this minute?
	if (seconds == 0) currentStepCountForSecondHand = -6 ;
	targetSecondPartStepCountForSecondHand = target;
	int targetFirstPartStepCountForSecondHand = target;
	targetFirstPartStepCountForSecondHand = targetFirstPartStepCountForSecondHand - 2;
 
	byte secondsDelayGap = 38;
 
	if ((target > currentStepCountForSecondHand) && (pauseSecondHand == false)) {	// if we need to move the hand...
		setStatusLight(secondHandLight, yellow);
		while (targetFirstPartStepCountForSecondHand > currentStepCountForSecondHand) {	// go slightly past the target**
			stepSecondsOnce();
			currentStepCountForSecondHand++;
			delay(secondsDelayGap);
			if (secondsDelayGap > 10) secondsDelayGap = secondsDelayGap - 3;	// speed up the movement (makes it look more like a mechanical clock)
			
			// Set a flag when hand is at recalibration point
			if(atSecondCalibrationPoint() == true) atSecondHandRecalibrationPoint = true;
			}
		}
	}
void secondPartOfSecondsHandMove(byte second) {
	if (targetSecondPartStepCountForSecondHand > currentStepCountForSecondHand) delay(150);
	if ((errorStatus == false)) {
		while (targetSecondPartStepCountForSecondHand > currentStepCountForSecondHand) {	// now fall forward to where we should be
			stepSecondsOnce();
			currentStepCountForSecondHand++;
			delay(30);
 
			// Set a flag when hand is at recalibration point
			if (atSecondCalibrationPoint() == true) atSecondHandRecalibrationPoint = true;
 
		}
		setStatusLight(secondHandLight, black);
		// clear out registers so stepper motors don't receive electricity when idle
		clearRegisters();
		writeRegisters();
	}
}
 
void updateMinutesHand(byte minutesbyte seconds) {
	float secondsElapsed = calculateSecondsOfThisHour(minutesseconds); // how many seconds into this hour are we?
	int target = targetStepCountForMinuteHand(secondsElapsed, maximumStepsThisHour); // what step count should we be at in this hour?
	if (target < currentStepCountForMinuteHand) recalibrateMinutesHand();	// in case where time has been put back
	
	if (target > currentStepCountForMinuteHand) {	// if we need to move the hand...
		setStatusLight(minuteHandLight, yellow);
		while (target > currentStepCountForMinuteHand) {
			stepMinutesOnce();
			currentStepCountForMinuteHand++;
		}
	}
	// clear out registers so stepper motors don't receive electricity when idle
	clearRegisters();
	if (minutes == 0 && seconds == 0) currentStepCountForMinuteHand = 0;	// presume that if target is 0 then hand is already at the top positionvoid updateHoursHand(byte hoursbyte minutesbyte seconds) {
 
	float secondsElapsed = calculateSecondsOfThisDay(hoursminutesseconds); // how many seconds into this 12 hour period are we?
	int target = targetStepCountForHourHand(secondsElapsed, maximumStepsThisHalfDay); // what step count should we be at in this hour?
	if (target < currentStepCountForHourHand) recalibrateHoursHand();	// in case where time has been put back
 
	if ((target > currentStepCountForHourHand) && (pauseHourHand == false)) {
		setStatusLight(hourHandLight, yellow);
		while (target > currentStepCountForHourHand) {
			stepHoursOnce();
			currentStepCountForHourHand++;
		}
	}
	// clear out registers so stepper motors don't receive electricity when idle
	clearRegisters();
	if ((hours == 12 || hours == 0) && minutes == 0 && seconds == 0) currentStepCountForHourHand = 0;	// presume that if target is 0 then hand is already at the top position
}
void updateCalendarHand(byte dayOfMonth) {
	float daysElapsed = dayOfMonth// what day is it? 
	int target = targetStepCountForCalendarHand(daysElapsed, maximumStepsThisMonth); // what step count should we be at in this day?
	if (target == 0) currentStepCountForCalendarHand = 0;
 
	if ((target > currentStepCountForCalendarHand) && (pauseCalendarHand == false)) {
		setStatusLight(calendarHandLight, yellow);
		while (target > currentStepCountForCalendarHand) {
			stepCalendarOnce();
			currentStepCountForCalendarHand++;
		}
	}
	// clear out registers so stepper motors don't receive electricity when idle
	clearRegisters();
}
void updateMoonDial(int yearint monthint day) {
	float lunarAge = calculateMoonPhase(yearmonthday);	// determine Lunar day (0-29.5)
	float moonDialDays = 0;
 
	// if start of lunar month, then flip from moon dial segment 1 > 2 > 1 etc
	if (RTCAtBeginningOfLunarMonth(lunarAge)) {
		if (moonDialSegment == 1) {
			moonDialSegment++; // 1 > 2
		}
		else {
			moonDialSegment--; // 2 > 1
		}
	}
 
	if (moonDialSegment == 1) {
		// we are in the first half of the moon dial being displayed
		moonDialDays = lunarAge;
	} else {
		// we are in the second half of the moon dial being displayed
		moonDialDays = lunarAge + 29.5;
	}
 
	int target = targetStepCountForMoonDial(moonDialDays, maximumStepsThisLunarMonth); // what step count should we be at in this lunar day?
	if (target == 0) currentStepCountForMoonDial = 0;
	//if (target < currentStepCountForMoonDial) recalibrateMoonDial();**? needed
 
/*
	Serial.print("Day ");
	Serial.print(day);
	Serial.print(" - Lunar:");
	Serial.print(lunarAge);
	Serial.print(" - moonDialDays:");
	Serial.print(moonDialDays);
 
	Serial.print(" - step:");
	Serial.println(target);
	*/
 
	if ((target > currentStepCountForMoonDial) && (pauseMoonDial == false)) {
		setStatusLight(moonDialLight, yellow);
		while (target > currentStepCountForMoonDial) {
			stepMoonDialOnce();
			currentStepCountForMoonDial++;
		}
	}
	// clear out registers so stepper motors don't receive electricity when idle
	clearRegisters();
}
 
int targetStepCountForSecondHand(int currentSecondsint maximumStepsThisMinute) {
 
	float proportionOfSecondsThisMinute = (float)currentSeconds / (float)60.0; // was 60
	float targetStepCount = proportionOfSecondsThisMinute * (float)maximumStepsThisMinute;
	return targetStepCount;
}
float targetStepCountForMinuteHand(float currentSecondsfloat maximumStepsThisHour) {
 
	float proportionOfSecondsThisHour = currentSeconds / maximumSecondsThisHour;
	float targetStepCount = proportionOfSecondsThisHour * maximumStepsThisHour;
	if (currentSeconds == 0) targetStepCount = maximumStepsThisHour;
	return targetStepCount;
} 
float targetStepCountForHourHand(float currentSecondsfloat maximumStepsThisHalfDay) {
 
	float proportionOfSecondsThisHalfDay = currentSeconds / maximumSecondsThisHalfDay;
	float targetStepCount = proportionOfSecondsThisHalfDay * maximumStepsThisHalfDay;
	if (currentSeconds == 0 || currentSeconds == maximumSecondsThisHalfDay) targetStepCount = maximumStepsThisHalfDay;
	return targetStepCount;
}
float targetStepCountForCalendarHand(float currentDayfloat maximumStepsThisMonth) {
 
	float proportionOfDaysThisMonth = (currentDay - 1) / maximumDaysThisMonth;	// -1 because first day of month is at step position 0 (not 16)
	float targetStepCount = proportionOfDaysThisMonth * maximumStepsThisMonth;
	return targetStepCount;
}
float targetStepCountForMoonDial(float currentLunarDayfloat maximumStepsThisLunarMonth) {
 
	float proportionOfDaysThisLunarMonth = currentLunarDay / maximumDaysThisLunarMonth;
	float targetStepCount = proportionOfDaysThisLunarMonth * maximumStepsThisLunarMonth;
	return targetStepCount;
}
int calculateSecondsOfThisHour(byte minutesbyte seconds) {
 
	int secondsElapsed = (minutes * 60) + seconds;
	return secondsElapsed;
} 
word calculateSecondsOfThisDay(byte hoursbyte minutesbyte seconds) {
	// hours must be <= 12 
	if (hours > 11) hours = hours - 12; // if 12 o'clock, don't count seconds for the hour
	word secondsElapsed = (hours * 60 * 60) + calculateSecondsOfThisHour(minutesseconds);
	return secondsElapsed;
} 
float calculateMoonPhase(int nYearint nMonthint nDay) { // calculate the current phase of the moon
	// Moon phase, takes three parameters: the year (4 digits), the month and the day.
	// The function returns fraction full, float 0-1 (e.g. 0 for new, .25 for crescent, .5 for quarter, .75 for gibbous and 1 for full). [since removed]
	// Also calculates phase and age in days. THIS VERSION uses a cosine function to model the illumination fraction.
	// Calculated at noon, based on new moon Jan 6, 2000 @18:00, illumination accurate to about 5%, at least for years 1900-2100.
	// Modified from post at http://www.nano-reef.com/topic/217305-a-lunar-phase-function-for-the-arduino/
	// S. J. Remington 11/2016
	// Copied from https://forum.arduino.cc/index.php?topic=438179.15
	// Adjustments made to remove things not required (e.g. fraction full)
	// You can also check the lunar day at http://www.moongiant.com/phase/today/
	float age, phase, days_since;
	long YY, MM, K1, K2, K3, JD;
	YY = nYear - floor((12 - nMonth) / 10);
	MM = nMonth + 9;
	if (MM >= 12)
	{
		MM = MM - 12;
	}
	K1 = floor(365.25 * (YY + 4712));
	K2 = floor(30.6 * MM + 0.5);
	K3 = floor(floor((YY / 100) + 49) * 0.75) - 38;
	JD = K1 + K2 + nDay + 59;  //Julian day
	if (JD > 2299160) //1582, Gregorian calendar
	{
		JD = JD - K3;
	}
 
	// Serial.print(" JD ");  //Julian Day, checked OK
	// Serial.print(JD);
	// Serial.print(" ");
 
	days_since = JD - 2451550L; //since noon on Jan. 6, 2000 (new moon @18:00)
	phase = (days_since - 0.25) / 29.53059; //0.25 = correct for 6 pm that day
	phase -= floor(phase);  //phase in cycle
	age = phase * 29.53059;
 
	// calculate fraction full
	//frac = (1.0 - cos(phase * 2 * PI)) * 0.5;
 
	return age;
}
void determineChime(byte hoursbyte minutes) {
	switch (minutes){
	case onTheHour:
		playWestminsterChime(hours, onTheHour);
		break;
	case quarterPast :
		playWestminsterChime(hours, quarterPast);
		break;
	case halfPast :
		playWestminsterChime(hours, halfPast);
		break;
	case quarterTo :
		playWestminsterChime(hours, quarterTo);
		break;
	} 
} 
void playWestminsterChime(byte hoursbyte minutes) {
	// https://en.wikipedia.org/wiki/Westminster_Quarters
	// chime and gone timing synchronized with Big Ben at https://www.youtube.com/watch?v=E9wWBjnaEck 
	switch (minutes) {
		case 0: // top of hour.
		playChimeSet(4);
		playChimeSet(5);
		recalibrateChime(); // ensure we are at calibration point (beginning of chime set 1) before starting again
		playChimeSet(1);
		playChimeSet(2);
 
		chimeDelay(5500);	// validated from Big Ben timings https://www.youtube.com/watch?v=E9wWBjnaEck 
		hitGong(hours);
		break;
 
		case 15:
		playChimeSet(3);
		break;
 
		case 30:
		playChimeSet(4);
		playChimeSet(5);
		recalibrateChime(); // ensure we are at calibration point (beginning of chime set 1) before starting again
		break;
 
		case 45:
		playChimeSet(1);
		playChimeSet(2);
 
		playChimeSet(3);
		break;
	}
} 
void playChimeSet(byte segment) {
	switch (segment){
	case 1:
		rotateChimeCams(westminsterChimesSetOne);
		break;
	case 2:
		rotateChimeCams(westminsterChimesSetTwo);
		break;
	case 3:
		rotateChimeCams(westminsterChimesSetThree);
		break;
	case 4:
		rotateChimeCams(westminsterChimesSetFour);
		break;
	case 5:
		rotateChimeCams(westminsterChimesSetFive);
		break;
	} 
	currentChimeSegment++;
	if(currentChimeSegment > 4) currentChimeSegment = 1;
} 
void rotateChimeCams(int steps) {	// rotate motor by # of steps to play the appropriate number of notes
	setStatusLight(chimeLight, white);
	for(int i = 0; i <= steps; i++) {
		stepChimeOnce();
	}
	clearRegisters();
	writeRegisters();
	setStatusLight(chimeLight, yellow);
}
void chimeDelay(int milliSeconds) {
	// wait for some milliSeconds, and while waiting update clock face
	do {
		milliSeconds = milliSeconds - 1200; // 1200 because that's how much time delay(500) + seconds hand update takes
		delay(500);
		// now read RTC and update hour/minute/second hands
		RTC.read(tm);
		updateSecondsHand(tm.Second);
		secondPartOfSecondsHandMove(tm.Second);
	} while (milliSeconds > 0);
}
void hitGong(byte hours) {
	if (hours > 12) hours = hours - 12; // convert to 12 hour clock if required
	for(byte i = 0; i < hours; i++) {
		setStatusLight(gongLight, yellow);
		setRegisterPin(chimeGong, 1); // pull back solenoid
		writeRegisters();
		delay(500); // wait for solenoid to engage - don't use chimeDelay because that will also release solenoid
		setRegisterPin(chimeGong, 0); // release solenoid
		writeRegisters(); // "gong "
		setStatusLight(gongLight, black);
		chimeDelay(3900);  // 4.4 seconds between gongs (- 500ms for the delay above)
	}
}
void tickTock() {
 
	tickSound = !tickSound;	// tick or tock?
 
	if (tickSound == true) {
 
		setStatusLight(tickTockLight, black);
		setRegisterPin(tickSolenoid, 1); // pull back solenoid
		writeRegisters();	// "tick "
		delay(15);
		setRegisterPin(tickSolenoid, 0); // release solenoid
		writeRegisters();
 
	}
	else {
 
		setStatusLight(tickTockLight, blue);
		setRegisterPin(tockSolenoid, 1); // pull back solenoid
		writeRegisters();	// "tock "
		delay(20);
		setRegisterPin(tockSolenoid, 0); // release solenoid
		writeRegisters();
	}
	
}
 
boolean atMoonDialCalibrationPoint() { // are we at Moon Dial Calibration Point?
 
	boolean moonDialCalibrationPoint;
	boolean readMoonDialHallEffectSensor = digitalRead(moonDialHallEffectSensor);
 
	if (readMoonDialHallEffectSensor == true && previousMoonDialHallEffectSensor == false) { //  sensor is seeing white (true) but was black (false) last time 
		moonDialCalibrationPoint = true// we have reached calibration point
	}
	else {
		moonDialCalibrationPoint = false;
	}
 
	previousMoonDialHallEffectSensor = readMoonDialHallEffectSensor; // store for next time round
 
	return moonDialCalibrationPoint;
}
boolean atCalendarCalibrationPoint() { // are we at Calendar Calibration Point?
 
	boolean calendarCalibrationPoint;
	boolean readCalendarHallEffectSensor = digitalRead(calendarHallEffectSensor);
 
	if (readCalendarHallEffectSensor == true && previousCalendarHallEffectSensor == false) { //  sensor is seeing white (true) but was black (false) last time 
		calendarCalibrationPoint = true// we have reached calibration point
	}
	else {
		calendarCalibrationPoint = false;
	}
	
	previousCalendarHallEffectSensor = readCalendarHallEffectSensor; // store for next time round
 
	return calendarCalibrationPoint;
}
boolean atHourCalibrationPoint() { // are we at Hour Calibration Point?
 
	boolean hourCalibrationPoint;
	boolean readHoursHallEffectSensor = digitalRead(hoursHallEffectSensor);
 
	if (readHoursHallEffectSensor == true && previousHoursHallEffectSensor == false) { //  sensor is seeing white (true) but was black (false) last time 
		hourCalibrationPoint = true// we have reached calibration point
	}
	else {
		hourCalibrationPoint = false;
	}
 
	previousHoursHallEffectSensor = readHoursHallEffectSensor; // store for next time round
 
	return hourCalibrationPoint;
}
boolean atMinuteCalibrationPoint() { // are we at Minute Calibration Point?
 
	boolean minuteCalibrationPoint;
	boolean readMinutesHallEffectSensor = digitalRead(minutesHallEffectSensor); //** add this to seconds calib point functions
 
	if (readMinutesHallEffectSensor == true && previousMinutesHallEffectSensor == false) { //  sensor is seeing white (true) but was black (false) last time 
		minuteCalibrationPoint = true// we have reached calibration point
	}
	else {
		minuteCalibrationPoint = false;
	}
	previousMinutesHallEffectSensor = readMinutesHallEffectSensor; // store for next time round
 
	return minuteCalibrationPoint;
}
boolean atSecondCalibrationPoint() { // are we at Second Calibration Point?
 
	boolean secondCalibrationPoint;
	boolean readSecondsHallEffectSensor = digitalRead(secondsHallEffectSensor);
 
	if (readSecondsHallEffectSensor == true && previousSecondsHallEffectSensor == false) { //  false=calibration point.  black (false) now but was white (true) last time round
		secondCalibrationPoint = true// we have reached calibration point
	}
	else {
		secondCalibrationPoint = false;
	}
 
	previousSecondsHallEffectSensor = readSecondsHallEffectSensor; // store for next time round
 
	return secondCalibrationPoint;
}
boolean RTCAtTopOfHour() { // is it x o'clock on the dot? this only happens for one second every hour
	boolean topOfHour = false;
	if (tm.Minute == 0 && tm.Second == 0) topOfHour = true;
	return topOfHour;
}
boolean RTCPriorToTopOfHour() { // are we close to top of hour but not quite there yet?
	boolean priorToTopOfHour = false;
	if (tm.Minute <= 59) priorToTopOfHour = true;
	return priorToTopOfHour;
}
boolean RTCAtTopOfHalfDay() { // is it 12 o'clock on the dot? this only happens for one second every 12 hours
	boolean topOfHalfDay = false;
	if ((tm.Hour == 0 || tm.Hour == 12) && tm.Minute == 0 && tm.Second == 0) topOfHalfDay = true;
	return topOfHalfDay;
}
boolean RTCPriorToTopOfHalfDay() { // are we close to top of half day but not quite there yet?
	boolean priorToTopOfHalfDay = false;
	if (tm.Minute <= 59) priorToTopOfHalfDay = true//** check if need to examine hours too
	return priorToTopOfHalfDay;
}
boolean RTCPriorToEndOfMonth() { // are we close to top of month but not quite there yet?
	boolean priorToEndOfMonth = false;
	if (tm.Day <= 31) priorToEndOfMonth = truereturn priorToEndOfMonth;
}
boolean RTCPriorToEndOfLunarMonth(byte day) { // are we close to top of month but not quite there yet?
	boolean priorToEndOfLunarMonth = false;
	if (day <= maximumDaysThisLunarMonth) priorToEndOfLunarMonth = true;
	return priorToEndOfLunarMonth;
}
boolean RTCAtBeginningOfMonth() { // is it 1st of the month? this only happens for one day every month
	boolean beginningOfMonth = false;
	if (tm.Day == 0) beginningOfMonth = true;
	return beginningOfMonth;
}
boolean RTCAtBeginningOfLunarMonth(float day) { // is it 1st day of the lunar month? this only happens for one day every month
	boolean beginningOfLunarMonth = false;
	if (day < 1) beginningOfLunarMonth = true;
	return beginningOfLunarMonth;
}
void displayTime() {
	lcd.setCursor(0, 0);
	printLCDDigits(tm.Month, "");
	printLCDDigits(tm.Day, "/");
	printLCDDigits(tmYearToCalendar(tm.Year)-2000, "/");	// tm.Year is from 1970, so this gives us years since 2000 (2 digit)
	lcd.setCursor(12, 0);
	printLCDDigits(tm.Hour, "");
	printLCDDigits(tm.Minute, ":");
	printLCDDigits(tm.Second, ":");
	//lcd.print(" ");  // this solves a problem where a digit sometimes gets printed to the right of the time - clear it out instead
}
void printPositionDigits(int digits) {   // utility function for lcd display: prints leading 0's
	if (digits < 100) lcd.print('0');
	if (digits < 10) lcd.print('0');
	lcd.print(digits);
}
void printLCDDigits(int digitscharprecursor) {   // utility function for lcd display: prints preceding character and leading 0
	lcd.print(precursor);
	if (digits < 10) lcd.print('0');
	lcd.print(digits);
}
void printStatusMessage(byte i) {        //print status message #i on lcd
	getMessage(i);
	clearLine(1);
	lcd.setCursor(0, 1);
	lcd.print(buffer);
}
byte addHour(byte hour) {
	if (hour >= 23) {
		hour = 0;
	}
	else {
		hour++;
	}
	return hour;
}
byte subtractHour(byte hour) {
	if (hour == 0) {
		hour = 23;
	}
	else {
		hour--;
	}
	return hour;
}
void addRTCHour() {
	if (tm.Hour >= 23) {
		tm.Hour = 0;
	}
	else {
		tm.Hour++;
	}
	RTC.write(tm);
	displayTime();
}
void subtractRTCHour() {
	if (tm.Hour == 0) {
		tm.Hour = 23;
	}
	else {
		tm.Hour--;
	}
	RTC.write(tm);
	displayTime();
}
void addMinute() {
	if (tm.Minute == 59) {
		tm.Minute = 0;
		if (tm.Hour == 23) {
			tm.Hour = 0;
		}
		else {
			tm.Hour++;;
		}
	}
	else {
		tm.Minute++;
	}
	RTC.write(tm);
	displayTime();
}
void subtractMinute() {
	if (tm.Minute == 0) {
		tm.Minute = 59;
		if (tm.Hour == 0) {
			tm.Hour = 23;
		}
		else {
			tm.Hour--;
		}
	}
	else {
		tm.Minute--;
	}
	RTC.write(tm);
	displayTime();
}
void addYear() {
	tm.Year++;
	RTC.write(tm);
	displayTime();
}
void subtractYear() {
	tm.Year--;
	RTC.write(tm);
	displayTime();
}
void addMonth() {
	if (tm.Month == 12) {
		tm.Month = 1;
	}
	else {
		tm.Month++;
	}
	RTC.write(tm);
	displayTime();
}
void subtractMonth() {
	if (tm.Month == 1) {
		tm.Month = 12;
	}
	else {
		tm.Month--;
	}
	RTC.write(tm);
	displayTime();
}
void addDay() {
	if (tm.Day == 31) {
		tm.Day = 1;
	}
	else {
		tm.Day++;
	}
	RTC.write(tm);
	displayTime();
}
void subtractDay() {
	if (tm.Day == 1) {
		tm.Day = 31;
	}
	else {
		tm.Day--;
	}
	RTC.write(tm);
	displayTime();
}
void menuSystem(byte selectedMenuItem) {
	printStatusMessage(msgUseDial); // "turn and push to set" message
	clearLine(2);
	clearLine(3);
	errorStatus = false;	// reset error status
 
	switch (selectedMenuItem) {   
 
	case 0:	// chime on & off
		printLCDOptionStatus(chimeSwitch);							// true or false equates to On and Off
		do {
			if (dialHasTurned()) {
				if (encoderValue != lastEncoderValue) {				// figure out what the new menu selection is
					chimeSwitch = !chimeSwitch;
					printLCDOptionStatus(chimeSwitch);
				}
			}
			lastEncoderValue = encoderValue;
			delay(encoderDelay);
		} while (isEncoderButtonPressed() == false);				// exit when the button is pressed to set time
		EEPROM.write(chimeSwitch_EEPROM, chimeSwitch);				// update EEPROM
		delay(encoderDelay); 
		break;
 
	case 1: // set seconds sleep
		secondsSleepHour = adjustSetting(secondsSleepHour, secondsSleepHour_EEPROM);
		break;
 
	case 2: // set seconds wake
		secondsWakeHour = adjustSetting(secondsWakeHour, secondsWakeHour_EEPROM);
		break;
 
	case 3: // set ticktock sleep
		tickTockSleepHour = adjustSetting(tickTockSleepHour, tickTockSleepHour_EEPROM);
		break;
 
	case 4: // set ticktock wake
		tickTockWakeHour = adjustSetting(tickTockWakeHour, tickTockWakeHour_EEPROM);
		break;
 
	case 5: // set chime sleep time
		chimeSleepHour = adjustSetting(chimeSleepHour, chimeSleepHour_EEPROM);
		break;
 
	case 6: // set chime wake time
		chimeWakeHour = adjustSetting(chimeWakeHour, chimeWakeHour_EEPROM);
		break;
 
	case 7: // set weekend wake time
		weekendWakeHour = adjustSetting(weekendWakeHour, weekendWakeHour_EEPROM);
		break;
 
	case 8: // set weekend sleep time
		weekendSleepHour = adjustSetting(weekendSleepHour, weekendSleepHour_EEPROM);
		break;
 
	case 9: //set hour (useful for seasonal spring forward / backward)
 
		do {
			if (dialHasTurned()) {
				if (encoderValue > lastEncoderValue) {				// figure out what the new menu selection is
					addRTCHour();
				}
				else {
					subtractRTCHour();
				}
			}
			lastEncoderValue = encoderValue;
			delay(encoderDelay);
		} while (isEncoderButtonPressed() == false);					// exit when the button is pressed to set time      
		delay(encoderDelay);											// pause to allow time for button to be released
		break;
 
	case 10: //set time
 
		do {
			if (dialHasTurned()) {
				if (encoderValue > lastEncoderValue) {					// figure out what the new menu selection is
					addMinute();
				}
				else {
					subtractMinute();
				}
			}
			lastEncoderValue = encoderValue;
			delay(encoderDelay);
		} while (isEncoderButtonPressed() == false);					// exit when the button is pressed to set time      
		delay(encoderDelay);											// pause to allow time for button to be released
		break;
 
	case 11: //set year
 
		do {
			if (dialHasTurned()) {
				if (encoderValue > lastEncoderValue) {					// figure out what the new menu selection is
					addYear();
				}
				else {
					subtractYear();
				}
			}
			lastEncoderValue = encoderValue;
			delay(encoderDelay);
		} while (isEncoderButtonPressed() == false);					// exit when the button is pressed to set time       
		delay(encoderDelay);											// pause to allow time for button to be released
		break;
 
	case 12: //set month
 
		do {
			if (dialHasTurned()) {
				if (encoderValue > lastEncoderValue) {					// figure out what the new menu selection is
					addMonth();
				}
				else {
					subtractMonth();
				}
			}
			lastEncoderValue = encoderValue;
			delay(encoderDelay);
		} while (isEncoderButtonPressed() == false);					// exit when the button is pressed to set time       
		delay(encoderDelay);											// pause to allow time for button to be released
		break;
 
	case 13: //set day
 
		do {
			if (dialHasTurned()) {
				if (encoderValue > lastEncoderValue) {					// figure out what the new menu selection is
					addDay();
				}
				else {
					subtractDay();
				}
			}
			lastEncoderValue = encoderValue;
			delay(encoderDelay);
		} while (isEncoderButtonPressed() == false);					// exit when the button is pressed to set time       
		delay(encoderDelay);											// pause to allow time for button to be released
		break;
 
	case 14: //recalibrate
		recalibrateAllHands();
		previousSeconds = 99;	// force update to movement
		previousDay = 99;		// force update to movement
		waitForUserAction();
		setStatusLight(powerLight, dgreen);	// reset any error light
		break;
	}
 
	clearLine(3);
	clearLine(2);
	clearLine(1);
}
void recalibrateAllHands() {
	recalibrateCalendarHand();
	recalibrateMoonDial();
	recalibrateHoursHand();
	recalibrateMinutesHand();
	recalibrateSecondsHand();
}
byte adjustSetting(byte settingNamebyte EEPROMsettingName) {
	printLCDOptionHour(settingName);
	do {
		if (dialHasTurned()) {
			if (encoderValue > lastEncoderValue) {				// figure out what the new menu selection is
				settingName = addHour(settingName);
			}
			else {
				settingName = subtractHour(settingName);
			}
			printLCDOptionHour(settingName);
		}
		lastEncoderValue = encoderValue;
		delay(encoderDelay);
	} while (isEncoderButtonPressed() == false);				// exit when the button is pressed to set time
	EEPROM.write(EEPROMsettingNamesettingName);	// update EEPROM	
	delay(encoderDelay);
	return settingName;
}
void printLCDOptionStatus(boolean option) {	// print user-friendly on/off message
	lcd.setCursor(8, 3);
	lcd.print("> O");
	if (option == false) {
		lcd.print("FF");
	}
	else {
		lcd.print("N ");
	}
}
void printLCDOptionHour(byte hour) {	// print user-friendly hour
	lcd.setCursor(8, 3);
	printLCDDigits(hour"");
	lcd.print(" Hrs");
}