Internet based Tide Clock

First the usual disclaimer; tide clocks are decorative items to hang on the wall of your home and admire, they are not for navigation and other such boaty stuff that requires failsafe accuracy on tide state etc.
If you're going near water consult recognised tide tables.
You have been warned.

So now lets have some fun and build something to be proud of.

First some background.
If your tides are every 6 hours and 13 minutes then the std tide clock works well. But if your tides are semi diurnal with a dwell in the flood tide then at ~5 hours out and ~7.5 hours in you need a bespoke clock.

Summary

This project uses an Adafruit Feather Huzzah, which is a board made by Adafruit to support using the ESP8266 wireless chip. The ESP8266 is a great IoT device and perfect for this project. It handles all the Wi-Fi conectivity and drives the 25 LED's through 3 shift registers.
The code flow is quite simple:

Construction

mitre saw The first thing to make is the frame. For this I had some left over 16mm thick deck timber, Utile. I machined up 4 lengths 35mm wide. Dads old mitre saw made short shrift of this and guarenteed the frame would be square. The tide profile I pulled off the internet and scaled it up so it would easily fit on a piece of A3 birch ply. The ply I chose was 3mm thick. Having transfered the grid and then the profile i drilled 5mm holes for the LED's, one for each half an hour change in the tide height.
I machined a groove for the ply 3mm back from the front face. I also rebated the rear face by 3mm for the clear perspex cover that fits on the back to close it off. then I glued it all together. Then I gave it a coat of cellulose sanding sealer followed by a coat of Tung oil to bring out the beauty of the wood.

back When the glue had set I fitted the breadboard. Although it's fixed with double sided tape I added a 6mm square strip of timber glued in place, just in case the breadboard adhesive gave way over time.
On the breadboard is the YwRobot power supply, four shift registers to drive the LED's, a level shifter for the signals from the MCU at 3.3v which require lifting to 5v for the shift registers, and finally the Feather Huzzah (ESP8266. I mount the shift registers in dil sockets so the chips can be mounted last after the wiring is complete. Being CMOS devices they can be damaged by static. The breadboard and LED's are wired up using solid core wire. The series resister for each LED is 180 ohms. The centre positive power lead came from an old charger. Always good to re-cycle.

close up of the electronics Here's a close up of the chips and the wiring.

Front Here is a view from the front. This shows the actual tide progression and the dwell or 'stand' on the flood tide. This phenomenon is known as the 'Young flood stand' .

battery cover I secured the LED's with a blob of epoxy glue as although they are a good fit in the plywood someone may just push one out. Little fingers and curiosity...
The perspex cover came from PerspexSheetUK ready cut to size. I made the battery cover a separate piece as this will have to be removed for occasional battery charging.

hanging bracket To hang it on the wall I used these cheap brass brackets. I only had one old fashioned bracket so had to settle for these rather modern looking pieces...
The screws are stainless as they always have to be when you are by the sea. Brass screws, which looks more appropriate, aren't so good when you have to keep removing and refitting.
The large hole in the perspex sheet is to access the on/off button on the YwRobot power supply.

Time to check Here I am comparing my tide clock with the WorldTides App on my watch. They should match as I'm using their API.

Parts

Here's the parts list and links to my favourite shopping sites for projects EBay and Amazon

  1. 16x45mm Utile Timber for the frame, which I made to 380x255mm measured externally
  2. 3mm thick Birch ply
  3. 26 off 5mm ultra bright red LEDs
  4. Solid core hookup wire
  5. Heat shrink tubing
  6. Breadboard
  7. YwRobot PSU
  8. 4 off 16pin dil sockets
  9. 4 off 8 bit shift registers
  10. 4 channel Level shifter
  11. Adafruit Feather Huzzzah or Node MCU ESP8266
  12. Barrel plug (Center positve) for PSU
  13. Floureon 7.2v 3500mAh NiMH battery
  14. Tamiya connector to connect the power lead from the GyRobot PSU to the battery
  15. Titebond wood glue
  16. Brass hanging brackets
  17. Perspex sheet
Other tools you might find useful

If what you need is not in my list then try searching;

Or

Software

Here's my code, which probably isnt bug free but it works for me....


/*
 * Tide Clock project
 * M Williams
 * 19/3/2018
 * v3.0 checks out the "Get Unix time" UPD request of v2.6
 * Code Plaigurised from;
 * updated for the ESP8266 12 Apr 2015
 * by Ivan Grokhotkov
 * the european NTP pool list can be found http://www.pool.ntp.org/zone/europe
 * 
 * v3.01
 * 20/3/2018
 * Polls the european pool for the nearest available server and return its IP address
 * 
 * v3.02
 * 20/3/2018
 * Change to WiFiMulti
 * this allows a choice of ssid's depending on availablity
 * 
 * v4.00
 * 22/3/2018
 * Add in the LED test sequence
 * Add call to WorldTidesAdd ParseJson at a basic level to simply identify the 'Station'
 *
 * v4.01
 * 24/3/2018
 * Expand the ParseJson to level of Ethernet shiled v2.60
 * 
 * v4.10
 * 24/3/2018
 * Add the resr of the code from v2.6 the Ethernet version
 * Program flow;
 * SETUP
 *  On power up test LEDs
 *  Start Wifi
 *  Initialise UDP
 *  Call tides API
 *  Parse the JSON of tide information into 0-6 tide periods with start time, LOw/HIGH & tide height (not currently used)
 * MAIN
 *  Get Unix time from the internet
 *  Determine which tide period the current unix time sits in the Worldtides parsed JSON. On first pass this should be tide perion (0)
 *  If curent Epoch time is in tide period 6 (the last one) then a New API call is required (every 1.5 days at 4 tides per day)
 *  If the 'New API' flag is set TRUE then a new API call is made, returning JSON is parsed, and current tide period is determined
 *  Work out which lamp to light depending on whether our tide period starts at HIGH tide (0-10) or LOW tide (10-25)
 *  Light that lamp
 *  Wait 5 mins
 * LOOP
 *  
 *  v5.00
 *  3/4/2018
 *  Using v4.10 as a base move to a UDP call every 12 hours rather than 5 mins and use millis() to run an internal clock
 *  Program flow
 *  SETUP
 *    On Power Up test LEDS
 *    Start WiFi
 *    Initialise UDP
 *    Call Tide API
 *    Parse Tides JSON
 *    Get Unix time
 *    Set internal clock of the time.h libary such that now() become the current epoch time
 *  MAIN
 *    Determine with tide period the current time sits within
 *    If curent Epoch time is in tide period 6 (the last one) then a New API call is required (every 1.5 days at 4 tides per day)
 *    If the 'New API' flag is set TRUE then a new API call is made, returning JSON is parsed, and current tide period is determined
 *    Work out which lamp to light (if the lamp fraction is > 0.5 flash the lamp otherwise light solid. lampFlashState flag required.
 *    Light the lamp depending on LampFlashState flag
 *    Check if we need to make another UDP call to sync the internal clock (3 hours since the last one)
 *    Wait 5 mins (look at ESP8266 sleep options)
 *  LOOP
 *  
 *  v5.01
 *  15/4/2018
 *    Revise look to simplify;
 *    Make API call if requireed then whatever selecttidePeriod(now())
 *  
 *  v5.10
 *  15/4/2018
 *    In previous versions 0-0.5 lights prior lamp and 0.51-1.00 lights next lamp
 *    But when a lampo is lit are we are aproaching that point of have we passed it?
 *    Better we differentiate between approaching a lamp and having passed a lamp.
 *    Flash lamp we are approaching, so interLampFraction between 0.51 and 1.00 set ledFlash to true
 *    lamp we are passed is solid, so interLampFraction between 0.0 and 0.50 set ledflash to false
 *    
 *  v5.11
 *  30/4/2018
 *    Occulting light for pre lamp and solid for post lamp
 *    Flashing lamp for error; not tide state or now() time before period[0]
 *    
 * Pins used
 * D0   Red LED (LOW = Bootloading)
 * D2   Blue LED
 * D4   SDA
 * D5   SCL
 * D12  SPI MISO - Data Pin  (LV1)  SER   chip pin14 on 74HC595 (HV1)
 * D13  SPI MOSI - Latch Pin (LV2)  RCLK  chip pin12 on 74HC595 (HV2)
 * D14  SPI SCK  - Clock pin (LV3)  SRCLK chip pin11 on 74HC595 (HV3)
 * D15  Pull Down (Boot Mode Detect)
 * D16  Can be used to wake from Deep Sleep
 * A0 Battery voltage (against 5v ref) maybe a later addition to tel you to change the battery (Max 1v)
 */
 
 /*
  * Debug print statements
  */
#define DEBUG                           / Comment out when not in DEBUG mode
#ifdef DEBUG
  #define DEBUG_PRINT(x) Serial.print(x)
  #define DEBUG_PRINTLN(x) Serial.println(x)
  #define DEBUG_PRINTDP(x,y) Serial.print(x,y)
  #define DEBUG_PRINTLNDP(x,y) Serial.println(x,y)
#else
  #define DEBUG_PRINT(x)
  #define DEBUG_PRINTLN(x)
  #define DEBUG_PRINTDP(x,y)
  #define DEBUG_PRINTLNDP(x,y)  
#endif
/*
 * Libraries
 */
#include 
#include 
#include 
#include 

/*
 * Variables
 * set up lamp pattern
 * B00000000 00000000 00000000 00000001
 * B00000000 00000000 00000000 00000010
 * etc all as long numbers
 * shift out 8 bits sequentially of the 4 byte long to set lamp sequence
 * All on would be 67108863 (3 FF FF FF)
 * Even (0,2,4...) would be 44739242
 * Odd (1,3,5....) would be 33554432
 */
const unsigned long lampPattern[]= {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,
32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432};       // 26 values since there are 26 LEDS (0-25)
const int SERIAL_OUT   = 12;                              // Serial Data to the shift registers Pin 14 on 74HC595
const int LATCH        = 13;                              // Latch Pin (ST_CP) pin 12 on 74HC595
const int CLK          = 14;                              // Clock Pin (SH_CP) pin 11 on &$HC595
const char ssid1[]     = "yourssid";
const char password1[] = "yourpassword";		  // min 8 chars
const char ssid2[]     = "********";
const char password2[] = "********";
const char ssid3[]     = "********";
const char password3[] = "********";
const char ssid4[]     = "********";
const char password4[] = "********";
const char ssid5[]     = "********";
const char password5[] = "********";
//char timeServer[] = "time.nist.gov";                  // time.nist.gov NTP server
//char timeServer[] = "uk.pool.ntp.org";                // uk.pool.ntp.org NTP server
const char timeServer[] = "pool.ntp.org";               //  NTP server Pool
const unsigned int localPort = 8888;                    // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48;                         // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE];                    // buffer to hold incoming and outgoing packets
unsigned long epoch;                                    // epoch time from internet
int lamp;                                               // lamp to light (0-19)
char tideState[7];                                      // tide state at this time
long int tideEpochTime[7];                              // tide time converted from JSON string
float tideHeight[7];                                    // tide height converted from JSON string
bool apiCallRequired = false;                           // api call not required first pass which is done in setup
byte period;                                            // tide period 0-6
bool ledFlash = false;                                  // 0-0.5 false and 0.51-1.0 true (true == flash)
bool noTideState = false;                               // failed to parse JSON
bool nowTooEarly = false;                               // time too early for parseds JSON

WiFiUDP Udp;                           // A UDP instance to let us send and receive packets over UDP
IPAddress timeServerIP;                // pool.ntp.org NTP server address
ESP8266WiFiMulti wifiMulti;            // Create an instance of the ESP8266WiFiMulti class
WiFiClient client;                     // Use WiFiClient class to create TCP connections
/*****************************************************************************************
 * SETUP code
 *****************************************************************************************/
void setup()
 {
  Serial.begin(115200);
  pinMode (SERIAL_OUT, OUTPUT);
  pinMode (LATCH, OUTPUT);
  pinMode (CLK, OUTPUT);
  ledTestSequence();
  startWiFi();                         // Connect to WiFi subroutine
  startUdp();                          // initialise UDP
  hitTidesApi();                       // before program starts call tides API and return JSON
  parseJSON();                         // now parse the JSON returned by WorldTides
  setSyncProvider(getUnixTime);        // Tell TimeLib what function returns epoch time
  setSyncInterval(10800);              // Set sync interval to 3hrs
  while(timeStatus() == timeNotSet);   // wait until time is set by the sync provider
 }
/*****************************************************************************************
 * LOOP code
 *****************************************************************************************/
void loop()                            // We've already made a call to Worldtides and Parsed the JSON
 {                                     // Now we have to check if the currrent time for which we want to light an led is within the dataset
  if(apiCallRequired)                  // Basically is dataset still valid; are we within its time frame
   {                                   // apiCallRequired will be set true if dataset is no lomger vaild
    hitTidesApi();                     // get a new tides JSON with an API call
    parseJSON();                       // now parse the JSON returned by WorldTides
    apiCallRequired = false;           // Reset API call flag
   }
  selectTidePeriod(now());             // find which period the current epoch time sits in
  DEBUG_PRINT(F("now() Epoch time = "));  // to distinguish from setSyncInterval calls
  DEBUG_PRINTLN(now()); 
  whichLampToLight(now());             // should return a number between 0 and 25
  printLampPattern(lamp);              // visual debug to Serial Monitor
// check if need to flash the lamp 
// Allow the flash to be the delay as we dont UDP every loop but use now()
// Flashing lamp should be occulting try 2.5s on 0.84s off unless error code then flash alternate lamps.
  if (!ledFlash)
  {
  	lightLamp(lamp);
  	delay(10000);						// delay 10s before looping
  	DEBUG_PRINTLN(F("10s"));
  }
  else
  {
  flashLamp();              // this will flash lamp and do error code
  }
}                           // Loop back and get next current time
 
 /********************************************************
  * this subroutine connects to one of a series of ssid's
  ********************************************************/
 void startWiFi() {
  WiFi.mode(WIFI_STA);                 // Set the ESP2866 to be a WiFi client (not an Access Point)
  wifiMulti.addAP(ssid1, password1);   // add Wi-Fi networks you want to connect to
  wifiMulti.addAP(ssid2, password2);
  wifiMulti.addAP(ssid3, password3);
  wifiMulti.addAP(ssid4, password4);
  wifiMulti.addAP(ssid5, password5);
  
  DEBUG_PRINTLN("Connecting");
  while (wifiMulti.run() != WL_CONNECTED) {  // Wait for the Wi-Fi to connect
   delay(500);
   DEBUG_PRINT('.');
  }
  DEBUG_PRINTLN();
  DEBUG_PRINT("Connected to ");
  DEBUG_PRINTLN(WiFi.SSID());             // Tell us what network we're connected to
  DEBUG_PRINT("IP address:\t");
  DEBUG_PRINTLN(WiFi.localIP());            // Send the IP address of the ESP8266 to the computer
}
/*******************************************************
 * this subroutine initialises the WiFi UDP
 *******************************************************/
void startUdp()
 {
  DEBUG_PRINTLN("Starting UDP");
  Udp.begin(localPort);                // Initializes the WiFi UDP library and network settings. Starts WiFiUDP socket, listening at local port PORT
  DEBUG_PRINT("Local port: ");
  DEBUG_PRINTLN(Udp.localPort());
 }
/*
 * this routine requests and returns the current Epoch time
 */
time_t getUnixTime()
{
 WiFi.hostByName(timeServer, timeServerIP);                                    //get a random server from the pool
 sendNTPpacket(timeServerIP);                                                  // send an NTP packet to a time server
 delay(1000);                                                                  // wait to see if a reply is available
 if (Udp.parsePacket())
  {
    Udp.read(packetBuffer, NTP_PACKET_SIZE);                                    // read the packet into the buffer       
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);          // the timestamp starts at byte 40 of the received packet and is four bytes
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);           // Extract the two words:                                                                                                                                             
    unsigned long secsSince1900 = highWord << 16 | lowWord;                     // combine the four bytes (two words) into a long integer of NTP time from 1/1/1970
    const unsigned long seventyYears = 2208988800UL;                            // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    epoch = secsSince1900 - seventyYears;                                       // subtract seventy years:
    DEBUG_PRINT(F("Unix Epoch time = "));  
    DEBUG_PRINTLN(epoch);                                                       // print Unix time
    return epoch;
  }
}
/*******************************************************************************
 * This routine sets up the NTP packet in order to request the current Epoc time
 *******************************************************************************/
void sendNTPpacket(IPAddress& address)                                               // send an NTP request to the time server at the given address
{
  memset(packetBuffer, 0, NTP_PACKET_SIZE);                                     // set all bytes in the buffer to 0
  /*
   * Initialize values needed to form NTP request
   * (see URL above for details on the packets)
   */
  packetBuffer[0] = 0b11100011;                           // LI, Version, Mode
  packetBuffer[1] = 0;                                    // Stratum, or type of clock
  packetBuffer[2] = 6;                                    // Polling Interval
  packetBuffer[3] = 0xEC;                                 // Peer Clock Precision
                                                          // packetBuffer[4]-[11] are 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  /* 
   * all NTP fields have been given values, now
   * we can send a packet requesting a timestamp:
   */
  Udp.beginPacket(address, 123);                          //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}
/*******************************************************
 * Test the LEDS before starting WiFi and UDP
 *******************************************************/
 void ledTestSequence()
 {
  clearAllLamps();
  delay(1000);
  lightAllLamps();
  delay(2000);
  clearAllLamps();
  loopLeds(); 
  clearAllLamps();
 }
/*******************************************************
 * Routine to clear lamps
 *******************************************************/
void clearAllLamps()
 {
  digitalWrite (LATCH, LOW);                        // hide pattern
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B00000000);  // 25-32 LED's OFF
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B00000000);  // 17-24 LEDs OFF
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B00000000);  // 9-16 LEDs OFF
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B00000000);  // 1-8 LEDs OFF
  digitalWrite(LATCH, HIGH);                        // Show Pattern
 }
 /*******************************************************
  * Routine to light all the lamps
  *******************************************************/
void lightAllLamps()
 {
  digitalWrite (LATCH, LOW);                        // hide pattern
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B00000011);  // 25-32 LED's ON
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B11111111);  // 17-24 LEDs ON
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B11111111);  // 9-16 LEDs ON
  shiftOut (SERIAL_OUT, CLK, MSBFIRST, B11111111);  // 1-8 LEDs ON
  digitalWrite(LATCH, HIGH);                        // Show Pattern
 }
 /*******************************************************
 * Routine to light a specific lamp lamp
 ********************************************************/
void lightLamp(int led)
 {
  unsigned long bin = lampPattern[led];             // transfer const lampPattern value to bin as temp store we can bit shift
  digitalWrite (LATCH, LOW);                        // hide pattern
  shiftOut(SERIAL_OUT, CLK, MSBFIRST, bin>>24);     // send most significant 8 bits
  shiftOut(SERIAL_OUT, CLK, MSBFIRST, bin>>16);     // send next 8 bits
  shiftOut(SERIAL_OUT, CLK, MSBFIRST, bin>>8);      // send next 8 bits
  shiftOut(SERIAL_OUT, CLK, MSBFIRST, bin);         // send least significant 8 bits
  digitalWrite(LATCH, HIGH);                        // Show Pattern   
 }
 /*******************************************************
 * LEDs test routine
 * Lights each lamp in sequence
 ********************************************************/
void loopLeds()
{
  for ( byte i = 0; i < 26; i++)
    {
      lightLamp(i);
      delay(100);
    }
}
/*******************************************************
 * This routine requests the Worldtides API JSON
 *******************************************************/ 
void hitTidesApi()
{
 if (client.connect("http://www.worldtides.info", 80))               // By default HTTP uses Port 80 and HTTPs uses Port 443
  {
    DEBUG_PRINTLN(F("Sending API request to WorldTides"));
    client.println("GET http://www.worldtides.info/api?extremes&datum=lat&length86400&lat=50.7628&lon=-1.3005&key=yourkeyhere");
    client.println();
  }
    else
  {
    DEBUG_PRINTLN(F("Connection to WorldTides failed"));
  }
  delay(2500);                                                      // delay was increased from 500ms for slow mobile connections as JSON not returning in time
}
/************************************************************************
 * Parses the return from sending the Worldtides API
 * 
 * typical reply
 * Station = Ryde
 * Tide Period 0 starting Epoch = 1521884790 Height = 1.023 State  = L
 * Tide Period 1 starting Epoch = 1521909372 Height = 3.638 State  = H
 * Tide Period 2 starting Epoch = 1521930308 Height = 1.161 State  = L
 * Tide Period 3 starting Epoch = 1521954439 Height = 3.560 State  = H
 * Tide Period 4 starting Epoch = 1521976514 Height = 1.177 State  = L
 * Tide Period 5 starting Epoch = 1522000083 Height = 3.454 State  = H
 * Tide Period 6 starting Epoch = 1522030083 Height = 1.177 State  = L
 *
 ************************************************************************/
void parseJSON()
{
 DEBUG_PRINTLN(F("Parsing JSON"));
 String tideTimeStr[7];                                  // time of each high or low tide as a string
 String tideHeightStr[7];                                // tide height at this state (not currently used)
 String tideStateStr[7];                                 // tide state at this time

 while(client.available())
  {
    if(client.findUntil("station\":\"", "\n\r"))
      {
        String station = client.readStringUntil('"');
        Serial.print(F("Station = "));
        Serial.println(station);
      }

    for (int i = 0 ; i < 7 ; i++)                               // 6 tide periods in each API call
      {                                                         // Acquire time, height, type (high or low)
        if(client.findUntil("dt\":", "\n\r"))                   // adding this if statements stops it continuing when all periods are discovered
          {
              tideTimeStr[i] = client.readStringUntil(',');     // Tide time as Epoch time
              DEBUG_PRINT(F("Tide Period "));
              DEBUG_PRINT(i);
              DEBUG_PRINT(F(" starting Epoch = "));
              tideEpochTime[i] = tideTimeStr[i].toInt();        // convert time string to integer
              DEBUG_PRINT(tideEpochTime[i]);

              client.findUntil("height\":", "\n\r");            // Tide height in M
              tideHeightStr[i] = client.readStringUntil(',');
              DEBUG_PRINT(F(" Height = "));
              tideHeight[i] = tideHeightStr[i].toFloat();       // convert tide height to a float
              DEBUG_PRINTDP(tideHeight[i],3);  

              client.findUntil("type\":\"", "\n\r");           // Tide type; High or Low
              tideStateStr[i] = client.readStringUntil('"');
              tideState[i] = tideStateStr[i].charAt(0);        // extract the first letter of the tide state
              DEBUG_PRINT(F(" State "));
              DEBUG_PRINT(F(" = "));
              DEBUG_PRINTLN(tideState[i]);                    // should be 'H' or 'L'
            }
       }
  }
  DEBUG_PRINTLN(F(""));
client.stop();
}
/***********************************************************************
 * This routine works out which tide period applies to the current time
 ***********************************************************************/
 void selectTidePeriod(unsigned long currentTime)
 {
  if(currentTime > tideEpochTime[6])                 // if current time is in the final tide period as we have no end time (tideEpochTime[7])
  {                                                  // to use to determine our position within the final tide period so new API call is required
    apiCallRequired = true;                          // so set flag to request another API call
    DEBUG_PRINTLN(F("New API Call required"));
    return;                                          // exit the routine in order to make that API call
  }
  if(currentTime > tideEpochTime[5])                 // all good we can use tideEpochtime[6] to determine how far through the tide period we are
  {
    period = 5;
    DEBUG_PRINT(F("Period = "));
    DEBUG_PRINTLN(period);
    return;
  } 
  if(currentTime > tideEpochTime[4])
  {
    period = 4;
    DEBUG_PRINT(F("Period = "));
    DEBUG_PRINTLN(period);
    return;
  }
  if(currentTime > tideEpochTime[3])
  {
    period = 3;
    DEBUG_PRINT(F("Period = "));
    DEBUG_PRINTLN(period);
    return;
  }  
  if(currentTime > tideEpochTime[2])
  {
    period = 2;
    DEBUG_PRINT(F("Period = "));
    DEBUG_PRINTLN(period);
    return;
  }
  if(currentTime > tideEpochTime[1])
  {
    period = 1;
    DEBUG_PRINT(F("Period = "));
    DEBUG_PRINTLN(period);
    return;
  }
  if(currentTime > tideEpochTime[0])
  {
    period = 0;
    DEBUG_PRINT(F("Period = "));
    DEBUG_PRINTLN(period);
    return;
  }
  if(currentTime < tideEpochTime[0])                 // our first tide period starts after the current time we have a problem with the returned API
    nowTooEarly = true;                              // set too early flag
    DEBUG_PRINT(F("Too Early"));
    return;
}
/*************************************************************************************************************
 * routine to decide which lamp to light
 * the first thing to decide is whether its a high to low tide, lamps 0-10 or low to high tide, lamps 10-25
 * there are 24 lamps total 12 for high to low and 12 for low to high so every 32 mins in effect
 *************************************************************************************************************/
void whichLampToLight(unsigned long currentTime)
{
  if(nowTooEarly)                           // now() time is before the start of period 0
    {
      ledFlash = true;						          // set flag to flash led 0 and 1 alternating
      DEBUG_PRINT(F("Time before period 0 so flash lamp 0 and lamp 1"));
      return;
    }
  // as we are in 'period' start working out where we are within the period
  
  float timeIntoTidePeriod = float(currentTime - tideEpochTime[period]);
  float lengthOfTidePeriod = float(tideEpochTime[period + 1] - tideEpochTime[period]);      // hence we can only use period 0-5
  float tidePeriodFraction =  timeIntoTidePeriod / lengthOfTidePeriod;                      // should return a number between 0 - 0.99
  
  // now we have the tide fraction (how far through the period we are)
  // we need to decide which lamp to light which depends whether tide period is 'H' to 'L' or 'L' to 'H'                           
  // should return a number between 0 - 0.99
  
  if(tideState[period] == 'H')                                              // high to Low therefore lamps 0-10 although 10 is really 0 for low to high section
    {
      DEBUG_PRINT(F("High Tide Fraction = "));
      DEBUG_PRINTLNDP(tidePeriodFraction,3);
      float tideLampFraction = tidePeriodFraction * 10;                     // which lamp 0-10 as a float
      lamp = round(tideLampFraction);                                       // max lamp number is 10 as it rounds up 9.99
      if( tideLampFraction - floor(tideLampFraction) > 0.5 )                // fractional part test
        {
          ledFlash = true;    // set flash flag for approaching lamp
        }
      else
        {
          ledFlash = false;   // set dont flash flag for lamp we have passed
        }
    }
  else if(tideState[period] == 'L')                                         // if not high must be low
    {
      DEBUG_PRINT(F("Low Tide Fraction = "));
      DEBUG_PRINTLNDP(tidePeriodFraction,3);
      float tideLampFraction = tidePeriodFraction * 15 + 10;                // which lamp 10-25 as a float
      lamp = round(tideLampFraction);                                       // max lamp number is 25 as it rounds up 14.9+10 = 24.9
      if( tideLampFraction - floor(tideLampFraction) > 0.5 )                // fractional part test
        {
          ledFlash = true;    // set flash flag for approaching lamp
        }
      else
        {
          ledFlash = false;   // set don't flash flag for lamp we have passed
        }      
    }
  else
    {
      DEBUG_PRINTLN(F("No tide state"));    // Error state so flash lamps 24 and 25
      ledFlash = true;                      // set flag to flash led
      noTideState = true;                   // set tide state flag
      apiCallRequired = true;               // so set flag to request another API call
    }
  DEBUG_PRINT(F("light lamp "));
  DEBUG_PRINTLN(lamp);
}
/**********************************************************************
 * Print the lap pattern as 32 bits (4 Bytes)to the Serial Monitor
 **********************************************************************/
void printLampPattern(byte lamp)
{
  DEBUG_PRINTLN(F("H         L              H"));
    for( int i = 0; i < 26; i++)                    // only 25 bits required
    {
      if( bitRead(lampPattern[lamp], i) == 1)
      {
        DEBUG_PRINT(F("1"));
      }
      else
      {
        DEBUG_PRINT(F("0"));
      }
    }
  DEBUG_PRINTLN(F(""));                            // carriage return
}
/***********************************************************************
 * flashLamp() will occult 
 ***********************************************************************/
void flashLamp()
{
  if(noTideState)                   // Failed to parse JSON flash lamp 24 and 25 alternately
  {
    for(byte i=0; i<4; i++)         // loop 4 times to give 10s delay before starting main loop again
    {
     lightLamp(24);                 // turn 'lamp' 24 on
     DEBUG_PRINTLN(F("24 ON"));
     delay(1250);
     clearAllLamps();
     DEBUG_PRINTLN(F("24 OFF"));    // turn 'lamp' 24 off
     lightLamp(25);                 // turn 'lamp' 25 on
     DEBUG_PRINTLN(F("25 ON"));
     delay(1250);
     clearAllLamps();
     DEBUG_PRINTLN(F("25 OFF"));   // turn 'lamp' 25 off
    }
    noTideState = false;           // reset flag
    return;
  }
  if(nowTooEarly)                   // now() or UDP time before period[0] starts
  {
    for(byte i=0; i<4; i++)         // loop 4 times to give 10s delay before starting main loop again
    {
     lightLamp(0);                 // turn 'lamp' 24 on
     DEBUG_PRINTLN(F("24 ON"));
     delay(1250);
     clearAllLamps();
     DEBUG_PRINTLN(F("24 OFF"));    // turn 'lamp' off
     lightLamp(1);                 // turn 'lamp' on
     DEBUG_PRINTLN(F("25 ON"));
     delay(1250);
     clearAllLamps();
     DEBUG_PRINTLN(F("25 OFF"));   // turn 'lamp' off

    }
    nowTooEarly = false;           // reset flag
    return;
  }
// not in error (the anticipated state) so occult lamp
for(byte i=0; i<3; i++)            // loop 3 times to give 10s delay before starting main loop again
  {
    lightLamp(lamp);                 // turn 'lamp' on
    DEBUG_PRINTLN(F("2.7s ON"));
    delay(2700);
    clearAllLamps();
    DEBUG_PRINTLN(F("0.7s OFF"));   // turn 'lamp' off
    delay(640);
    }
}

/********************************************************************************
		   For reference API returns
 * {"status":200,"callCount":1,"copyright":"Tidal data retrieved from www.worldtide.info. Copyright (c) 2014-2017 Brainware LLC. Licensed for use of individual spatial coordinates on behalf of\/by an end-user. Copyright (c) 2010-2016 Oregon State University. Licensed for individual spatial coordinates via ModEM-Geophysics Inc. NO GUARANTEES ARE MADE ABOUT THE CORRECTNESS OF THIS DATA. You may not use it if anyone or anything could come to harm as a result of using it (e.g. for navigational purposes).","requestLat":50.7628,"requestLon":-1.3005,"responseLat":50.7333,"responseLon":-1.1667,"atlas":"TPXO_8_v1","station":"Ryde","requestDatum":"LAT","responseDatum":"LAT","extremes":[{"dt":1521884790,"date":"2018-03-24T09:46+0000","height":1.023,"type":"Low"},{"dt":1521909372,"date":"2018-03-24T16:36+0000","height":3.638,"type":"High"},{"dt":1521930308,"date":"2018-03-24T22:25+0000","height":1.161,"type":"Low"},{"dt":1521954439,"date":"2018-03-25T05:07+0000","height":3.56,"type":"High"},{"dt":1521976514,"date":"2018-03-25T11:15+0000","height":1.177,"type":"Low"},{"dt":1522000083,"date":"2018-03-25T17:48+0000","height":3.454,"type":"High"},{"dt":1522022435,"date":"2018-03-26T00:00+0000","height":1.267,"type":"Low"},{"dt":1522045612,"date":"2018-03-26T06:26+0000","height":3.43,"type":"High"}]}
 *
 *Of which the interesting bit is
 *
 *[{"dt":1521884790,"date":"2018-03-24T09:46+0000","height":1.023,"type":"Low"}
 *,
 *{"dt":1521909372,"date":"2018-03-24T16:36+0000","height":3.638,"type":"High"}
 *,
 *{"dt":1521930308,"date":"2018-03-24T22:25+0000","height":1.161,"type":"Low"}
 *,
 *{"dt":1521954439,"date":"2018-03-25T05:07+0000","height":3.56,"type":"High"}
 *,
 *{"dt":1521976514,"date":"2018-03-25T11:15+0000","height":1.177,"type":"Low"}
 *,
 *{"dt":1522000083,"date":"2018-03-25T17:48+0000","height":3.454,"type":"High"}
 *,
 *{"dt":1522022435,"date":"2018-03-26T00:00+0000","height":1.267,"type":"Low"}
 *,
 *{"dt":1522045612,"date":"2018-03-26T06:26+0000","height":3.43,"type":"High"}
 *]
 ********************************************************************************/