External eeprom read clash

Hello,
I want to use an external eeprom to hold 2200 four letter words. These are to be read randomly and displayed on some clock hardware.
Using small sketches I have written all the words that I want and in another sketch I can read them without any issue.
I then implement the same small read code into my main clock code and it does not return readable data.
If I put just the read code on to the micro in the clock then it reads the data perfectly.
If I put an I2C scanner on the same micro it detects the external eeprom AND the BME280 sensor perfectly.
The problem just comes when I integrate the read process in my main clock code.
The main clock code already uses the eeprom.h lib in order to store data from a web configuration page so I wanted to keep the words data entirely separate.
I just wonder if there is any clash between using the eeprom.h lib AND trying to read from an external eeprom.
My eeprom read code is very simple and works perfectly in isolation.

Here is my standalone sketch that works perfectly - it reads the first 22 words as a test:

#include <Wire.h> 

int wordtext[4]; // used to store returned word
int wordpointer=0; // points to element of returned word to store it
#define eeprom 0x50 //defines the base address of the EEPROM

void setup()  {
Wire.begin(); //creates a Wire object
  Serial.begin(9600); 

unsigned int currentaddress=0;


Serial.println("");
Serial.println("Start Read");

for(int readword=0; readword<22; readword++)
{
wordpointer = 0; // reset word pointer
//Now Read 4 bytes
  for(int address = (readword * 4); address< (readword*4+4); address++) {
  wordtext[wordpointer] = readEEPROM(eeprom, address);
  wordpointer++;
  }

// now print out contents of wordtext
Serial.print(char(wordtext[0]));
Serial.print(char(wordtext[1]));
Serial.print(char(wordtext[2]));
Serial.print(char(wordtext[3]));
Serial.println("");
}

}



void loop() {
  /*there's nothing in the loop() function because we don't want the arduino to 
  repeatedly write the same thing to the EEPROM over and over. 
  We just want a one-time write, so the loop() function is avoided with EEPROMs.*/
}

//defines the readEEPROM function
byte readEEPROM(int deviceaddress, unsigned int eeaddress ) {
  byte rdata = 0xFF;
  Wire.beginTransmission(deviceaddress);
  Wire.write((int)(eeaddress >> 8));      //writes the MSB
  Wire.write((int)(eeaddress & 0xFF));    //writes the LSB
  Wire.endTransmission();
  Wire.requestFrom(deviceaddress,1);
  if (Wire.available()) 
    rdata = Wire.read();
  return rdata;
  }

I have tried progressively adding all the libs that I use in the main clock sketch and the standalone code continues to work without issue. I will continue to check it this way but just wondered if there any known clashes which would cause this to fail.
The read function is super simple and very clearly knows to read data from the external device and yet it returns 0xFF every read (which is set in the read function as a default returned value).
I will add some debug print statements in that function to check that the address is set right at that point as well as to see what if (Wire.available()) actually returns.
Thank you for any pointers

  • Richard

Just a idle late night thought, but what if you were to do this instead:

  Wire.requestFrom(deviceaddress,1);
  return Wire.read();

No check to see if there's data available, just assume that it will be there, and try and read it. It's easy enough to try and its success/failure would be illuminating either way.

Edit: oh. I just realized that the sketch you included was something that worked. That's not at all useful for debugging the sketch you didn't include that doesn't work, now it is?

After some debugging I can confirm that the device address is being passed correctly however the if (Wire.available()) test is failing ie. Wire.available() is returning false.

Not sure if I can help but I suggest that you post your problematic program in full and tell us which board you're compiling for as well as which external EEPROM you use. Please also indicate where we can find any third party libraries that you might be using.

Hello and thank you for replying, I can certainly to that but it's 2800 lines long, is it OK to post something that big?

One file or multiple files? Multiple files can be posted in multiple replies (please indicate file names when doing so). For one file, you will need to zip and attach.

I asked for additional information so please provide that as well :wink:

FLWremote.zip (7.2 MB)
Code is written for Wemos D1 Mini (ESP8266) in PlatformIO.
eeprom is 24C16N attached via I2C with address 0x50
Mostly standard libraries though there is one custom one which is listed in the platofrmIO.ini file.

I first had to convert your code for use with the the Arduino IDE and I think that I did succeed.

Your problem is more than likely the following warning that I got

C:\Users\bugge\OneDrive\Documents\Arduino\1_forum.arduino.cc\1353797_eepromClash\Nixology_Panaplex_8_digit.org\Nixology_Panaplex_8_digit.org.ino:2508:71: warning: iteration 8 invokes undefined behavior [-Waggressive-loop-optimizations]
 2508 |   for (int zerobits = 0; zerobits < 9; zerobits++) BitArray[zerobits] = 0x00;
      |                                                    ~~~~~~~~~~~~~~~~~~~^~~~~~
C:\Users\bugge\OneDrive\Documents\Arduino\1_forum.arduino.cc\1353797_eepromClash\Nixology_Panaplex_8_digit.org\Nixology_Panaplex_8_digit.org.ino:2508:35: note: within this loop
 2508 |   for (int zerobits = 0; zerobits < 9; zerobits++) BitArray[zerobits] = 0x00;
      |                          ~~~~~~~~~^~~
C:\Users\bugge\OneDrive\Documents\Arduino\1_forum.arduino.cc\1353797_eepromClash\Nixology_Panaplex_8_digit.org\Nixology_Panaplex_8_digit.org.ino:2508:71: warning: 'void* __builtin_memset(void*, int, unsigned int)' forming offset 8 is out of the bounds [0, 8] of object 'BitArray' with type 'unsigned char [8]' [-Warray-bounds]
 2508 |   for (int zerobits = 0; zerobits < 9; zerobits++) BitArray[zerobits] = 0x00;
      |                                                    ~~~~~~~~~~~~~~~~~~~^~~~~~
C:\Users\bugge\OneDrive\Documents\Arduino\1_forum.arduino.cc\1353797_eepromClash\Nixology_Panaplex_8_digit.org\Nixology_Panaplex_8_digit.org.ino:547:15: note: 'BitArray' declared here
  547 | unsigned char BitArray[64 / 8]; // space for 64 bits
      |               ^~~~~~~~

You're writing outside the bounds of the array; array indices run from 0 to the size of the array minus 1 so the maximum index is 7 and you're writing the non-existing BitArray[8]. After that all bets are off.

The full sketch that I compiled in the IDE:

#define HV5522
#define SP352


#include "dummy.h"
// All updated to drive two HV5622 chips with 64 outputs to provide 6 digis on SP=101 Panaplex displays with colons, dots and most commas etc.
// 28/06/2020 - still in lockdown

// Original code written in response to 2 Tube Clock required challenge by Roddy Scott - I knew I'd get to use those 74141's one day!
//
// Use 3 GPIO's to drive Shift Register that in turn will drive 2 x 4 bit BCD converters (74141) to drive 2 x Nixie Tubes directly. UPDATED to single HV5622 in May 2020
// Use 1 x GPIO's to PWM drive Opto Couplers for individual tube brightness - Removed Opto Couplers and using blanking signal on HV5622 in May 2020
// Use 2 x GPIO's to I2C coupled to a BMP280 temp/pressure sensor
// Use 1 x GPIO to drive a single (or multiple) Neopixels WS2811 - NOT ON PANAPLEX VERSION
// Use 1 x GPIO to connect a PIR module
//
// 02/03/2019 - converted to traditional time library sync method - using PJRC example Code
// 04/03/2019 - added timezone library to deal with timezones!
// 05/03/2019 - tidied up some things including seconds display (no longer send redundant first loop second) and created a variable clockTuner that can be used to help match seconds display with another clock
// 06/03/2019 - set up function for nightMode instead of multiple checks
// 16/03/2019 - Added code to deal with PIR connected to D5, clock must work with NO PIR connected AND must turn off after suitable delay if PIR is connected
// 16/03/2019 - Removed WiFi resetting code - not really needed - redeployed pin for PIR connection
// 20/03/2019 - Transitioned from using WiFi Manager to using ESPAsyncWiFiManager and associated librarys - had to add ESPAsyncWebServer.h, ESPAsynWiFiManager.h and ESPAsyncTCP.h libraries to the IDE
// 31/03/2019 - Checked PIR to find that when clock goes off, Also extended PIR on time to 5 minutes.
// 25/04/2019 - Changed so that PIR can activate 24x7 and the delaytime varies depending on whether it is daytime or nighttime (nightmode)
// 28/06/2020 - Code re-deployed for 6 x Beckman SP-101 displays (or similar)- need to consult the SP-151 Code to work out how I did it and do the same in here.
// 06/07/2020 - V07 - Mostly there - last thing I want to look at in this version is the main timeing loop and remove dependance on NonBlock counters and move over to updating time on second change only
//              Need to anaylse main loop and be sure about where setLocal is being used - use only once per loop itteration - check in the various voids displaytime and displaydate.
//              then at some point in the main loop - only call displaytime if the second has changed.
// 29/11/2020   Changed to read BME sensor instead - because that is what I seem to have now!
//
// 01/12/2020   Added humidity now that there is a BME sensor
// 06/12/2020   Moved to Sperry SP-353 code base for 6 digit SP-353 based clocks
// 15/12/2020   Findally got some meaningful data displayed - now on to tidying up the code
//              Moved to V03 - remove all redundant code (neopixel stuff)
//              Added DEF for BME or BMP - set only 1 and it will control what library is loaded and how it is used and whether or not humidity is displayed.
// 31/12/2020   Moved to V04 - added def for SP-353 or SP-352 as there is an error in the footprint for the SP-352 which I need to fix in software - B/F and E/C swapped on left hand digit.
// 03/01/2021   Moved to V03 - taking out some of the old LED requests and start looking to see if I can get status info displayed on power up.
// 12/01/2021   Moved to V06 - now I've worked out how to get things displayed on startup!
// 13/01/2021   Moved to V7, changing colon bits in setAllBits to reflect the six decimal points available
//              SetTransitionBits still uses CTR, CTL etc
// 16/02/2021   Saved as Beckman352x8v07 to move to 8 digits on two drivers for SP-352 and SP-356 displays.
//              Because some of the 8 digits were spread across two drivers I changed the code to use a single 64 bit variable 'L' which has its bits set and is shifted out all together.
// 18/02/2021   Moved to V08 as I start to sort out the display - having got the basic character shapes working!
// 21/02/2021   Moved to V09 as I want to add compile switch to set time display to HH-MM-SS instead of <blank> HHMMSS <blank>
// 25/02/2021   Moved to V10 - added compile switch for HV5622 drivers
// 15/04/2021   WIth help from Nick Stock - finally (it seems) got it working with HV5622 drivers - nothing obvious (to me) about the trnsition from 5522 to 5622 !!!!
// 17/04/2021   Moved to V11 - adding startup segment test routine
// 09-22-2023   Changed from using 'l' to BitArray, also changed pointers array for 4 x SP-332 displays, also added name scroll stuff
// 09-29-2023   Fixed the scolling name stuff having finally plugged a sensor in!
//
// 03-05-2024   This code is the combination of the following two files:
//              Nixology_SP_352_356_8_digit_Panaplex_03_12_24
//              Nixology_SP_332_8_digit_Panaplex_03_12_24
//              No use a #define to choose between the two when compiling
//              New Code is Panaplex_8_Digit - committed to New Repo
//
// 05-08-2024   Moved into PlatformIO

// USER CHANGES - JEFF
//  07-07-2023   Begin to change configuration to support USA parameters
//  07-09-2023   Added additional display character definitions;  changed all instances of HH-MM-SS to HH_MM_SS to fix compiler warnings
//  07-17-2023   Move all includes to top of file
//  07-19-2023   Added sensor detection and logic to display humidity only with the BME sensor type
//  07-19-2023   Added serial monitor test display of sensor type and status - noSenSor; E-SenSor; P-SenSor
//  07-23-2023   Converted the clock to display degrees F and inches of mercury
//  07-23-2023   Correction to displaytest to exercise segments and decimals
//  07-23-2023   Added Nixieshift controls for individual decimal points
//  07-23-2023   Added logic for 12/24 hour Display; added variable to adjust scroll speed
//  07-23-2023   Added variable to set what second if clock would trigger date or environmentals
//  07-23-2023   Added sweeping decimals during AP Connection on start-up
//  07-24-2023   Spaced the Hg for the pressure display
//  08-09-2023   Added tail to 9 - add in definition and transition sequence.
//  08-09-2023   Added variables to set imperial or metric in one place in the environmental area.
//  08-09-2023   Added metric or US Units to initial display after sensor type
//  10-14-2023   Moved date right one place; Scroll name - JEFF -; set startup rotation of segments to five times
//  10-23-2023   Commented the zerobits references - function not needed and causes compiler warnings
//  10-23-2023   Added horizontal and vertical segments to startup test routine
//  10-26-2023   Added option to skip the name message display
//  10-29-2023   Fixed metric temp to show tenths of a degree
//  11-03-2023   Date now selected by the environmental settings for metric or imperial
//               Metric displays DDdd-MMMM-YYYY;  imperial (US Units) displays MMMM-DDdd-YYYY
//  11-18-2023   IP Address displays as a scroll to the left instead of two blocks
//  12-24-2023   Added new scrolling display - JEFFS SP-332 -
//  02-07-2024   Added brightness control to the user settings (0-1023 range)
//  03-12-2024   Cleaned up the Time Zone and DST Section

// ALL LIBRARY INCLUDES AT TOP OF CODE---------------------------------------------------

// includes for time
#include <TimeLib.h>

// includes for timezone and DST calculations - change your rules here - see the timezone library documentation for details
#include <Timezone.h>

// includes for WiFi
// #include <ESP8266WiFi.h>
// #include <WiFiUdp.h>
#include <DNSServer.h>

// includes for web gui
#include "EEPROMConfig.h"
#include "web.h"
#include "configs.h"

// OTA Include:
#include <ArduinoOTA.h>

// Includes for AsyncWifi Manager
// #include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h>

AsyncWebServer server(80);
DNSServer dns;

// includes etc for BMP280/BME280
// #include <Wire.h>
// #include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Adafruit_BMP280.h>

// for the external eeprom read
//#include <wire.h> //for eeprom
int wordtext[4]; // used to store returned word
int wordpointer=0; // points to element of returned word to store it
#define eepromaddr 0x50 //defines the base address of the external EEPROM

// NeopixelBus Stuff
#include <NeoPixelBus.h>
const uint16_t PixelCount = 4; // this example assumes 4 pixels, making it smaller will cause a failure
const uint8_t PixelPin = 2;  // make sure to set this to the correct pin, ignored for Esp8266
#define colorSaturation 128
NeoPixelBus<NeoGrbFeature, NeoWs2812xMethod> strip(PixelCount, PixelPin);
RgbColor red(colorSaturation, 0, 0);
RgbColor green(0, colorSaturation, 0);
RgbColor blue(0, 0, colorSaturation);
RgbColor white(colorSaturation);
RgbColor black(0);

HslColor hslRed(red);
HslColor hslGreen(green);
HslColor hslBlue(blue);
HslColor hslWhite(white);
HslColor hslBlack(black);
// end of neopixelbus stuff





//defines the readEEPROM function
byte readEEPROM(int deviceaddress, unsigned int eeaddress ) {
  byte rdata = 0xFF;
  Wire.beginTransmission(deviceaddress);
  Wire.write((int)(eeaddress >> 8));      //writes the MSB
  Wire.write((int)(eeaddress & 0xFF));    //writes the LSB
  Wire.endTransmission();
  delay(15);
  Wire.requestFrom(deviceaddress,1);
  if (Wire.available()) {
    Serial.println("Wire Available");
    rdata = Wire.read();
    };
  return rdata;
  }



//-------------------------------------------------------------------------------------------------------------
// CUSTOMIZATION AREA FOR TWEAKING CLOCK OPERATION BETWEEN THE HASHED LINES







//*****ENVIRONMENTAL VARIABLES*****

// These variables can be used to fine tune the temp and pressure readings to known source

// HUMIDITY OFFSET - PERCENTAGE RH
int humidityoffset = 11; // Calibrate the sensor in percentage

//*****Metric Section*****
// #define metricunits  //uncomment for metric units
// PRESSURE OFFSET - PASCALS **METRIC**
// int PascalOffset = 0;  //Pressure offset in Pascals if using that system (default = 0)  Adjust for altitude

// TEMPERATURE OFFSET - CENTIGRADE
// float CelciusOffset = 0;  //Temp offset in degrees C if displaying Centigrade - to calibrate sensor (default = 0)

//*****Imperial Section*****
// #define imperialunits  //uncomment for imperial units
// PRESSURE OFFSET - INCHES OF MERCURY  **IMPERIAL** Set for Altitude
// int inHgoffset = 98;  //Pressure offset inches of Mercury/100 if displaying inches of Hg - to calibrate sensor (default = 0 at sea level)
// Example:  100 will change pressure from 29.50 to 30.50.  Altitude offset is 100 for every 1000 feet elevation.  Flagstaff add about 648.
// Barometric pressute is normalized to sea level.

// TEMPERATURE OFFSET - FAHRENHEIT
// float FahrenheitOffset = -11.9;  //Temp offset in degrees F if displaying Fahrenheit - to calibrate sensor (default = 0) - Clock will add heat

//*****DISPLAY AND APPEARANCE VARIABLES*****

// int Maxbright = 400;     //Set clock maximum Display brightness (max=1023 default 400)

// What second do we want to display date and time?  Note:  Setting to less than 9 causes the date and environmentals to display each time
// int datedisplaysecs = 8;  //set to the second that you want the date and environmentals displayed - Set to 9 or greater!

// 12 OR 24 HOUR OPERATION - Used to change between 12 and 24 hour time system - "0" is for 24 hour time and "12" is for 12 hour time
// int timesystem = 12;

// TIME TWEAK - Set ClockTuner to a number of seconds that you want to add or subtract from the displayed time in order to make it match another clock
// int clockTuner = 1;

// SCROLL SPEED - milliseconds per step.  Default is 90.  Lower number like 50 is faster, higher number like 120 is slower
// Used to control the speed that the display shifts left for the year and barometric pressure
int scrollspeed = 90;

// HOURS OF OPERATION AND PIR
// Operational Hours setup - change values here according to when you would like the clock to be active
// If you want the clock to be always on then set firsthour to 0 and lasthour to 23, night mode will never activate!

// int firsthour = 0;  //clock comes on at 07:00
// int lasthour = 23;  //clock goes off at 00:00, ie - last hour on is 23.00

// PIR ACTIVATION - Set the time (in minutes) the clock will remain active following PIR activation
// Now have different delays for day and Night time
// int DayclockONtime = 10;
// int NightclockONtime = 4;

// DISPLAY DATE OR ENVIRONMENTALS - on alternate minute cycles.  Set "1" to alternate or "0" for date only
int dispDate = 1;

// SHOW SCROLLING NAME MESSAGE -
//  If set to show message - Display order: Date --> Message --> Environmentals (3 minute cycle)
//  if set to not show message - Display order: Date --> Skip Message --> Environmentals (3 minute cycle)
int showname = 1; // To show the name message, set showname to "1";  To skip, set to "0"

// All New New string processing vars
char buf[70]; // made 5 longer than string max length - definable string is 40 - 12 digit clock adds 12+12
String MessageString1;

// HTML items will be showin in this order
BaseConfigItem *clockSet[] = {
    // Clock
    &firsthour,
    &lasthour,
    &DayclockONtime,
    &NightclockONtime,
    &timesystem,
    &leadingzero,
    &ordinals,
    &timezone,
    &metricUnits,
    &PressOffset,
    &TempOffset,
    &clockTuner,
    &Maxbright,
    &datedisplaysecs,
    &MessageString,
    0};

// A composite config item can contain other config items (including other CompositeConfigItems)
// It is just an easy way to group config items together
CompositeConfigItem clockConfig("clock", 0, clockSet);

// This controls saving config items to EEPROM and retrieving them from there.
EEPROMConfig config(clockConfig);

//*************PRE-DEFINE VOIDS******

void pirTubes();
void nonBlock(unsigned long delay_time);
void bitArrayWrite(const unsigned int index, const boolean value);
void setLocalTime();
// void fillMessageTime(int sgap2);
void bitbashTX();
void setAllBits(uint8_t aa, uint8_t bb, uint8_t cc, uint8_t dd,  uint8_t DP1, uint8_t DP2, uint8_t DP3, uint8_t DP4);
void disptest();
void nixieshift(uint8_t d, uint8_t c, uint8_t b, uint8_t a, uint8_t dpstatus);
void pirChange();
void displaytime();
void displaytemp();
void displayhumidity();
void displaypressure();
void displaydate();
void displayword();
void zerobits();
void startup();
void displayname();
void dimtube();
void pixelsOff();
//void colorWipe(uint32_t c, uint8_t wait);



//*****CHOOSE TIMEZONE SERVER - TIMEZONE - DST START/STOP SETTINGS*****

// NTP SERVER: - Select appropriate NTP Time Server for Connection
static const char ntpServerName[] = "us.pool.ntp.org";
// static const char ntpServerName[] = "europe.pool.ntp.org";

// Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; // UTC + 11 hours
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; // UTC + 10 hours
Timezone ausET(aEDT, aEST);

// Moscow Standard Time (MSK, does not observe DST)
TimeChangeRule msk = {"MSK", Last, Sun, Mar, 1, 180};
Timezone tzMSK(msk);

// Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};   // Central European Standard Time
Timezone CE(CEST, CET);

// United Kingdom (London, Belfast)
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; // British Summer Time
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0};  // Standard Time
Timezone UK(BST, GMT);

// UTC
TimeChangeRule utcRule = {"UTC", Last, Sun, Mar, 1, 0}; // UTC
Timezone UTC(utcRule);

// US Eastern Time Zone (New York, Detroit)
TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; // Eastern Daylight Time = UTC - 4 hours
TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300};  // Eastern Standard Time = UTC - 5 hours
Timezone usET(usEDT, usEST);

// US Central Time Zone (Chicago, Houston)
TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300};
TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360};
Timezone usCT(usCDT, usCST);

// US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420};
Timezone usMT(usMDT, usMST);

// Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST);

// US Pacific Time Zone (Las Vegas, Los Angeles)
TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420};
TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480};

Timezone usPT(usPDT, usPST);

std::map<String, Timezone &> timeZones = {
    {"Aus Eastern", ausET},
    {"EU Central", CE},
    {"Moscow", tzMSK},
    {"UK", UK},
    {"US Eastern", usET},
    {"US Central", usCT},
    {"US Mountain", usMT},
    {"US Arizona", usAZ},
    {"US Pacific", usPT},
    {"UTC", UTC}};
//-----------------------------------------------------------------------------------------

// How to set Timezone and DST calculations

// Define a TimeChangeRule for Standard(STD) or Daylight Saving Time(DST) as follows:
// TimeChangeRule myRule = {abbrev, week, dow, month, hour, offset};

// Where:
// abbrev is a character string abbreviation for the time zone; it must be no longer than five characters.
// week is the week of the month that the rule starts.
// dow is the "day of week" that the rule starts.
// hour is the hour in local time that the rule starts (0-23).
// offset is the UTC offset in minutes for the time zone being defined.
// For convenience, the following symbolic names can be used:
//   week: First, Second, Third, Fourth, Last
//   dow: Sun, Mon, Tue, Wed, Thu, Fri, Sat
//   month: Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
//-----------------------------------------------------------------------------------------

// Timezone ukTZ(myDST, mySTD);
// TimeChangeRule *tcr;  // pointer to the time change rule, use to get TZ abbrev

//-------------------------------------------------------------------------------------------------------------

// Declare Variables

/*const int namelength = 24;  // (8 blanks) + (8 characters) + (8 blanks)
//Variables for scrolling name  //  "- JEFF -"   (36, 35, 17, 21, 20, 20, 35, 36) = eight characters
int nametext[namelength] = { 35, 35, 35, 35, 35, 35, 35, 35, 36, 35, 17, 21, 20, 20, 35, 36, 35, 35, 35, 35, 35, 35, 35, 35 };
int namescroll = 16;  // the number of scroll steps to get the name in and out again (# of characters in the message + 8 initial blanks)
*/

const int namelength = 32; // (8 blanks) + (16 characters) + (8 blanks)
// Variables for scrolling name  //  "- JEFFS SP-332 -"   (36, 35, 17, 21, 20, 20, 35, 36) = eight characters
int nametext[namelength] = {35, 35, 35, 35, 35, 35, 35, 35, 36, 35, 17, 21, 20, 20, 15, 35, 15, 25, 46, 3, 3, 2, 35, 36, 35, 35, 35, 35, 35, 35, 35, 35};
int namescroll = 24; // the number of scroll steps to get the name in and out again (# of characters in the message + 8 initial blanks)

int temphour = 0; // Used to set the hours in display routine based on 12 or 24 hour time

int hundredchar = 0; // variables used reading temp sensor
int hundredsreadtemp = 0;
int tensreadtemp = 0;
int unitsreadtemp = 0;
float readtemp = 0;
int tenthsreadtemp = 0;

int readpres = 0; // variables for reading pressure from the sensor
int unitsreadpres = 0;
int tensreadpres = 0;
int hundredsreadpres = 0;
int thousreadpres = 0;

// declare variables for timezone
time_t utc;
time_t local;

// Variables that define the decimal points in the displays.  There are 8.  These are reset after each nixieshift - "0"=off, "1"=on
// Need to define the bit set in void setallbits
int dpc1 = 0; // control for dp1 (Decimal Point Control) 1 (left decimal)
int dpc2 = 0; // control for dp2 (Decimal Point Control) 2 (second from left)
int dpc3 = 0; // control for dp3 (Decimal Point Control) 3 (third from left)
int dpc4 = 0; // control for dp4 (Decimal Point Control) 4 (fourth from left)
int dpc5 = 0; // control for dp5 (Decimal Point Control) 5 (fourth from right)
int dpc6 = 0; // control for dp6 (Decimal Point Control) 6 (third from right)
int dpc7 = 0; // control for dp6 (Decimal Point Control) 7 (second from right)
int dpc8 = 0; // control for dp6 (Decimal Point Control) 8 (right decimal)

/*PIN Mapping Guide - Just a note to remind me of the physical to numerical mapping of GPIO/s on a NodeMCU

  const uint8_t D0   = 16;    Spare - really? - now Allocated to PIR detection - initialise as input with Pullup - so it is permanently triggered when nothing is connected.
  const uint8_t D1   = 5;     SCL
  const uint8_t D2   = 4;     SDA
  const uint8_t D3   = 0;     neopixels
  const uint8_t D4   = 2;     pwmpin
  const uint8_t D5   = 14;    PIR - connect if desired
  const uint8_t D6   = 12;    datapin   to Shift Register
  const uint8_t D7   = 13;    clockpin  to Shift Register
  const uint8_t D8   = 15;    latchpin  to Shift Register
  const uint8_t D9   = 3;     Not Available on Wemos D1 (or is it?)
  const uint8_t D10  = 1;     Not Available on Wemos D1 (or is it?)

*/

// define debug - various testing purposes - generally writes more to the display
// #define debug

// define for display mode of time on 8 digits
// #define HH-MM-SS (changed all instances of HH-MM-SS to use HH_MM_SS.  Fixes compiler warning and autoformat issues)
#define HH_MM_SS

// define which drivers are being used - only use one of the following two #defines
// #define HV5622rev
// #define HV5522

// define which clock type - used for startup display and OTA setup SP352 or SP332
// #define SP332 - moved to platformio.ini
// #define SP352

// use this to control colon status when displaying time - set to 1 to display colons
bool colonstatus = false;

// PWM Output pin
int pwmPin = D5; // D5

// PIR Stuff
const int PIRpin = D4; // set to use D4

int clockStatus = HIGH;  // should the clock be on?
int PIRstatus = HIGH;    // used to record PIR Status - set to 1 if active - deals with situation where no PIR is connected
int prevPIRstatus = LOW; // This should force a change detected on first pass due to pullup if PIR not connected
int currentPIRstatus;    // used to see what the current status of the PIR pin is - if it never goes low (no PIR) then clock will never switch off.
int tubeStatus;          // used to determine whether tubes should be illuminated accorging to clockStatus and PIRstatus
unsigned long triggerTime;
unsigned long daydelay;
unsigned long nightdelay;
unsigned long delaytime; // used to store either daydelay or night delay - depending on the time of day.

// Vars used to compute IP Address for display on startup

int dpos1;
int dpos2;
int dpos3;
String ippart1;
String ippart2;
String ippart3;
String ippart4;

// var to pad seconds display in the last 10 seconds of the minute
int extraSecs;

// var to store the current second - used to see if second has changed and then display the updated time.
int oldSecond = 0;

// declare function to return nightMode
// Add code here to make it not night mode if the PIR has been tripped - ie PIRstatus goes to LOW
// With NO PIR connected, ClockStatus, PIRStatus and tubeStatus always = HIGH
// With PIR connected and PIR triggered then ClockStatus and tubeStatus are HIGH, PIRstatus is LOW
// With PIR connected and PIR not triggered then PIRstatus goes HIGH
// We want to say that, if it is nightmode the say so, unless PIR has been trigered.
//
// Also consider that, if night time hours are valid then PIR Timer should be set to a shorter time

boolean nightMode()
{
  if ((hour(local) < firsthour || hour(local) > lasthour) && PIRstatus == HIGH)
    return true;
  else
    return false;
}

// Another night mode function that is used to determine whether the PIR delay is (for example) 1 minute during night and 5 minutes during day.
boolean nightModeDelay()
{
  if (hour(local) < firsthour || hour(local) > lasthour)
    return true;
  else
    return false;
}

WiFiUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
time_t getNtpTime();
void sendNTPpacket(IPAddress &address);

Adafruit_BME280 bme; // I2C  Initialize both BMP and BME to detect the installed sensor
Adafruit_BMP280 bmp; // I2C  Initialize both BMP and BME to detect the installed sensor

int FoundSensor = 0;  // Variables for sensor detection
int FoundSensorP = 0; // Variables for sensor detection
int SensorType = 0;   // Variables for sensor detection

int foundBMP = 1; // set initially to 1  - gets set to 0 later if BMP is NOT found
uint8_t currentValue = 0;

// const byte DIpin = 13;  // MOSI, D7
// const byte CLpin = 14;  // SCK,  D5
const byte LEpin = 16; 

int latchPin = D0; //16;  //D0
int clockPin = D6; //12;  //D6
int dataPin =  D7; //13;  //D7
int blankpin = D5; //14;  //D5


unsigned char BitArray[64 / 8]; // space for 64 bits
unsigned char OldBitArray1[64 / 8]; // space for 64 bits - to be remembered for fade out purposes
unsigned char OldBitArray2[64 / 8]; // space for 64 bits - to be remembered for fade out purposes


// These variables are used to store the previous displayed number when performing a transition so that, if the number isn't changing then it keeps displaying the same number; otherwise it displays transitional number patterns
uint8_t olda = 0;
uint8_t oldb = 0;
uint8_t oldc = 0;
uint8_t oldd = 0;
uint8_t olde = 0;
uint8_t oldf = 0;
uint8_t oldg = 0;
uint8_t oldh = 0;

//
//
// variables used in stages of transtion
uint8_t stagea = 0;
uint8_t stageb = 0;
uint8_t stagec = 0;
uint8_t staged = 0;
uint8_t stagee = 0;
uint8_t stagef = 0;
uint8_t stageg = 0;
uint8_t stageh = 0;

//
// variable used to store current second - used in main loop to determine whether or not the displaytime should be called.
uint8_t storedsecond = 0;

// When calling nixieshift from void displaytime, remember to store the new digits as old ones at the end of performing the transitionv

// Modified below for Beckman SP-352 x 4 displays

// Going to have to re-think this because (I believe that) some digits are spread across the two shift registers - well that's bad planning!
// No Drama, re-organise into a single [64] element array and then have one set of pointers to all the 'bits' - then we shall just have to send all 64 bits out in one go!
#ifdef HV5522

#ifdef SP352 // actually now for 4 x B7971
int pointers[64] = { 38, 41, 40, 37, 46, 34, 45, 48, 42, 39, 43, 35, 44, 36, 47,
                     53, 56, 55, 52, 61, 49, 60, 63, 57, 54, 58, 50, 59, 51, 62,
                     12,  0, 14, 11,  5,  8,  4,  7,  1, 13,  2,  9,  3, 10,  6, 
                     27, 15, 29, 26, 20, 23, 19, 22, 16, 28, 17, 24, 18, 25, 21, 
                     30, 31, 32, 33};

//int fader[15] = {1, 8, 9, 2, 10, 3, 11, 12, 4, 13, 5, 14, 6, 7, 8};
int fader[15] = {0, 7, 8, 1, 9, 2, 10, 3, 14, 11, 12, 4, 13, 5, 6};

                    
#endif

// digits are          8   8   8   8   8   8   8   8   7   7   7   7   7   7   7   7   6   6   6   6   6   6   6   6   5   5   5   5   5   5   5   5
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                     0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  39  30  31

//                     4   4   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                    32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63



#ifdef SP332
int pointers[64] = {54, 53, 43, 41, 40, 55, 52, 42, 50, 49, 47, 45, 44, 51, 48, 46, 62, 61, 35, 33, 32, 63, 60, 34, 58, 57, 39, 37, 36, 59, 56, 38,
                    6, 5, 27, 25, 24, 7, 4, 26, 2, 1, 31, 29, 28, 3, 0, 30, 14, 13, 19, 17, 16, 15, 12, 18, 10, 9, 23, 21, 20, 11, 8, 22};
#endif

#endif
// digits are          8   8   8   8   8   8   8   8   7   7   7   7   7   7   7   7   6   6   6   6   6   6   6   6   5   5   5   5   5   5   5   5
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                     0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  39  30  31

//                     4   4   4   4   4   4   4   4   3   3   3   3   3   3   3   3   2   2   2   2   2   2   2   2   1   1   1   1   1   1   1   1
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                    32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63

// define an array of segments to build and then clear for cirlce thing
// For the 'outer ring' there are 8 top and bottom, 2 left and right so 20 in total - and yes, I know for 20 - we only need 19 (includes element '0') but - hey ho
// start top - right of centre - segment 'a' on 4th digit from right No.4 in above definition

int circle[20] = {32, 40, 48, 56, 57, 58, 59, 51, 43, 35, 27, 19, 11, 3, 4, 5, 0, 8, 16, 24};

// The data below has been tested by Nick Stock - he used HV5622's in palce HV5522's - took ages to work out what was going on but with many videos back and forth between UK and US - we think it is sorted!
// Phew!
#ifdef HV5622rev
int pointers[64] = {10, 11, 22, 21, 23, 8, 9, 20, 14, 15, 17, 18, 19, 12, 13, 16, 2, 0, 31, 30, 4, 3, 1, 29, 7, 57, 26, 28, 5, 6, 56, 27,
                    59, 58, 25, 24, 38, 61, 60, 39, 63, 33, 34, 36, 37, 62, 48, 35, 50, 52, 46, 47, 32, 49, 51, 45, 54, 55, 41, 43, 42, 53, 40, 44};
#endif

// digits are   (rev)  8   8   8   8   8   8   8   8   7   7   7   7   7   7   7   7   6   6   6   6   6   6   6   6   5   5   5   5   5   5   5   5
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                     0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  39  30  31

//                     4   4   4   4   4   4   4   4   3   3   3   3   3   3   3   3   2   2   2   2   2   2   2   2   1   1   1   1   1   1   1   1
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                    32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63

// digits are          1   1   1   1   1   1   1   1   2   2   2   2   2   2   2   2   3   3   3   3   3   3   3   3   4   4   4   4   4   4   4   4
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                     0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  39  30  31

//                     5   5   5   5   5   5   5   5   6   6   6   6   6   6   6   6   7   7   7   7   7   7   7   7   8   8   8   8   8   8   8   8
//                     A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P   A   B   C   D   E   F   G   P
//                    32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63

// int pointers[64] = {   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
//                       32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 ,53 ,54 ,55, 56, 57, 58, 59, 60, 61, 62, 63 };

// offsets in pointers are NOT 0,10,20

// Character shapes for digits 0-9 using 'standard' seven segment notation, Initially set to 10 parts for the digits 0-9, expanded to include 'Cdhnrst' for st, nd, rd, th and degrees C
// May increase this yet further if I decide to show month as Jan, Feb, March, April, May, June, July, August Sept Oct Nov Dec
// set blankdigit to number of available digits+1 - this is used to make no segments light
uint8_t blankdigit = 136;

// Segments
// GFEDCBA

int sevenseg[136] = {

// numbers 0-9 in positions 0-9 for hisoric reasons                  
0b111111001000100, // 0
0b011000001000000, // 1
0b100110001000010, // 2
0b101100001100000, // 3
0b011001000100010, // 4
0b100101000010010, // 5
0b101111000100010, // 6
0b100000001000100, // 7
0b111111000100010, // 8
0b111101000100010, // 9
0b000000000000000, //blank 10
0b000000000000000, //blank 11
0b000000000000000, //blank 12
0b000000000000000, //blank 13
0b000000000000000, //blank 14
0b000000000000000, //blank 15
0b000000000000000, //blank 10
0b000000000000000, //blank 11
0b000000000000000, //blank 12
0b000000000000000, //blank 13
0b000000000000000, //blank 14
0b000000000000000, //blank 15
0b000000000000000, //blank 16
0b000000000000000, //blank 17
0b000000000000000, //blank 18
0b000000000000000, //blank 19
0b000000000000000, //blank 1A
0b000000000000000, //blank 10b
0b000000000000000, //blank 1C
0b000000000000000, //blank 1D
0b000000000000000, //blank 1E
0b000000000000000, //blank 1F
0b000000000000000, //blank 20 - SPACE
0b011000000000000, // 21 !
0b011000000000000, // 22 " - not yet defined
0b000011010001010, // 23 #
0b011000000000000, // 24 $ - not yet defined
0b001001101110110, // 25 % - not yet defined
0b000011010001010, // 26 & - not yet defined
0b000000100000000, // 27 '
0b000000001010000, // 28 (
0b000000100000100, // 29 )
0b000000111111110, // 2A *
0b000000010101010, // 2B +
0b000000000000100, // 2C ,
0b000000000100010, // 2D -
0b000000000000001, // 2E . not yet defined properly - this is underscore
0b000000001000100, // 2F /
0b111111001000100, // 30 0
0b011000001000000, // 31 1
0b100110001000010, // 32 2
0b101100001100000, // 33 3
0b011001000100010, // 34 4
0b100101000010010, // 35 5
0b101111000100010, // 36 6
0b100000001000100, // 37 7
0b111111000100010, // 38 8
0b111101000100010, // 39 9
0b000000001010000, // 3A : - not yet defined
0b000000001010000, // 3B ; - not yet defined
0b000000001010000, // 3C <
0b000100000100010, // 3D =
0b000000100000100, // 3E >
0b110001000101000, // 3F ?
0b111110000001010, // 40 @
0b111011000100010, // 41 A
0b111100010101000, // 42 B
0b100111000000000, // 43 C
0b111100010001000, // 44 D
0b100111000000010, // 45 E
0b100011000000010, // 46 F
0b101111000100000, // 47 G
0b011011000100010, // 48 H
0b100100010001000, // 49 I
0b011110000000000, // 4A J
0b000000011011000, // 4B K
0b000111000000000, // 4C L
0b011011101000000, // 4D M
0b011011100010000, // 4E N
0b111111000000000, // 4F O
0b110011000100010, // 50 P
0b111111000010000, // 51 Q
0b100011001010010, // 52 R
0b101101000100010, // 53 S
0b100000010001000, // 54 T
0b011111000000000, // 55 U
0b000011001000100, // 56 V
0b011011000010100, // 57 W
0b000000101010100, // 58 X
0b000000101001000, // 59 Y
0b100100001000100, // 5A Z
0b100111000000000, // 5B [
0b000000100010000, // 5C "\"
0b111100000000000, // 5D ]
0b010000001000000, // 5E ^
0b000100000000000, // 5F _
0b000000010000001, // 60 ' - need to check
0b111110000100010, // 61 a
0b000111000010010, // 62 b
0b000110000100010, // 63 c
0b011100000100100, // 64 d
0b100111000000010, // 65 e
0b100011000000010, // 66 f
0b111100100100000, // 67 g
0b001011000100010, // 68 h
0b000000000001000, // 69 i
0b011100000000000, // 6A j
0b000000011011000, // 6B k
0b000000010001000, // 6C l
0b001010000101010, // 6D m
0b000010000010010, // 6E n
0b001110000100010, // 6F o
0b100011001000010, // 70 p
0b110001000110010, // 71 q
0b000000000101000, // 72 r
0b101100100100000, // 73 s
0b000111000000010, // 74 t
0b001110000000000, // 75 u
0b000010000000100, // 76 v
0b001010000010100, // 77 w
0b000000101010100, // 78 x
0b011100100100000, // 79 y
0b100100001000100, // 7A z
0b000000001010010, // 7B {
0b001000100000000, // 7C ! - to be tested
0b000000100100100, // 7D }



};

// Standard definitions of 7 segment display characters 0-9 plus Cdhnrst - as at 29/06/2020
// Added additional letters to make months 01/07/2020
// use value of maxdigits to set blank digits for now
//
// Months as follows
//  JAN     17,18,19
//  FEb     20,21,22
//  nnArch  13,13,18,14,24,12
//  APri1   18,25,14,26,30
//  nnAY    23,23,18,27
//  JUNE    17,28,19,21
//  JuLY    17,29,30,27
//  AuGuSt  18,29,31,29,5,16
//  5EPT    5,21,25,16
//  OCt     0,10,16
//  Nou     19,0,29
//  DEC     11,21,10
//

// these are used to calculate the st, nd, rd and th to add to the day number
uint8_t dayletter1 = 0;
uint8_t dayletter2 = 0;

// variables for diplaying certain items at certain times
int donedatetemp = 0; // have we displayed date or temp/pressure yet?

void setup()
{





  EEPROM.begin(2048);
  // config.setDebugPrint(&Serial);
  config.init();
  // clockConfig.debug(&Serial);
  clockConfig.get(); // Read all of the config values from EEPROM
  // clockConfig.debug(&Serial);

  // set up SPI
  //SPI.begin();
  //SPI.setFrequency(100000);
  //SPI.setBitOrder(MSBFIRST);
  //SPI.setDataMode(SPI_MODE1);

  // initialise serial comms
  Serial.begin(9600);
  Serial.println("");
  Serial.println("FLW (4) digit Clock");
  Serial.println("");
  Serial.println("Reads status of BMP/BME Connection and runs accordingly");
  Serial.println("Responds to PIR if connected");
  Serial.println("Supports OTA programming");
  Serial.println("");

  // Set up neopixels
   // this resets all the neopixels to an off state
    strip.Begin();
    strip.Show();

// display some colour for testing
    strip.SetPixelColor(0, red);
    strip.SetPixelColor(1, green);
    strip.SetPixelColor(2, blue);
    strip.SetPixelColor(3, hslWhite);
    strip.Show();

nonBlock(1000);

    strip.SetPixelColor(0, red);
    strip.SetPixelColor(1, red);
    strip.SetPixelColor(2, red);
    strip.SetPixelColor(3, red);
    strip.Show();



// eeprom raed testing
for(int readword=0; readword<10; readword++)
{
wordpointer = 0; // reset word pointer
//Now Read 4 bytes
  for(int address = (readword * 4); address< (readword*4+4); address++) {
  wordtext[wordpointer] = readEEPROM(eepromaddr, address);
  wordpointer++;
  }

// now print out contents of wordtext
Serial.print(wordtext[0]);
Serial.print(wordtext[1]);
Serial.print(wordtext[2]);
Serial.print(wordtext[3]);
Serial.println("");
}

Serial.println("Words Tested");

  // set outputs for SPI
  pinMode(LEpin, OUTPUT);

  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(blankpin, OUTPUT);



  // For PIR detection
  pinMode(PIRpin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIRpin), pirChange, RISING); // If something changes then some motion was detected - start the timer in the main loop

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

  // Call Display Test if desired!
  startup();
  //disptest();

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

#ifdef SP332

  // set tubes to display startup message SP-332 - now centered OK
  nixieshift(32, 83, 80, 45, 3, 3, 2, 32, 0);
  analogWrite(pwmPin, Maxbright);
  nonBlock(3000);
#endif

#ifdef SP352
  // set tubes to display startup message 7971 
  // now display 7971
  zerobits();
  nixieshift(7, 9, 7, 1,  0);
  analogWrite(pwmPin, Maxbright);
  nonBlock(3000);

#endif

  // Add call to startup display routine here:
  //   displaytest();
  //

  // now say 'AP' on the display and wait for connection - centred OK
  // nixieshift(35, 35, 35, 18, 25, 35, 35, 35, 0);   test - remove comment if fail

  // Routine to add moving decimal points to the word 'AP-CON' when establishing wi-fi connection
  // 'AP-COn__' with sweeping decimal points acts as progress bar
  // loop the decimal sweep in pairs - 9 times before switching to 'CONECT'
  // dp is temporary loop counter

  // set tubes to display Connected message ' AP '
  nixieshift(31, 65, 80, 32,  0);

  //nonBlock(2000); // wait a moment


  // WiFiManager
  // Local intialization. Once its business is done, there is no need to keep it around
  // WiFiManager wifiManager;
  AsyncWiFiManager wifiManager(&server, &dns); // changed from line above
// reset saved settings
// wifiManager.resetSettings();

// Connect to WiFi

// fetches ssid and pass from eeprom and tries to connect
// if it does not connect it starts an access point with the specified name
// here
// and goes into a blocking loop awaiting configuration
#ifdef SP332
  wifiManager.autoConnect("SP-332_x8_digit-ClockAP");
#endif
#ifdef SP352
  wifiManager.autoConnect("FLW_4_digit-ClockAP");
#endif

  // added delay to ensure that initial NTP request succeeds
  nonBlock(2000);

  createWebPages(server, &clockConfig, name2html);

  // set tubes to display Connected message 'CNCT'
  nixieshift(67, 78, 67, 84,  0);

  nonBlock(2000); // wait a moment

  Serial.println("Connected");
  Serial.print("IP number assigned by DHCP is ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());

  // set tubes to display IP address
  // use the .toString thing
  String ipstring = WiFi.localIP().toString();
  Serial.print("Wifi String = ");
  Serial.println(ipstring);
  Serial.println("");
  // next job, split the string into indiviadual octets so that the clock can display aaaa.bbbb then cccc.dddd so that the IP address is displayed for the user
  // ultimately this will be helpful in accessing the web config page - one day !
  // depends on whether the returned string is always nnn.nnn.nnn.nnn - or could it be nn.nnn.n.nn?
  // if the former then just pluck the groups of three digits out.
  // if the latter then get the positions of the three dots and the length of the string then divi it up based on that - see the code in Teensy for parsing the GPS string.
  // get positions of the three DP's in the string to break it up correctly
  // pad each octet to 'nnn' if not long enough
  // use nixieshift to send out the parts
  // need to add code to set the DP's on in the right place. Need DP on 3rd digit only

  dpos1 = ipstring.indexOf(".");
  dpos2 = ipstring.indexOf(".", dpos1 + 1);
  dpos3 = ipstring.indexOf(".", dpos2 + 1);
  /*
Serial.println(dpos1);
Serial.println(dpos2);
Serial.println(dpos3);
*/
  ippart1 = ipstring.substring(0, dpos1);
  ippart2 = ipstring.substring(dpos1 + 1, dpos2);
  ippart3 = ipstring.substring(dpos2 + 1, dpos3);
  ippart4 = ipstring.substring(dpos3 + 1, ipstring.length());
  /*
Serial.println(ippart1);
Serial.println(ippart2);
Serial.println(ippart3);
Serial.println(ippart4);
*/
  // Pad the strings if necessary, ie if octet was <100 or <10  it would need to be 099 or 009
  if (ippart1.length() < 3)
    ippart1 = "0" + ippart1;
  if (ippart1.length() < 3)
    ippart1 = "0" + ippart1;

  if (ippart2.length() < 3)
    ippart2 = "0" + ippart2;
  if (ippart2.length() < 3)
    ippart2 = "0" + ippart2;
  if (ippart3.length() < 3)
    ippart3 = "0" + ippart3;
  if (ippart3.length() < 3)
    ippart3 = "0" + ippart3;
  if (ippart4.length() < 3)
    ippart4 = "0" + ippart4;
  if (ippart4.length() < 3)
    ippart4 = "0" + ippart4;

  // use colon type 3 for this
  // need to modify setallbits to cope with the additional colon parts
  // put out 'IPAD' first

  nixieshift(73, 80, 65, 68,  0);
  nonBlock(1000);



// Display IP address in 4 separate blocks!

  nixieshift(ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(),32,0);
  nonBlock(1000);
  nixieshift(ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(),32,0);
  nonBlock(1000);
  nixieshift(ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(),32,0);
  nonBlock(1000);
  nixieshift(ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(),32,0);
  nonBlock(1000);

  /*
  //  Display IP address in two separate blocks
  nixieshift(35, ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 35, 3);
  nonBlock(750);
  nixieshift(35, ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 35, 3);
  nonBlock(1000);
*/
  // Display the IP Address as a scrolling display
  // Scroll IPAddr to left
/*
  nixieshift(32, 32, 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, 32, 32, 32, 32, 32, 32, ippart1.substring(0, 1).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, 32, 32, 32, 32, 32, ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, 32, 32, 32, 32, ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, 32, 32, 32, ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), 45, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, 32, 32, ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), 45, ippart2.substring(0, 1).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, 32, ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), 45, ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), 45, ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart1.substring(0, 1).toInt(), ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), 45, ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 45, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart1.substring(1, 2).toInt(), ippart1.substring(2, 3).toInt(), 45, ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 45, ippart3.substring(0, 1).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart1.substring(2, 3).toInt(), 45, ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 45, ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(45, ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 45, ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart2.substring(0, 1).toInt(), ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 45, ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), 45, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart2.substring(1, 2).toInt(), ippart2.substring(2, 3).toInt(), 45, ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), 45, ippart4.substring(0, 1).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart2.substring(2, 3).toInt(), 45, ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), 45, ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(45, ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), 45, ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart3.substring(0, 1).toInt(), ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), 45, ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart3.substring(1, 2).toInt(), ippart3.substring(2, 3).toInt(), 45, ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart3.substring(2, 3).toInt(), 45, ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 32, 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(45, ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 32, 32, 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart4.substring(0, 1).toInt(), ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 32, 32, 32, 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart4.substring(1, 2).toInt(), ippart4.substring(2, 3).toInt(), 32, 32, 32, 32, 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(ippart4.substring(2, 3).toInt(), 32, 32, 32, 32, 32, 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
  nixieshift(32, 32, 32, 32, 32, 32, 32, 32, 0);
  nonBlock(scrollspeed * 2.5);
*/
  // set the TimeSync thing
  setSyncProvider(getNtpTime);
  setSyncInterval(300);

  // Time to pause - to see if we can ensure that first getNTP returns good data

  Serial.begin(9600);
  while (!Serial)
    ; // time to get serial running
  Serial.println("");
  Serial.println("");
  Serial.print(("BMP280/BME280 test:  "));

  unsigned status;
  unsigned status1;

  status = bme.begin(0x76);  // status = bme.begin();
  status1 = bmp.begin(0x76); // status = bmp.begin();

  // Note:  BME returns 96 and BMP returns 88
  FoundSensorP = (bmp.sensorID());
  FoundSensor = (bme.sensorID());

  Serial.println(FoundSensor);
  if (FoundSensor == 88)
    Serial.print("Found BMP: ");
  if (FoundSensor == 96)
    Serial.print("Found BME: ");
  if (FoundSensor == 0)
    Serial.println("No Sensor");

  SensorType = 0; // for No sensor (default)
  if (FoundSensor == 88)
    SensorType = 1; // For BMP
  if (FoundSensor == 96)
    SensorType = 2; // For BME

  // Display Sensor Found Status here:
  //
  if (SensorType == 0)
  {
    // Display 'NoSE'
    nixieshift(78, 111, 83, 69, 0);
    nonBlock(750);
  }
  if (SensorType == 1)
  {
    // Display 'P-SE'
    nixieshift(80, 45, 83, 69, 0);
    nonBlock(750);
  }
  if (SensorType == 2)
  {
    // Display 'E-SE'
    nixieshift(69, 45, 83, 69,  0);
    nonBlock(750);
  }

  // Now we have a value in SensorType that shows which is fitted - or none fitted!
  Serial.print("SensorType is ");
  Serial.println(SensorType);
  Serial.println("");
  delay(1200);
  Serial.println("Read temperature: ");

  if (SensorType == 1)
  {
    // Display 'P-SenS'
    Serial.print(bmp.readTemperature());
    Serial.print(" Degrees C");
    Serial.println("");
    Serial.print((1.8 * (bmp.readTemperature()) + 32));
    Serial.print(" Degrees F");
    delay(1200);
    Serial.println("");
    Serial.println("");
    Serial.println("Read pressure: ");
    Serial.print(bmp.readPressure() / 100);
    Serial.println(" Millibars Pressure");
    Serial.print(((bmp.readPressure()) / 33.864) / 100);
    Serial.println(" inches Hg");
    Serial.println("");
    delay(1200);
  }
  if (SensorType == 2)
  {
    // Display 'E-Sens'
    Serial.print(bme.readTemperature());
    Serial.print(" Degrees C");
    Serial.println("");
    Serial.print((1.8 * (bme.readTemperature()) + 32));
    Serial.print(" Degrees F");
    delay(1200);
    Serial.println("");
    Serial.println("");
    Serial.println("Read humidity: ");
    Serial.print(bme.readHumidity());
    Serial.print("% Relative Humidity");
    Serial.println("");
    delay(1200);
    Serial.println("");
    Serial.println("Read pressure: ");
    Serial.print(bme.readPressure() / 100);
    Serial.println(" Millibars Pressure");
    Serial.print(((bme.readPressure()) / 33.864) / 100);
    Serial.println(" inches Hg");
    Serial.println("");
    delay(1200);
  }

  if (metricUnits == true)
  {                                                // #ifdef metricunits
    nixieshift(77, 101, 116, 114,  0); // Spells Metr
    nonBlock(1000);

  } // #endif

  else
  {                                                // #ifdef imperialunits
    nixieshift(85, 83, 117, 110,  0); // Spells USun
    nonBlock(1000);
  } // #endif

nixieshift(85, 110, 105, 116,  0); // Unit
    nonBlock(1000);
    

/* Default settings from datasheet. */
//  bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,     /* Operating Mode. */
//                  Adafruit_BMP280::SAMPLING_X2,     /* Temp. oversampling */
//                  Adafruit_BMP280::SAMPLING_X16,    /* Pressure oversampling */
//                  Adafruit_BMP280::FILTER_X16,      /* Filtering. */
//                  Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */

// OTA Added Startup Code
#ifdef SP332
  ArduinoOTA.setHostname("SP-332-8_digit");
#endif
#ifdef SP352
  ArduinoOTA.setHostname("SP-352-8_digit");
#endif

  // Set authentication for OTA
  ArduinoOTA.setPassword("nixology");
  ArduinoOTA.onStart([]()
                     {
    Serial.println("Start");
    // display '-UPLOAd-'
    nixieshift(85, 80, 76, 68, 0); });
  ArduinoOTA.onEnd([]()
                   { Serial.println("\nEnd"); });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
                        { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); });
  ArduinoOTA.onError([](ota_error_t error)
                     {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed"); });
  ArduinoOTA.begin();
  Serial.println("OTA Ready");
  Serial.print("OTA IP Address: ");
  Serial.println(WiFi.localIP());
  // End of OTA Section
}

unsigned long lastCommit = 0;

void loop()
{

  // update the LED's


    strip.SetPixelColor(0, red);
    strip.SetPixelColor(1, red);
    strip.SetPixelColor(2, red);
    strip.SetPixelColor(3, red);
    strip.Show();
  
  // Re-create web page in filing system if any config values were saved
  checkRewriteWebPage(&clockConfig, name2html);
  
  unsigned long now = millis();

  // The web code does a commit when the user saves values, so probably don't need this
  if (now - lastCommit > 60000)
  {
    lastCommit = now;
    // Any ConfigItems that had put() called will be written to EEPROM
    config.commit();
  }

  // Added OTA Functionality
  ArduinoOTA.handle();

  // Loop showing time (HH:MM:SS)
  // on (datedisplaysecs) seconds go to either date OR temp/pressure

  // work out the local DST corrected time - do this once per main loop cycle - check that displaytime and displaydate are not also doing it!
  setLocalTime();

  int tensmin = int(minute(local) / 10);
  int unitsmin = minute(local) - (tensmin * 10);

  int storesecs = second(local) % 60;
  int tenssec = int(storesecs / 10);
  int unitssec = storesecs - (tenssec * 10);

  // reset check vars at the start of the cycle, this is done because we want to display the data when the seconds are (datedisplaysecs) - this could be any time after (datedisplaysecs) seconds as the display routine takes time and will probably never hit dead on (datedisplaysecs) seconds
  // so if second(local)<8 then we know for sure that we can reset the variable

  if (second(local) < 8)
  {
    donedatetemp = 0;
#ifdef debug
    Serial.println("Reset donedatetemp");
#endif
  }

  if (second(local) != oldSecond)
  {
    oldSecond = second(local); // store the updated second
    displaytime();             // display the time - which will incur a small delay itself due to the digit transformations
  }

  // work out the local DST corrected time - this has to be done each time before using second(local) as it only updates the (local) versions when called.
  // setLocalTime(); // commented out because this has already been done at the top of the main loop

  if ((second(local) > (datedisplaysecs)) && (donedatetemp == 0))
  { // if second(local)> (datedisplaysecs) and we have not displayed it yet then lets do it

// Must decide which one to do - based on value if dispDate
//#ifdef debug
    Serial.print("dispDate=");
    Serial.println(dispDate);
    Serial.print("SensorType");
    Serial.println(SensorType);
//#endif

    if (dispDate == 0 && SensorType > 0)
    { // only display the temp and pressure if a sensor is attached  - ie foundBMP is true - otherwise - display date EVERY minute instead,
      // call routine to display temp
      displaytemp();
      nonBlock(1000);

      // call routine to display Humidity - if we have BME280 sensor
      if (SensorType == 2)
      { // For BME
        displayhumidity();
        nonBlock(1000);
      }

      // call routine to display pressure
      displaypressure();
      nonBlock(500); // This is a delay at the end of the pressure display
    }

    if (dispDate == 1)
      displaydate(); // call routine to display date
    if (dispDate == 2)
      displayname(); // call routine to display scrolling name

    // increment dispdate and reset to 0 if >2
    dispDate = dispDate + 1;
    if (dispDate > 2)
      dispDate = 0;

    // set var to say we've done it.
    donedatetemp = 1;

    // at this point it would be good if clock could sync up with the next second so that when the year or pressure has been displayed, the time that scrolls in is displayed for a whole second as right
    // now, once the date has been been displayed, the time that scrolls in canges immediately - it would be nicer if it hung around!
    // Perhps trick it into thinking that second(local) does equal oldSecond so that when it gets back to the top of the loop, the time is not displayed until the next second, Hmm, interesting....
    setLocalTime();
    oldSecond = second(local);
  }

  
  // Check to see it doing word display #1
  if ((second(local) > 15) && second(local) < 20)
  {
    displayword();
    setLocalTime();
    oldSecond = second(local); 
  }



  // Check to see it doing word display #2
  if ((second(local) > 45) && second(local) < 50)
  {
    displayword();
    setLocalTime();
    oldSecond = second(local); 
  }



  if (PIRstatus != prevPIRstatus)
  {                            // Change detected, start the timer
    triggerTime = millis();    // note the time
    prevPIRstatus = PIRstatus; // remember the previous status so that the timer will not be restarted next time around
    clockStatus = HIGH;        // make the clock ON!
    Serial.println("Trigger"); // A trigger was received
  }

  // Now check to see if on timer has expired AND the PIR input pin is low (if PIR is connected then it will be driven low.
  currentPIRstatus = digitalRead(PIRpin);

  // Set the delaytime to the normal day time delay and then change it if it is night time
  daydelay = DayclockONtime * 60 * 1000;
  nightdelay = NightclockONtime * 60 * 1000;
  delaytime = daydelay;
  if (nightModeDelay())
    delaytime = nightdelay;

  if ((millis() - triggerTime >= delaytime) && currentPIRstatus == LOW)
  {                    // ie - delay time has been reached AND the PIR has gone low again then we can turn the clock off
    clockStatus = LOW; // Set the clock off
    PIRstatus = HIGH;  // reset PIR status and prevPIR status so that the timer is not retriggered
    prevPIRstatus = HIGH;
    Serial.println("Clock Turned Off");
    dimtube();
  }

  // now wait for ???one??? second before checking again, this is so the colons change status once a second or thereabouts
  // nonBlock(579); // commented out as I want to switch over to ONLY calling displaytime if the second(local) has changed.
  // end of display loop
}

// Subroutines - it's an age thing!

void dimtube()
{
  // dim the light
  // now need to check that hour is within range of start hour to end hour
  pirTubes(); // work out whether to display anything according to PIR readings
  // work out the local DST corrected time
  // setLocalTime();

  if (!nightMode() && tubeStatus == HIGH)
  {
    for (int pwmval = 0; pwmval <= Maxbright; pwmval = pwmval + 2)
    {
      yield();
      analogWrite(pwmPin, Maxbright - pwmval);

      delay(2);
    }
    // wait a mo
    nonBlock(200);
  }
  else
    analogWrite(pwmPin, 0); // set to max for good measure - also - just in case clock is switched on during 'off' times - the value may never have been set - It is now!
}

void brighttube()
{
  // raise the light value
  // now need to check that its daytime (ie. !nightMode)
  // work out the local DST corrected time
  // Add a reference to PIRstatus, if PIRstatus goes low - PIR has been activated and clock should show digits / if PIR status is high then do not  show digits
  pirTubes(); // work out whether to display anything according to PIR readings

  // setLocalTime();

  if (!nightMode() && tubeStatus == HIGH)
  {
    for (int pwmval = 0; pwmval <= Maxbright; pwmval = pwmval + 2)
    {
      yield();
      analogWrite(pwmPin, pwmval);
      delay(2);
    }
  }
}

// turn on instantly - used for seconds display start - to avoid delay of brighttube loop
void tubeon()
{
  pirTubes(); // work out whether to display anything according to PIR readings
  // work out the local DST corrected time
  // setLocalTime();

  if (!nightMode() && tubeStatus == HIGH)
    analogWrite(pwmPin, Maxbright); // Turn on instantly during the day time
}

// This is taken from previous clock code and now needs to set 8 digits as well as any DP status etc/

void nixieshift(uint8_t d, uint8_t c, uint8_t b, uint8_t a, uint8_t dpstatus)
{



  // Set all bits - and set colon status as required on, off, degrees
setAllBits(a, b, c, d,  0, 0, 0, 0);
  /*
  if (dpstatus == 0)
    setAllBits(a, b, c, d,  0, 0, 0, 0);
  if (dpstatus == 1)
    setAllBits(a, b, c, d,  1, 1, 1, 1);

  if (dpstatus == 2)
    setAllBits(a, b, c, d, 1, 0, 0, 0);
  // this one for IP address display, need third digit DP on

  // Need to change setAllBits to deal with those digits
  if (dpstatus == 3)
    setAllBits(a, b, c, d, 0, 0, 0, 0);

  // this one for the display test, light all DP's
  if (dpstatus == 9)
    setAllBits(a, b, c, d, 1, 1, 1, 1);

  // Individual decimal points within the Nixieshift routine
  if (dpstatus == 11)
    setAllBits(a, b, c, d,  0, 0, 0, 1); // far right
  if (dpstatus == 12)
    setAllBits(a, b, c, d, 0, 0, 1, 0); // 2nd from right
  if (dpstatus == 13)
    setAllBits(a, b, c, d,  0, 1, 0, 0); // 3rd from right
  if (dpstatus == 14)
    setAllBits(a, b, c, d,  1, 0, 0, 0); // 4th from right
  */
  // SPI transfer code
  bitbashTX();
  zerobits();
}


void displaypressure()
{

  if (metricUnits == true)
  { // #ifdef metricunits
    // now calculate the pressure, we need 4 digits.
    // pressure will always be between 900 and 1500
    if (SensorType == 1)
    {
      readpres = (bmp.readPressure() / 100) + PressOffset; // useful to add offset to match Accuweather or known source.  Remember elevation!
    }
    if (SensorType == 2)
    {
      readpres = (bme.readPressure() / 100) + PressOffset;// useful to add offset to match Accuweather or known source.  Remember elevation!
    }
    // now convert to inches of Hg x 100 - that way we can get the 4 digits and then display as XX.XX

#ifdef debug
    Serial.print("Just read pressure ");
    Serial.println(readpres);
#endif

    // Get the thousands digit
    int thousreadpres = int(readpres / 1000);

    // Get the hundreds digit - take the total, subtract the number of thousands then divide the result by a 100 and take the int
    int hundredsreadpres = int((readpres - (thousreadpres * 1000)) / 100);

    // Get the tens digit
    int tensreadpres = int((readpres - (thousreadpres * 1000) - (hundredsreadpres * 100)) / 10);

    // Get the units digit
    int unitsreadpres = readpres - (thousreadpres * 1000) - (hundredsreadpres * 100) - (tensreadpres * 10);

    if (thousreadpres < 1)
      thousreadpres = blankdigit; // blank leading "0"

    // display the Pressure
    dimtube();
    //  110,98 is for nb if reading pascal;  72,103 is for Hg inches of mercury;
    nixieshift(thousreadpres, hundredsreadpres, tensreadpres, unitsreadpres,  0); // Sending blankdigit causes the display to be blank
    brighttube();

    nonBlock(1000);
    // display ' mb '
    nixieshift(32, 109,98,32,0);
    nonBlock(1000);
    
    // consider scrolling the pressure out to the left!
    // move pressure one to the left and wait 200ms, repeat 5 times
    // PPPPPP
    // PPPPP.
    // PPP...
    // PP....
    // P.....
    // ......
/*
    nixieshift(thousreadpres, hundredsreadpres, tensreadpres, unitsreadpres, blankdigit, 110, 98, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(hundredsreadpres, tensreadpres, unitsreadpres, blankdigit, 110, 98, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(tensreadpres, unitsreadpres, blankdigit, 110, 98, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(unitsreadpres, blankdigit, 110, 98, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(blankdigit, 110, 98, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(110, 98, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(98, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nixieshift(blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
*/
    // consider setting the 'remembered' time variables so that the next nixieshift routine will transition in from blank displays!
    olda = blankdigit;
    oldb = blankdigit;
    oldc = blankdigit;
    oldd = blankdigit;
    olde = blankdigit;
    oldf = blankdigit;
    oldg = blankdigit;
    oldh = blankdigit;

  } // endif

  else
  { // #ifdef imperialunits
    // now calculate the pressure, we need 4 digits.

    if (SensorType == 1)
    {
      readpres = (bmp.readPressure()) / 100;
      readpres = ((readpres / 33.864 * 100) + PressOffset*100); // useful to add offset to match Accuweather or known source.  Remember elevation!
    }
    if (SensorType == 2)
    {
      readpres = (bme.readPressure()) / 100;
      readpres = ((readpres / 33.864 * 100) + PressOffset*100); // useful to add offset to match Accuweather or known source.  Remember Elevation!
    }
    // now convert to inches of Hg x 100 - that way we can get the 4 digits and then display as XX.XX
    // based on mb / 33.864 gives HG,  do that then multiply by 100.
    // the inHgoffset allows calibrating the result directly in inches of Hg instead of Pascal mb

#ifdef debug
    Serial.print("Just read pressure ");
    Serial.println(readpres);
#endif

    // Get the thousands digit
    int thousreadpres = int(readpres / 1000);

    // Get the hundreds digit - take the total, subtract the number of thousands then divide the result by a 100 and take the int
    int hundredsreadpres = int((readpres - (thousreadpres * 1000)) / 100);

    // Get the tens digit
    int tensreadpres = int((readpres - (thousreadpres * 1000) - (hundredsreadpres * 100)) / 10);

    // Get the units digit
    int unitsreadpres = readpres - (thousreadpres * 1000) - (hundredsreadpres * 100) - (tensreadpres * 10);

    // display the Pressure
    dimtube();
    //  110, 98 is for nb if reading pascal;  72,103, is for Hg inches of mercury;
    nixieshift( thousreadpres, hundredsreadpres, tensreadpres, unitsreadpres, 16); // Sending blankdigit causes the display to be blank - 16 is 3rd from left dp
    brighttube();

    nonBlock(1000);
   // display 'INhg'
    nixieshift(73, 110,72,103,0);
    nonBlock(1000);
    
    // consider scrolling the pressure out to the left!
    // move pressure one to the left and wait 200ms, repeat 5 times
    // PPPPPP
    // PPPPP.
    // PPP...
    // PP....
    // P.....
    // ......
/*
    nixieshift(thousreadpres, hundredsreadpres, tensreadpres, unitsreadpres, blankdigit, 72, 103, blankdigit, 17);
    nonBlock(scrollspeed);
    nixieshift(hundredsreadpres, tensreadpres, unitsreadpres, blankdigit, 72, 103, blankdigit, blankdigit, 18);
    nonBlock(scrollspeed);
    nixieshift(tensreadpres, unitsreadpres, blankdigit, 72, 103, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(unitsreadpres, blankdigit, 72, 103, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(blankdigit, 72, 103, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(72, 103, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(scrollspeed);
    nixieshift(103, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nixieshift(blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
*/
    // consider setting the 'remembered' time variables so that the next nixieshift routine will transition in from blank displays!
    olda = blankdigit;
    oldb = blankdigit;
    oldc = blankdigit;
    oldd = blankdigit;
    olde = blankdigit;
    oldf = blankdigit;
    oldg = blankdigit;
    oldh = blankdigit;
  }
} // #endif

void displaytemp()
{
  // read temp and get int of temp and decimal of temp and display

  if (metricUnits == true)
  {
    // #ifdef metricunits  //for Metric Display

    if (SensorType == 1)
    {
      readtemp = (bmp.readTemperature() + TempOffset); // Celcius with CelciusOffset
      hundredsreadtemp = int(readtemp / 100);
      tensreadtemp = int(readtemp / 10);
      unitsreadtemp = int(readtemp - (tensreadtemp * 10));
      tenthsreadtemp = int((readtemp - (tensreadtemp * 10) - (unitsreadtemp)) * 10);

      // hundredsreadtemp will generally be 0, sometimes 1 if it is really hot and the air conditioning is broken
      // set the variable for the hundredchar to be either a '1' or 'blank'

      if (hundredsreadtemp < 1)
        hundredchar = blankdigit;
      if (hundredsreadtemp > 0)
        hundredchar = 1;
    }
    if (SensorType == 2)
    {
      readtemp = (bme.readTemperature() + TempOffset); // Celcius with CelciusOffset
      hundredsreadtemp = int(readtemp / 100);
      tensreadtemp = int(readtemp / 10);
      unitsreadtemp = int(readtemp - (tensreadtemp * 10));
      tenthsreadtemp = int((readtemp - (tensreadtemp * 10) - (unitsreadtemp)) * 10);

      // hundredsreadtemp will generally be 0, sometimes 1 if it is really hot and the air conditioning is broken
      // set the variable for the hundredchar to be either a '1' or 'blank'

      if (hundredsreadtemp < 1)
        hundredchar = blankdigit;
      if (hundredsreadtemp > 0)
        hundredchar = 1;
    }

    dimtube();

    // sending a '67' should display a capital "C" for degrees
    // sending a '70' should display a capital "F" for degrees
    //  select decimal position with last digit in nixieshift.  15 is the 4th from left
    nixieshift(tensreadtemp, unitsreadtemp, blankdigit, 67,  0);
    brighttube();
  } // #endif

  else
  { // #ifdef imperialunits  //for USA Display
    // int readtemp = (bmp.readTemperature() + CelciusOffset);  //Celcius with CelciusOffset

    if (SensorType == 1)
    {
      readtemp = 1.8 * (bmp.readTemperature()) + 32 + TempOffset; // converts C to F and uses offset for Fahrenheit
      hundredsreadtemp = int(readtemp / 100);
      tensreadtemp = int(readtemp / 10);
      unitsreadtemp = int(readtemp - (tensreadtemp * 10));
      tenthsreadtemp = int((readtemp - (tensreadtemp * 10) - (unitsreadtemp)) * 10);

      // hundredsreadtemp will generally be 0, sometimes 1 if it is really hot and the air conditioning is broken
      // set the variable for the hundredchar to be either a '1' or 'blank'

      if (hundredsreadtemp < 1)
        hundredchar = blankdigit;
      if (hundredsreadtemp > 0)
        hundredchar = 1;
    }
    if (SensorType == 2)
    {
      readtemp = 1.8 * (bme.readTemperature()) + 32 + TempOffset; // converts C to F and uses offset for Fahrenheit
      hundredsreadtemp = int(readtemp / 100);
      tensreadtemp = int(readtemp / 10);
      unitsreadtemp = int(readtemp - (tensreadtemp * 10));
      tenthsreadtemp = int((readtemp - (tensreadtemp * 10) - (unitsreadtemp)) * 10);

      // hundredsreadtemp will generally be 0, sometimes 1 if it is really hot and the air conditioning is broken
      // set the variable for the hundredchar to be either a '1' or 'blank'

      if (hundredsreadtemp < 1)
        hundredchar = blankdigit;
      if (hundredsreadtemp > 0)
        hundredchar = 1;
    }

    dimtube();

    // sending a '10' should display a capital "C" for degrees
    // sending a '20' should display a capital "F" for degrees
    //  select decimal position with last digit in nixieshift.  15 is the 4th from left
    nixieshift( tensreadtemp, unitsreadtemp, blankdigit, 70, 0);
    brighttube();
  } // #endif
}

// Do not run the void displayhumidity for BMP280.  Only works with BME280
void displayhumidity()
{
  if (SensorType == 0)
  {
    nonBlock(0);
  }
  if (SensorType == 1)
  {
    nonBlock(0);
  }
  if (SensorType == 2)
  {
    // read humidity and display
    int readhumidity = bme.readHumidity();
    readhumidity = (readhumidity + humidityoffset); // use humidity offset to calibrate sensor
    // routine to make sure that the offset does not cause an invalid result
    if (readhumidity > 99)
      readhumidity = 99;
    if (readhumidity < 0)
      readhumidity = 0;
    int tenshumidity = int(readhumidity / 10);
    int unitshumidity = readhumidity - (tenshumidity * 10);

    dimtube();
    nixieshift(tenshumidity, unitshumidity,  blankdigit, 37, 0); // 114=r, 104=h, 0=no colons
    brighttube();
  }
}

void displaytime()
{

  // work out the local DST corrected time - do this again in case minutes changed whilst hours was being displayed.
  // setLocalTime(); already done just before we got here
  // Display hours

  // dimtube();

  // check to see if 12/24 hours and set hours accordingly - set temp variable for interim steps
  temphour = hour(local);
  // Check the initial setting to make sure that the timesystem is either "0" or "12" and not an wrong value
  if (timesystem > 0)
    timesystem = 12;
  // if the clock thinks it is 13 hundred hours or later and clock is set for 12 hour time, set the temphour variable to 12 hour time
  if (timesystem > 0 && temphour > 12)
    temphour = (temphour - timesystem);
  // if the clock thinks it is 00 hours and clock is set for 12 hour time, set the temphour variable to 12
  if (timesystem > 0 && temphour == 0)
    temphour = 12;

  int tenshour = int(temphour / 10);
  int unitshour = temphour - (tenshour * 10);
  int tensmin = int(minute(local) / 10);
  int unitsmin = minute(local) - (tensmin * 10);
  int storesecs = second(local) % 60;
  int tenssec = int(storesecs / 10);
  int unitssec = storesecs - (tenssec * 10);

  // blank the leading zero if desired
  if (!leadingzero && tenshour == 0)
    tenshour = blankdigit;

  tubeon();
  //  nixieshift(tenshour, unitshour, tensmin, unitsmin, tenssec, unitssec, colonstatus);

#ifdef HHMMSS
  nixieshift(tenshour, unitshour, tensmin, unitsmin,  colonstatus);
#endif
// Here I Am
#ifdef HH_MM_SS
  nixieshift(tensmin, unitsmin,  tenssec, unitssec,  colonstatus);
#endif

  // toggle colon status
  colonstatus = !colonstatus;
}

// Display the date
void displaydate()
{

  // work out the local DST corrected time
  //   setLocalTime(); // no need - it's done in the main llop

  // Display Day and Month
#ifdef debug
  Serial.print("Day=");
  Serial.print(day(local));
  Serial.print("   ");
  Serial.print("Month=");
  Serial.print(month(local));
  Serial.print("   ");
#endif

  dimtube();
  int tensday = int(day(local) / 10);
  int unitsday = day(local) - (tensday * 10);
  int tensmonth = int(month(local) / 10);
  int unitsmonth = month(local) - (tensmonth * 10);

  // Display Day
  //  use numbers to display the two letters following the numeric date as follows:
  //
  //   B0111001, C  10
  //   B1011110, d  100
  //   B1110100, h  104
  //   B1010100, n  110
  //   B1010000, r  114
  //   B1101101, s  83
  //   B1110000  t  116
  //   Cdhnrst

  if (ordinals == true)
  {

    // Preset the two digits to be 'th'
    dayletter1 = 116;
    dayletter2 = 104;
    // now modify for the special cases
    // deal with 'st' for the 1st,21st and 31st - but not the 11th!
    if (day(local) == 1 || day(local) == 21 || day(local) == 31)
    {
      dayletter1 = 83;
      dayletter2 = 116;
    }

    // deal with 'nd' for the 2nd and 22nd  - but not the 12th!
    if (day(local) == 2 || day(local) == 22)
    {
      dayletter1 = 110;
      dayletter2 = 100;
    }

    // deal with 'rd' for the 3rd and 23rd - but not the 13th!
    if (day(local) == 3 || day(local) == 23)
    {
      dayletter1 = 114;
      dayletter2 = 100;
    }
  }
  else
  { // ie - do not display ordinals
    dayletter1 = 125;
    dayletter2 = 125;
  }

  // do not display tensday if it is '0' - ie blank leading 0 on day number!
  if (tensday == 0)
    tensday = blankdigit;

  if (metricUnits == true)
  { // #ifdef metricunits  // ****************Use DDdd-MMMM-YYYY format on  separate lines******************************

    nixieshift(tensday, unitsday,  dayletter1, dayletter2,  0);

    brighttube();
    nonBlock(1000);

// Display Month
#ifdef debug
    Serial.print("Month=");
    Serial.print(month(local));
    Serial.print("   ");
#endif

    dimtube();
    //  JAN     74, 65, 78
    //  FEb     70, 69, 98
    //  nnArch  110, 110, 65, 114, 99, 104
    //  APri1   65, 80, 114, 105, 108
    //  nnAY    110, 110, 65, 89
    //  JUNE    74, 85, 78, 69
    //  JuLY    74, 117, 76, 89
    //  AuGuSt  65, 117, 71, 117, 83, 116
    //  5EPT    83, 69, 80, 116
    //  OCt     79, 67, 116
    //  Nou     78, 79, 118
    //  DEC     68, 69, 67
    //

    //  nixieshift(blankdigit, tensmonth, unitsmonth, blankdigit,blankdigit,blankdigit,0);
    switch (month(local))
    {
    case 1:
      nixieshift( 74, 65, 78,  blankdigit, 0);
      break;
    case 2:
      nixieshift( 70, 69, 98, blankdigit, 0);
      break;
    case 3:
      nixieshift(110, 110, 65, 114, 0);
      break;
    case 4:
      nixieshift( 65, 80, 114, 105, 0);
      break;
    case 5:
      nixieshift( 110, 110, 65, 89,  0);
      break;
    case 6:
      nixieshift( 74, 85, 78, 69, 0);
      break;
    case 7:
      nixieshift(74, 117, 76, 89,  0);
      break;
    case 8:
      nixieshift(65, 117, 71, 117, 0);
      break;
    case 9:
      nixieshift( 83, 69, 80, 116,  0);
      break;
    case 10:
      nixieshift(79, 67, 116, blankdigit, 0);
      break;
    case 11:
      nixieshift(78, 79, 118, blankdigit, 0);
      break;
    case 12:
      nixieshift( 68, 69, 67, blankdigit, 0);
      break;
    }

    brighttube();
    nonBlock(1000);

    // Display Year

    int thisyear = year(local);
#ifdef debug
    Serial.print("Year =  ");
    Serial.print(thisyear);
    Serial.print("   ");
#endif

    // Get the thousands digit
    int thousyear = int(thisyear / 1000);

    // Get the hundreds digit - take the total, subtract the number of thousands then divide the result by a 100 and take the int
    int hundredsyear = int((thisyear - (thousyear * 1000)) / 100);

    // Get the tens digit
    int tensyear = int((thisyear - (thousyear * 1000) - (hundredsyear * 100)) / 10);

    // Get the units digit
    int unitsyear = thisyear - (thousyear * 1000) - (hundredsyear * 100) - (tensyear * 10);

    // display the year (four digits)
    dimtube();

    nixieshift(thousyear, hundredsyear, tensyear, unitsyear,  0);
    brighttube();
    nonBlock(1000);

    // consider scrolling the year out to the left!
    // move year one to the left and wait 200ms, repeat 6 times
    // ..YYYY..

    // .YYYY...
    // .YYY....
    // .YY.....
    // .Y......
    // ........
/*
    nixieshift(blankdigit, thousyear, hundredsyear, tensyear, unitsyear, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(hundredsyear, tensyear, unitsyear, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(tensyear, unitsyear, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(unitsyear, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
*/
  } // #endif

  else
  { // #ifdef imperialunits  // ****************Use MMMM-DDdd-YYYY format on  separate lines******************************

// Display Month First -  MMMM-DDdd-YYYY
#ifdef debug
    Serial.print("Month=");
    Serial.print(month(local));
    Serial.print("   ");
#endif

    // dimtube();
    //  JAN     17,18,19            74, 65, 78
    //  FEb     20,21,22            70, 69, 98
    //  nnArch  13,13,18,14,24,12   110, 110, 65, 114, 99, 104
    //  APri1   18,25,14,26,30      65, 80, 114, 105, 108
    //  nnAY    23,23,18,27         110, 110, 65, 89
    //  JUNE    17,28,19,21         74, 85, 78, 69
    //  JuLY    17,29,30,27         74, 117, 76, 89
    //  AuGuSt  18,29,31,29,5,16    65, 117, 71, 117, 83, 116
    //  5EPT    5,21,25,16          83, 69, 80, 116
    //  OCt     0,10,16             79, 67, 116
    //  Nou     19,0,29             78, 79, 118
    //  DEC     11,21,10            68, 69, 67
    //

    nixieshift(blankdigit, blankdigit, blankdigit, blankdigit, 0);

    switch (month(local))
    {
    case 1:
      nixieshift(74, 65, 78, blankdigit, 0);
      break;
    case 2:
      nixieshift(70, 69, 98, blankdigit,  0);
      break;
    case 3:
      nixieshift( 110, 110, 65, 114,  0);
      break;
    case 4:
      nixieshift( 65, 80, 114, 105, 0);
      break;
    case 5:
      nixieshift(110, 110, 65, 89,  0);
      break;
    case 6:
      nixieshift(74, 85, 78, 69, 0);
      break;
    case 7:
      nixieshift(74, 117, 76, 89,  0);
      break;
    case 8:
      nixieshift( 65, 117, 71, 117,  0);
      break;
    case 9:
      nixieshift( 83, 69, 80, 116,  0);
      break;
    case 10:
      nixieshift( 79, 67, 116, blankdigit,  0);
      break;
    case 11:
      nixieshift( 78, 79, 118, blankdigit, 0);
      break;
    case 12:
      nixieshift(68, 69, 67, blankdigit,  0);
      break;
    }

    brighttube();
    nonBlock(1000);

    // Display the Day
    dimtube();
    nixieshift(tensday, unitsday,dayletter1, dayletter2,  0);

    brighttube();
    nonBlock(1000);

    // Display Year

    int thisyear = year(local);
#ifdef debug
    Serial.print("Year =  ");
    Serial.print(thisyear);
    Serial.print("   ");
#endif

    // Get the thousands digit
    int thousyear = int(thisyear / 1000);

    // Get the hundreds digit - take the total, subtract the number of thousands then divide the result by a 100 and take the int
    int hundredsyear = int((thisyear - (thousyear * 1000)) / 100);

    // Get the tens digit
    int tensyear = int((thisyear - (thousyear * 1000) - (hundredsyear * 100)) / 10);

    // Get the units digit
    int unitsyear = thisyear - (thousyear * 1000) - (hundredsyear * 100) - (tensyear * 10);

    // display the year (four digits)
    //  consider scrolling the year out to the left!
    //  move year one to the left and wait 200ms, repeat 6 times
    //  ..YYYY..

    // .YYYY...
    // .YYY....
    // .YY.....
    // .Y......
    // ........

    dimtube();
/*
    nixieshift(blankdigit, blankdigit, thousyear, hundredsyear, tensyear, unitsyear, blankdigit, blankdigit, 0);
    brighttube();
    nonBlock(1000);
    nixieshift(blankdigit, thousyear, hundredsyear, tensyear, unitsyear, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(hundredsyear, tensyear, unitsyear, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(tensyear, unitsyear, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(unitsyear, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    nonBlock(100);
    nixieshift(blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);
    
*/
    // brighttube();
    tubeon();
  } // #endif

  // consider setting the 'remembered' time variables so that the next nixieshift routine will transition in from blank displays!
  olda = blankdigit;
  oldb = blankdigit;
  oldc = blankdigit;
  oldd = blankdigit;
  olde = blankdigit;
  oldf = blankdigit;
  oldg = blankdigit;
  oldh = blankdigit;
}

// This is a non-blocking 'delay' - use instead of 'delay' to avoid ESP8266 WDT resets
void nonBlock(unsigned long delay_time)
{
  unsigned long time_now = millis();
  while (millis() - time_now < delay_time)
  {
    yield();
    ESP.wdtFeed();
    // waiting!
  }
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48;     // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0)
    ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500)
  {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE)
    {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 = (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + clockTuner;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // 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
  // 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
  // you 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();
}

// work out the local DST corrected time
void setLocalTime()
{
  utc = now();
  TimeChangeRule *tcr; // pointer to the time change rule, use to get the TZ abbrev
  local = timeZones.at(timezone).toLocal(utc, &tcr);
}

ICACHE_RAM_ATTR void pirChange()
{
  PIRstatus = LOW;
}

// work out whether tubes should be on or off depending on the values of PIRstatus and clockStatus
/*
   This is derived from the following truth table which will deal with NO PIR connected or PIR connected and clock at rest or if it has been triggered both immediately and then after the current cycle has been completed
                clockStatus   PIRstatus     Show Tubes
   NO PIR           1             1               Y
   PIR and off      0             1               N     Clock is at rest and should not show anything
   PIR and on       1             0               Y     Clock was recently triggered and is counting down the on time
   PIR and Trigg    0             0               Y     Clock is mid way through showing something but has just been triggered (not yest set clockStatus)
*/

void pirTubes()
{
  if (clockStatus == LOW && PIRstatus == HIGH)
    tubeStatus = LOW;
  else
    tubeStatus = HIGH;
}

// Taken originally from the SP-151 code, now change to handle six sigits, set digit to 11 in order to leave blank - now using variable blankdigit
//  Start with six digits P8, P7, P6, P5, P4, P3, P2 and P1
//  Add DP's later
//
// Set all bits for the six numbers (a,b,c,d,e,f, g, h left to right), the decimal points (DP) for each digit on SP-352
// There are offsets at 0,8,16,24,32,40,48 and 56 as each digit has 7 segments plus a DP - 8 bits in total

void setAllBits(uint8_t aa, uint8_t bb, uint8_t cc, uint8_t dd,  uint8_t DP1, uint8_t DP2, uint8_t DP3, uint8_t DP4)
{

  // Old Comment - left here just in case: Load L bits for numbers dd,ee and ff ON THE LEFT - shifted out first
  // Load the bits using bitArrayWrite(pointers[thebittoset],1);

/*
Serial.print(aa);
Serial.print(" ");
Serial.print(bb);
Serial.print(" ");
Serial.print(cc);
Serial.print(" ");
Serial.println(dd);

*/
  // Set bits for dd Tens of hours
  if (dd < blankdigit)
  {

    for (uint8_t bitpointer = 0; bitpointer < 15; bitpointer++)
    {
     // if(bitRead(sevenseg[dd], (14-bitpointer)) ==1) Serial.print("1");
     // else Serial.print("0");
     
      if (bitRead(sevenseg[dd], (14-bitpointer)) == 1)
        bitArrayWrite(pointers[0 + bitpointer], 1);
    }
  }
//Serial.println("");

  // Set bits for cc  hours
  if (cc < blankdigit)
  {

    for (uint8_t bitpointer = 0; bitpointer < 15; bitpointer++)
    {
     // if(bitRead(sevenseg[dd], (14-bitpointer)) ==1) Serial.print("1");
     // else Serial.print("0");
     
      if (bitRead(sevenseg[cc], (14-bitpointer)) == 1)
        bitArrayWrite(pointers[15 + bitpointer], 1);
    }

    }

  // Set bits for bb tens of minutes
  if (bb < blankdigit)
  {
    for (uint8_t bitpointer = 0; bitpointer < 15; bitpointer++)
    {
      if (bitRead(sevenseg[bb], (14-bitpointer)) == 1)
        bitArrayWrite(pointers[30 + bitpointer], 1);
    }
  }

  // Set bits for aa minutes
  if (aa < blankdigit)
  {
    for (uint8_t bitpointer = 0; bitpointer < 15; bitpointer++)
    {
      if (bitRead(sevenseg[aa], (14-bitpointer)) == 1)
        bitArrayWrite(pointers[45 + bitpointer], 1);
    }
  }

  // Set bits for CTL, CBL, CTR, CBR as required
  // Now modified so that  we actually control the 8 DP's as follows:
/*
  if (DP1 == 1)
    bitArrayWrite(pointers[7], 1);
  if (DP2 == 1)
    bitArrayWrite(pointers[15], 1);
  if (DP3 == 1)
    bitArrayWrite(pointers[23], 1);
  if (DP4 == 1)
    bitArrayWrite(pointers[31], 1);
  if (DP5 == 1)
    bitArrayWrite(pointers[39], 1);
  if (DP6 == 1)
    bitArrayWrite(pointers[47], 1);
  if (DP7 == 1)
    bitArrayWrite(pointers[55], 1);
  if (DP8 == 1)
    bitArrayWrite(pointers[63], 1);
*/

// remember what we set - perhaps for use when doing transition to WORD display

olda = aa;
oldb = bb;
oldc = cc;
oldd = dd;




}

void bitbashTX()
{

  // send data out


      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[7]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[6]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[5]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[4]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[3]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[2]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[1]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[0]);
    

  digitalWrite(LEpin, HIGH);
  digitalWrite(LEpin, LOW);
  //remember the contents of the old bitarry - for fade out before word display
  OldBitArray1[0] = BitArray[0];
  OldBitArray1[1] = BitArray[1];
  OldBitArray1[2] = BitArray[2];
  OldBitArray1[3] = BitArray[3];
  OldBitArray1[4] = BitArray[4];
  OldBitArray1[5] = BitArray[5];
  OldBitArray1[6] = BitArray[6];
  OldBitArray1[7] = BitArray[7];
  
  // this is now performed after the call to bitbashTX so that it can be used for fade out
  //zerobits(); // clear the data store
}


void disptest()
{
  analogWrite(pwmPin, Maxbright);

  for (int testcount = 0; testcount < 10; testcount++)
  {
    Serial.println(testcount);

    for (int bittest = 0; bittest < 64; bittest++)
    {

      // zerobits();

      // bitSet(j, bittest);
      bitArrayWrite(bittest, 1);

      Serial.print("j=");
      Serial.println(bittest);

      bitbashTX();
      zerobits();
      nonBlock(40);
    }
  }
}

void zerobits() {
  for (int zerobits = 0; zerobits < 9; zerobits++) BitArray[zerobits] = 0x00;
}

void bitArrayWrite(const unsigned int index, const boolean value)
{
  if (index > 64)
    return;
  bitWrite(BitArray[index / 8], index % 8, value); // write the right bit of the right char
}

void startup()
{

  // clear the bit array
  zerobits();

  // odd behaviour noted here - I wanted to add bits to the array and write them out each time as it grew - it seemed that the array was cleared after the bitbashTX - so for now - I complete the whole thing before updating the display each time.

  // set inter step speed
  int startdel = 100;

  // turn display up
  //analogWrite(pwmPin, Maxbright);
  digitalWrite(blankpin,HIGH);

  Serial.println("Doing offset 0");


  // do this thing 5 times.  Circletimes is the number of rotations the segments make aroung the clock at startup
  for (int circletimes = 0; circletimes < 5; circletimes++)
  {
    //Serial.println("Pass");
    
        for (int testbit = 0; testbit < 15; testbit++)
    {
          //Serial.print(testbit);
          //Serial.print("-");
          //Serial.println(pointers[testbit]);

zerobits(); // clear all bits
      // set the bit
 //     bitArrayWrite(testbit, 1);
 bitArrayWrite(pointers[0+testbit], 1);
 bitArrayWrite(pointers[15+testbit], 1);
 bitArrayWrite(pointers[30+testbit], 1);
 bitArrayWrite(pointers[45+testbit], 1);
 
  
  /*
  // show contents of bit array
for(int barseg=0; barseg<8; barseg++){
  for(int bar=0; bar<8; bar++) {
    if((BitArray[barseg]  & (1 << bar)) == 0 ) Serial.print("0");
    else Serial.print("1");
  }
  Serial.println("");
}
  
  */


       // update the display

      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[7]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[6]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[5]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[4]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[3]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[2]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[1]);
      shiftOut(dataPin, clockPin, MSBFIRST, BitArray[0]);
    
  digitalWrite(latchPin, HIGH);
  digitalWrite(latchPin, LOW);
  

    
nonBlock(startdel);

}
  }




}
/*
//Display the name
void displayname() {
  //Scroll the name as defined across the 8 characters of the display
  // loop namescroll times
  // nixieshift only 8 parts out that is starting at 0 (with the subsequent 7 parts) and then cycling namescroll times.
  if (showname == 1) {
    for (int namepos = 0; namepos <= namescroll; namepos++) {
      nixieshift(nametext[namepos], nametext[namepos + 1], nametext[namepos + 2], nametext[namepos + 3], nametext[namepos + 4], nametext[namepos + 5], nametext[namepos + 6], nametext[namepos + 7], 0);
      nonBlock(200);
    }
    nonBlock(100);
  }

  nonBlock(0);
}
*/

/*
//Display the scrolling name message - NOW COMMENTED OUT AS IT HAS BEEN REPLACED BY DEFINED STRING VERSION AT END OF FILE
void displayname() {
  //scrollgap ads spaces before the date comes in
  //Scroll the name as defined across the 12 characters of the display
  // loop namescroll times
  // nixieshift only 12 parts out that is starting at 0 (with the subsequent 11 parts) and then cycling namescroll times.
  dimtube();
  nixieshift(blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, blankdigit, 0);  //Sending blankdigit causes the display to be blank
  brighttube();

  for (int namepos = 0; namepos <= namescroll; namepos++) {
    nixieshift(nametext[namepos], nametext[namepos + 1], nametext[namepos + 2], nametext[namepos + 3], nametext[namepos + 4], nametext[namepos + 5], nametext[namepos + 6], nametext[namepos + 7], nametext[namepos + 8], nametext[namepos + 9], nametext[namepos + 10], nametext[namepos + 11], 0);
    nonBlock(175);
  }
  nonBlock(200);
}
*/
/*
void fillMessageTime(int sgap2)
{
  // sgap2 is used to pass the previously defined scrollgap on to this routine.

  // get the time components and populate the message string that is being scrolled out of the date display.
  // It will fill the middle 8 of the last 12 character positions.

  setLocalTime(); // set the local time
  // check to see if 12/24 hours and set hours accordingly - set temp variable for interim steps
  temphour = hour(local);
  // Check the initial setting to make sure that the timesystem is either "0" or "12" and not an wrong value
  if (timesystem > 0)
    timesystem = 12;
  // if the clock thinks it is 13 hundred hours or later and clock is set for 12 hour time, set the temphour variable to 12 hour time
  if (timesystem > 0 && temphour > 12)
    temphour = (temphour - timesystem);
  // if the clock thinks it is 00 hours and clock is set for 12 hour time, set the temphour variable to 12
  if (timesystem > 0 && temphour == 0)
    temphour = 12;

  int tenshour = int(temphour / 10);
  int unitshour = temphour - (tenshour * 10);
  int tensmin = int(minute(local) / 10);
  int unitsmin = minute(local) - (tensmin * 10);
  int storesecs = second(local) % 60;
  int tenssec = int(storesecs / 10);
  int unitssec = storesecs - (tenssec * 10);

  if (!leadingzero && tenshour == 0) //set leading time zero from web interface
  {
    tenshour = blankdigit;
  }

  // so now we have everything we need
  // time to stuff the numbers into the string
  // regardless of the imperial/metric - the new time will always start in the last 12 characters of the array - so - set the new start pos,
  // later - pass this as a paramater.

  if (metricUnits == true)
  {
    newTimePos = datePTR + 6;
  }
  else
  {
    newTimePos = datePTR + 11;
  }

  // for (int dateScroll = 1; dateScroll < (datePTR + 3 + 2); dateScroll++) {

  dateText[newTimePos + 0 + sgap2] = tenshour;
  dateText[newTimePos + 1 + sgap2] = unitshour;
  dateText[newTimePos + 2 + sgap2] = 36;
  dateText[newTimePos + 3 + sgap2] = tensmin;
  dateText[newTimePos + 4 + sgap2] = unitsmin;
  dateText[newTimePos + 5 + sgap2] = 36;
  dateText[newTimePos + 6 + sgap2] = tenssec;
  dateText[newTimePos + 7 + sgap2] = unitssec;

  // toggle colon status
  // colonstatus = !colonstatus;
}
*/

// Display the name - scrolling
void displayname()
{

  int nametextPTR = 0; // used when filling out nametext[]

  // Scroll the name as defined across the 8 characters of the display
  //  loop namescroll times
  //  nixieshift only 6 parts out that is starting at 0 (with the subsequent 5 parts) and then cycling namescroll times.

  // set the old digits to blank so that the next display scrolls in.

  // Take defined MessageString and store in MessageString1 (which is a regular string, MessageString is NOT!)
  MessageString1 = MessageString;

  // Add the required number of spaces at the beginning and end of the string for blank at each end of the display. For 8digit - there will be 8 at each end!
  MessageString1 = "    " + MessageString1 + "    "; // add the padding for scrolling the message in then out again - remember to add 8+8 to all definitions involving the string length - allow 40+6+6+1 for good measure.
  
  // initialise buf to all null - useful when shorter string is defined after longer one
  for (int bufptr = 0; bufptr < 64; bufptr++) buf[bufptr] = 0;
  
  MessageString1.toCharArray(buf, 64);                       // place the contents of MessageString into 40 chars of buf HMM that is all wrong, there will be at least 40 chars of message+24 of blank spaces = 64

  for (int bufptr = 0; bufptr < 64; bufptr++)
  {
    if (buf[bufptr] > 0)
    { // here check to see if there is an ascii position defined, it should be 0 if there is not - ie NULL
      // Serial.println(buf[bufptr], DEC); // used for debugging only
      //  now set an element in nametext[]
      nametext[bufptr] = buf[bufptr];
      nametextPTR++; // incrememnt nametextPTR = this will become namescroll
    }
  }

  // now scroll the message
  // in below changed from -8 to - 4
  for (int namepos = 0; namepos <= nametextPTR - 4; namepos++)
  {
    nixieshift(nametext[namepos], nametext[namepos + 1], nametext[namepos + 2], nametext[namepos + 3], 0);
    nonBlock(250);
  }

  olda = blankdigit;
  oldb = blankdigit;
  oldc = blankdigit;
  oldd = blankdigit;
  olde = blankdigit;
  oldf = blankdigit;
  oldg = blankdigit;
  oldh = blankdigit;
}

void displayword()
{
/// now transition existing display to blank using a 
// preserve the contents of the bit array as OldBitArray1 is overwritten in bitbashTX
OldBitArray2[0] = OldBitArray1[0];
OldBitArray2[1] = OldBitArray1[1];
OldBitArray2[2] = OldBitArray1[2];
OldBitArray2[3] = OldBitArray1[3];
OldBitArray2[4] = OldBitArray1[4];
OldBitArray2[5] = OldBitArray1[5];
OldBitArray2[6] = OldBitArray1[6];
OldBitArray2[7] = OldBitArray1[7];
// at this point - OldBitArray2 will contain the last things that were displayed 
// now a loop ot 15 passes
// each pass put all of OldBitArray2 into BitArray
// then progressively blank out segment 1-15 of each digit
// call bitbashTX

for (int fadeloop=0; fadeloop <15; fadeloop++)
{
// write all of OldBitArray2 into BitArray
for (int rewrite = 0; rewrite<8; rewrite++) BitArray[rewrite] = OldBitArray2[rewrite];

/*
//put on the segment that we are about to blank and display it
bitArrayWrite(pointers[ 0+fadeloop],1);
bitArrayWrite(pointers[15+fadeloop],1);
bitArrayWrite(pointers[30+fadeloop],1);
bitArrayWrite(pointers[45+fadeloop],1);
// update the display
bitbashTX();
//small delay
nonBlock(100);
*/

// now blank out segments 0 to fadeloop
for (int fadeloopblank = 0; fadeloopblank < fadeloop; fadeloopblank++) {


bitArrayWrite(pointers[ 0+fader[fadeloopblank]],0);
bitArrayWrite(pointers[15+fader[fadeloopblank]],0);
bitArrayWrite(pointers[30+fader[fadeloopblank]],0);
bitArrayWrite(pointers[45+fader[fadeloopblank]],0);
}

// update the display
bitbashTX();

//small delay
nonBlock(80);

}



    // now clear the array before writing the real word
    zerobits();
    dimtube();
    nixieshift(87, 111, 114, 100, 0);
    brighttube();
    nonBlock(2000);
    dimtube();
    nixieshift(32,32,32,32,0);
    brighttube();
}

Very many thanks - I had (thus far) ignored most of the Yellow (in platformIO) warnings. I will now go through and eliminate these (or is that what you did in the code that you posted?

No, I did not eliminate the error so it's still in the code that I showed.

Note / edit:
I had to copy some files from your zip and placed them in the sketch directory and included them with double quotes.

I have resolved the bounds issue and no longer get those errors though the eeprom read still returns 0xFF instead of the real data.

I also removed the test to see if (wire.available) and it still returned 0xFF.

I got distracted by my 'other' issue which turned out to be hardware related and not software so now I need to get back to this. Fortunately I can test it on standalone hardware (ie. a Wemos D1 Mini with the eeprom board connected). I'll set about adding in bits until it breaks. Assuming I get to that point I can then set about seeing why it breaks and fix it.
At this stage I'm assuming that it's going to be some library conflict thing, I will report back asap.

I added and initialised all the libs from my main clock code into the small test code and the small test code still read the data OK.
I research some more and found that I could test for read errors, I implemented this in the small test code and it still read the data OK.
I put the same mods in the main clock code and it showed that it was getting read errors.


The image shows the additional steps to crudely show that there was a read error.
Now I need to look at how to debug read errors.

In desperation and clutching at straws I decided to add a Wire.begin(0x50); in the readEEPROM section as follows:

et voila!

It now reads the data from the eeprom correctly.
Could anyone explain to me why this step was necessary?

Here is the output from the test that I implemented within the main clock code:

Which shows groups of 4 bytes albeit in hex notation.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.