[code]
//
// Cindy's alarm clock - By Mark M. Lambert on February 20th, 2022
// Copyright Mark M. Lambert - All Rights Reserved
//                             May be used or excerpted with attribution
//
const String Version  = "8.07";
const int UnitID   = 5;        //Serial number controls conditional compile
//
// V8.07 - 14Dec22 - MML - MOTD bug is a persistent little bugger
// V8.06 - 13Dec22 - MML - MOTD bug
// V8.05 - 11Dec22 - MML - Tweak on unit #6 (Cont.)
// V8.04 - 07Dec22 - MML - Tweak on unit #6
// V8.03 - 21Nov22 - MML - Hour chime offset
// V8.02 - 19Oct22 - MML - Revert and clean up - Compiles w/o error
// V8.01 - 13Jul22 - MML - Conditional Compile for alternate RTC DS3231 vs DS1302
// V7.26 - 10Jul22 - MML - Patch in dual four digit LED displays
// V7.25 - 04Jul22 - MML - Fix "final" pre-release bug
// V7.24 - 28Jun22 - MML - Work on chime bug
// V7.23 - 26Jun22 - MML - Try to get at least baro cal working - Release Candidate
// V7.22 - 24Jun22 - MML - Retain backlight setting over reset
// V7.21 - 19Jun22 - MML - Lint Pass - punch list
// V7.20 - Omitted to prevent being mistaken for a large rev
// V7.19 - 04May22 - MML - Patched for dead RTC
// V7.18 - 02May22 - MML - Add Prompts file
// V7.17 - 01May22 - MML - Fix minor bug in stopwatch
// V7.15 - 29Apr22 - MML - Add special holiday banner at top of hour
// V7.14 - 28Apr22 - MML - Put timeout in wait to prevent hard lock
// V7.13 - 27Apr22 - MML - Change 8-ball into running advert at top of minute
// V7.12 - 24Apr22 - MML - Add magic 8-ball
// V7.11 - 23Apr22 - MML - Extend months and days to full words, polish
// V7.10 - Omitted to prevent being mistaken for a large rev
// V7.09 - 22Apr22 - MML - Lint
// V7.08 - 19Apr22 - MML - Ack button trigger timer
// V7.07 - 18Apr22 - MML - Expand LCD display functions. Allow DHT11/DHT22
// V7.06 - 16Apr22 - MML - DHT22 upgrade for outdoor temp, radio out, LCD in
// V7.05 - 15Apr22 - MML - BME280 working
// V7.04 - 13Apr22 - MML - Tighten, saved 10% of RAM!
// V7.03 - 11Apr22 - MML - Refine
// V7.02 - 10Apr22 - MML - Add Stopwatch
// V7.01 - 07Apr22 - MML - Clean up
// V7.00 - 05Apr22 - MML - Add new features and get sound working
// V6.24 - 30Mar22 - MML - Cut down the verbosity
// V6.23 - 28Mar22 - MML - Display weather Hi/lo
// V6.22 - 25Mar22 - MML - Remote dims LEDs - override pot
// V6.21 - 24Mar22 - MML - Use pot for matrix brightness
// V6.20 - Omitted to prevent being mistaken for a large rev
// V6.19 - 18Mar22 - MML - Add radio code per original buttons
// V6.18 - 17Mar22 - MML - Try I2C FM module with antenna! :D
// V6.17 - 16Mar22 - MML - Swap alarm set and relay on LED's
// V6.16 - 11Mar22 - MML - Work on MP3 player
// V6.15 - 10Mar22 - MML - Fix bug in alarm on
// V6.14 - 08Mar22 - MML - Turn off log dump & clean up auto off logic
// V6.13 - 06Mar22 - MML - Invert Colon
// V6.12 - 04Mar22 - MML - Install SD card - read data on first compile! :D
// V6.11 - 03Mar22 - MML - Lint
// V6.10 - Omitted to prevent being mistaken for a large rev
// V6.09 - 01Mar22 - MML - Tweak display timing
// V6.08 - 28Feb22 - MML - Indicate power relay state
// V6.07 - 27Feb22 - MML - Resequence 420 & 840, simplify setting
// V6.06 - 26Feb22 - MML - Add clock set (after glitching the RTC)
// V6.05 - 25Feb22 - MML - Wire & rough out radio code - polish
// V6.04 - 24Feb22 - MML - Cleanup & tweak functionality
// V6.03 - 23Feb22 - MML - Add four digit 7 segment display & RTC
// V6.02 - 22Feb22 - MML - Rough out command framework. RTC module due today
// V6.01 - 21Feb22 - MML - LCD, DHT working, 8x8x4 matrix & buzzer up, time displaying
// V6.00 - 20Feb22 - MML - Subset/superset of Rick Box to be custom alarm clock
// V5.18 - 18Feb22 - MML - Fork from RTC project. See for 'tween versions
// V1.00 - 06Dec21 - MML - Baseline with LCD - Mega 2650 has built in pullups
//
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
//
// Device Master List
// ==================
// Mega 2560 Processor                            - Works
// Screw terminal shield                          - Works
// 8x8x4 dot matrix display module MOSI/MISO      - Works - Main Display
// 20x4 LCD display                               - Works - Stats
// R/Y/G Traffic Light w/ traffic signal shroud   - Works - Power/Alarm/Hearbeat
// RGB 10mm LED                                   - Works - OUT
// LDR                                            - Works - Makes RGB LED step - OUT
// IR remote command in & remote                  - Works - Use wide FOV mount
// RTC DS1302                                     - Works
// RTC DS3231 With Lithium rechargable battery
// BackLight                                      - Works
// Indoor BME380 Temp/Humidity/Baro               - Works
// Outdoor DHT22 Temp/Humidity                    - Works, remote wiring issue resolved
//
// MP3 player - Serial Control - for rude wake up messages - OUT - I'm annoyed
//
// Coffee Pot Relay                               - Works great! - Back in and out
// SD Card with MISO/MOSI                         - Works - on first try, but not this time
// Reset Button                                   - Works - Green
// Ack Button (also Select)                       - Works - Silver $$
// Analog pot for random input                    - Works 5K
// I2C FM radio module                            - Works, post amp issues, needs +12 - Doesn't pick up squat - OUT
// 5-to-12 Converter                              - Works, not tested for max output - Not needed - OUT
// Buzzer                                         - Works, obnoxious
// 1" Piezo speaker                               - Works, softer, upgraded to regular speaker
//
// ==================================
// Start of Pin Constants
// ==================================
//
// Analogs first
//
const int xx      = 0;      // Placeholder value
//
const int Vnoise  = A2;     // Random number seed is open AI
const int Vin     = A3;     // Power supply direct output monitor - NOT USED
//
const int I2C_A4  = A4;    // RESERVED
const int I2C_A5  = A5;    // RESERVED
//
// A6-A13 - SPARE
//
// const int LDR_AI  = A14;    // LDR analog See D34
//
// Variable input device
//
const int Wiper   = A15;    // Wiper from 5k pot for data entry
//
// Float constants for wiper
//
const float aiSteps     = 1024.0;             // 10 bit ADC
const float VoltsPerBit = (5.0 / aiSteps);    // Compute bit weight based on +5 Vin max
const float stepsPerHr  = aiSteps /  24.0;    // Hours input
const float stepsPerMin = aiSteps /  60.0;    // Minutes input
const float stepsPerYr  = aiSteps / 100.0;    // Year input, 20xx presumed
const float stepsPerMon = aiSteps /  12.0;    // Months input
const float stepsPerDt  = aiSteps /  31.0;    // Date input
const float stepsPerDoW = aiSteps /   7.0;    // Day input
const float stepsPerYN  = aiSteps /   2.0;    // 0/1
const float stepsPerCal = aiSteps /   2.0;    // Six menu options - CHANGED to TWO for TEST
const float adjRange    = 40.0;               // Needed later
const float stepsPerAdj = aiSteps /  adjRange;    // +- adjRange of adjustmenta
//
// Analogs end
// -----------
// Now digitals
//
// D0 and D1 are rx/tx for serial programming
//
// 2-10 sorta spare
//
// D11 & 12 reserved for MOSI/MISO
// D13 is LED_BUILTIN and is used (maybe) by MOSI/MISO devices
//
// Comm channel I/O's
//
const int S3_TX     = 14;     // ESP-01 WiFi - not wired
const int S3_RX     = 15;
//
const int S2_TX     = 16;     // Knob radio - OUT - See I2C
const int S2_RX     = 17;
//
const int S1_TX     = 18;     // MP3 player - In for test (again), at least it blinks now.
const int S1_RX     = 19;
//
// I2C was brought out to buss bars for mass connections - wasn't needed
// Actually the one I2C device (FM radio) connected thru the SDA/SCL pins up by D13
//
const int I2C_SDA   = 20;     // These are attached to the matching buss bars for bussing to all I2C devices
const int I2C_SCL   = 21;     // Pullups are internal to Mega 2560
//
// tm1637 devices
//
const int sda_blue  = 22;     // four digit LED - blue - Both work independently
const int scl_blue  = 23;
const int sda_red   = 24;     // four digit LED - red
const int scl_red   = 25;
//
// Alternate RTC is connected to 20/21
//
// RTC I/O pins - Working fine
//
const int kCePin    = 26;  // Chip Enable - grn
const int kIoPin    = 27;  // Input/Output - yel
const int kSclkPin  = 28;  // Serial Clock - orn
//
// 29-34 free
//
// Humidity/temp sensors - working great - Indoor replaced with BME280
//
const int DHTPIN2   = 35;     // Digital pin connected to the outdoor DHT sensor
const int DHTPIN2X  = 36;     // Digital pin connected to the indoor DHT sensor
//
// IR remote control input - noisy - doesn't like LED lighting or PS noise
// Increased FOV and operation is more reliable
// 07Dec22 - seems like this is hardware vendor specific issue with wide mount IR sensor
// Velleman 317's don't have issue
//
const int RECV_PIN  = 37;     //IR Receiver input pin
//
// On the remote there are seven rows of three buttons each.
// Each has a unique code.
// We're going to define the button codes here instead
// of scattering them in a massive SWITCH.
//
// ----------------------
// _00 = _RC, 0 indexed
//
// All the little IR remotes appear to use the same set of codes
// Determined empirically:
//
const int Btn_00  = 23971;  // Top Row, left button - Row 0, Col 0
const int Btn_01  = 25245;  // Row 0, Col 1 -
const int Btn_02  =  7651;   // Top Row, right button - Row 0, Col 2
//
const int Btn_10  =  8925;   // Row 1, col 0 - (red)
const int Btn_11  =   765;
const int Btn_12  = 15811;
//
const int Btn_20  =  8161;
const int Btn_21  = 22441;
const int Btn_22  = 28561;
//
const int Btn_30  = 26775;
const int Btn_31  = 26521;
const int Btn_32  = 20401;
//
const int Btn_40  = 12495;
const int Btn_41  =  6375;
const int Btn_42  = 31365;
//
const int Btn_50  =  4335;
const int Btn_51  = 14535;
const int Btn_52  = 23205;
//
const int Btn_60  = 17085;
const int Btn_61  = 19125;
const int Btn_62  = 21165;     // Bottom Row, right button
//
// Ordered a different brand of IR remote that has a DIFFERENT set of codes!
// Haven't sussed everything out yet but there is at least SOME overlap
// I've got half a dozen of these little remotes and this is the first
// one that isn't a clone. Stay tuned - more to come
//
// ----------------------
//
// 38, 39 - SPARE
//
// Relay pin
//
const int K2        = 40;   // Coffee Pot - Back IN! (yayyy!) - is my fave device
//
// 41-42 SPARE
//
const int backLite  = 42;   // backlight - Big LED panel for interior lighting
const int Buzzer    = 43;   // Annunciator
const int Speaker   = 44;   // Speaker - tone command
//
const int AckBtn    = 45;   // Acknowledge button to silence alarm
//
// Traffic lights are near top end of DOs
//
const int TL_R      = 46;   // Coffee On
const int TL_Y      = 47;   // Alarm On
const int TL_G      = 48;   // Heartbeat
//
// 50-53 reserved for MOSI/MISO
// SPI:
//  50/25/11 (MISO) <<== not needed for output only
//                      Needed for SD card
//  51/24/12 (MOSI)
//  52/23/13 (SCK)z
//  53/22/10 (SS)   <<== Device Select
//
// 11, 12 & 13 Are MISO/MOSI - AKA SPI on UNO
// 22-25 reserved for MOSI/MISO SP1 devices
// this conflicts with other docs or it may be
// uno vs mega 2560
//
// Initializes the SD library and card.
// This begins use of the SPI bus
// pins 11, 12, and 13 on Arduino UNO boards;
// pins 50, 51, and 52 on the Mega
// and the chip select pin,
// which defaults to the hardware SS pin
// pin 10 on Arduino UNO boards,
// pin 53 on the Mega
//
// Got dot matrix and 4 digit readouts in
// The matrix needs MOSI and I think the readouts
// are I2C, but I won't be sure until they arrive
// Modules arrived, waiting on cables
//
// Mega 2560 pinout is correct - worked on first try
//
// SD card is MOSI/MISO, needs its own CS line
//
const int sdSel     = 49;   // SD card Chip Select
const int MISOLED   = 50;   // Data Write for LED matrix & SD card
const int MOSILED   = 51;   // SD Card Data Read
const int SCKLED    = 52;   // SPI clock - SD Card/LED Matrix
const int CSLED     = 53;   // LED matrix CS line
//
// End of Digitals
//
// ==================================
// End of Pin Constants
// ==================================
//
// Device Constants
//
const unsigned interval     = 1000; // Don't overpoll
const unsigned long timeOut = 300000;   // 1000 * 60 * 5 = 300,000 = 5 minutes in milliseconds
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
//
// EEPROM image - we need pointers, not variables
//
const int promTempFC        =  0;   // Flag - EEPROM BASE - ONLY ADD AT END
const int promAlarmSet      =  1;   // Flag
const int promMatrixBright  =  2;   // Int, 0-15
const int promClockMode12   =  3;   // Flag
const int promScrollRateMs  =  4;   // Int 50 to 100 typical
const int promAlarmHr       =  5;   // Int - 0-23
const int promAlarmMin      =  6;   // Int - 0-59
const int promFrequency     =  7;   // Int - 889-1079, two BYTES
const int promChimeOnHr     =  9;   // Int - 0-23 - Chime start this hour
const int promChimeOffHr    = 10;   // Int - 0-23 - Chime stop this hour
const int promBingBong      = 11;   // Flag - 1 buzzer, 0 speaker
const int promLCDmode       = 12;   // 0, show both, 1 out only, 2 in only, 3 ...
const int promDHTtype       = 13;   // 22 or 11 for outside sensor - different ports
const int promSTFU          = 14;   // Flag, 0 ads, 1 no ads
const int promBackLite      = 15;   // Flag, 0 off, 1 on
const int promCalBaro       = 16;   // Offset for calibration
//
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
//
// Includes - DO NOT REORDER
//
#include <EEPROM.h>       // EEPROM support for tables
#include <stdio.h>        // Standard IO
#include <DHT.h>          // Digital Humidity/Temp sensor library
#include <IRremote.h>     // IR Remote Library
#include <LG_Matrix_Print.h>      // LED matrix - test works
#include <SPI.h>          // SPI interface driver
#include <SD.h>           // SD Card
//
// Wire.h must load first
//
#include <Wire.h>               // I2C Line Driver
//
// const int hasBME = true;
//
//#if hasBME == true
#include <Bme280.h>             // Works!
//#endif
//
#include <CPUTemp.h>      // Internal CPU temp
//
#include <LiquidCrystal_I2C.h>  //I2C LCD display driver for all LCD's
//
// Start building objects
//
LiquidCrystal_I2C   lcd(0x27, 20, 4);  // set the LCD object address to 0x27 for a 20 chars and 4 line display
LiquidCrystal_I2C lcdSm(0x3E, 16, 2);  // set the lcdSm object address to 0x3E for a 16 chars and 2 line display
//
// Special hardware only on #5
//
#include <TM1637Display.h>      // Driver for four digit LED displays
//
// Four digit LED displays are sda/scl with dedicated feeds independent from the standard pair
//
TM1637Display displayBlue(scl_blue, sda_blue);
TM1637Display  displayRed(scl_red,  sda_red );
//
File LogFile;         // File object
//
// Create 8x8x4 Matrix LED object
// Number of 8x8 segments
//
const int segments = 4;
LG_Matrix_Print lmd(segments, CSLED);
//
// Units 1-4 use oldRTC, unit 5 = new
//
const int oldRTC = true;    //DS1302
// #include <RTClib.h>
// RTC_DS1307 rtc;
/*
  #if oldRTC == true
  //
  // Alternate RTC DS3231
  //
  // Init the DS3231 using the hardware interface
  //
  int RTCtype = 3231;
  #include <DS3231.h>
  RTClib rtc;
  //  DS3231 rtc(I2C_SDA, I2C_SCL);
   // Initialize the rtc object
  //  rtc.begin();
  //
  #else
*/
//
// Create a DS1302 RTC object.
//
int RTCtype = 1302;
#include <DS1302.h>       // RTC library
DS1302 rtc(kCePin, kIoPin, kSclkPin);
//
// #endif
//
// Create humidity/temp objects
//
// Outdoor upgraded to DHT-22
// 22 vs 11 may or may not have different pinouts - Different data too
// and we want to put in optional cable to support
// either.
//
DHT dhtO(DHTPIN2,  DHT22);     // Create Object for outdoors
// DHT dhtI(DHTPIN2X, DHT22);     // Create Object for indoors
//
// IR Remote control object
//
IRrecv irrecv(RECV_PIN);      // Build IR receiver object
//
// This next line throws a compiler warning about being depreciated
// but I've not found what to replace it with that compiles, runs
// and actually works.
//
decode_results results;       // Not sure about this, but it works
//
// #if hasBME == true
//
// BME280 Temp/humidity/Baro
//
Bme280TwoWire sensor;         // Temp/Humidity/Baro - works great
// #endif
//
// -------------------------
// Objects built
// -------------------------
//
// Global state flags need to come from EEPROM
//
// We have to manually, byte by byte figure out what goes where
// in the EEPROM. We've got 4k to tinker with and are limited to
// ~100k write operations on any given memory location. Rears
// are unlimited. So you want to check and only write changes
//
// 0000 - 0FFF - EEPROM range. Everything runs on bytes
//
// BaseAddr  =    0;
// TopAddr   = 4095;
//
// ******************************
// Start of EEPROM address pointers
//
// define start of each object in EEPROM - ONLY ADD ITEMS AT END
//
// Declare stuff from EEPROM
//
int clockMode12       = false;
int AlarmSet          = false;
int tempFC            = false;
int MatrixBright      = false;
int scrollRateMs      = 0;            // trip init
int speedUp           = 15;           // speed up advert
int K2State           = false;
int alarmHr           = 11;           //Default to 11:00a
int alarmMin          = 00;
int onbyAlarm         = false;
int bingBong          = true;         // Default to buzzer
//
// Time & date setting vars - might not need all
//
int clockDoW;   //Day of week
int clockHr;
int clockMin;
int clockDt;
int clockMon;
int clockYr;
//
int aborted     = false;    //Return flag from sloChime and Chime
//
const int MatrixMax   = 15;     // Max intensity value
//
// indoor Temp/Humidity was from DHT-11 - moved to BME280 for indoor readings
// Outdoor is DHT-22, 2nd indoor sensor is also DHT-22
//
float h, f, c;            // Humidity, TempF, TempC - inside BME280
float hO, fO, cO;         // Humidity, TempF, TempC - outside DHT-22
// float hI, fI, cI;         // Humidity, TempF, TempC - inside DHT-22
//
// baro Stat Storage elsewhere
//
float humidity;           // 2 decimals but noisy in the last digits
int LCDmode   = 0;          // Read from EEPROM but default
int DHT22or11 = 22;         // Default
const int maxLCDmode = 4; // Zero based
//
float CPUtemp;            // CPU internal temperature - some oddball unit
const float Kelvin = 273.15;  // Conversion from kelvin
int valx = 0;             // variable to store the value read
//
// ============================================
// SETUP
// ============================================
//
// Globalize
//
String logName = "logmm-yy.txt\0";  //Template
//
String motd        = "";     //Message of the day
String promptHr    = "";
String promptWake1 = "";
String promptWake2 = "";
String promptWake3 = "";
String promptFumo1 = "";
String promptFumo2 = "";
String promptFumo3 = "";
String promptFumo4 = "";
//
// Stopwatch start
//
unsigned long startTime   = 0;
//
float tmpF;           // Working float
//
// Peak and low temp/humidity vars - start with defaults
//
float dailyPeakF = -100.0;
float dailyLowF  =  200.0;
int dailyPeakH   =    0;
int dailyLowH    =  100;
//
float dailyPeakFO = -100.0;
float dailyLowFO  =  200.0;
int dailyPeakHO   =    0;
int dailyLowHO    =  100;
//
// Time holders for F
//
int peakFHr;
int peakFMin;
int lowFHr;
int lowFMin;
//
int peakFHrO;
int peakFMinO;
int lowFHrO;
int lowFMinO;
//
// Time holders for H
//
int peakHHr;
int peakHMin;
int lowHHr;
int lowHMin;
//
int peakHHrO;
int peakHMinO;
int lowHHrO;
int lowHMinO;
//
// Just one set of baro readings
//
float baro;           // millibars - BMD280 - convert to in hg at display time if needed
float lastbaro;       // Last reading for direction of change
//
float hiB =    0.0;    // Hard vacuum
float loB = 2000.0;    // Two atmospheres
//
// Hi/Lo times
//
int hiBHr;
int hiBMin;
int loBHr;
int loBMin;
//
// Calibration factors
//
int CalBaro;
//
int backLiteMode;
//
// Dim by remote or knob
//
int STFU;               // Set blocks ads
int dimMode    = true;  // Knob /Remote
//
int chimeOnHr  = 11;    // First chime time
int chimeOffHr = 22;    // Last chime time
//
const char *months[] = {"January", "Febuary",  "March",
                        "April",   "May",      "June",
                        "July",    "August",   "September",
                        "October", "November", "December"
                       };
//
// "day" gets appended when needed
//
const char *dows[]   = {"Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"};
//
// Calibration options
//
const char *cals[]   = {"Baro", "Quit"};
// const char *cals[]   = {"Baro", "Tmp In", "Tmp Out", "Hum In", "Hum Out", "Quit"};
//
// magic8 ball is chewing up lots of RAM
// moved it to microSD
//
const char EOL        = 0x0D;    // end of cr/lf pair
int linesInFile       = 0;      // Number of lines in propmts.txt
int linesIn8BallFile  = 0;      // Number of line in 8ball.txt
//
// Time t;     // Device data type - read RTC
//
String MatrixText;      // Indeterminate display string
// =================================================
//
// SETUP BEGINS
//
void setup()
{
  //
  // serial is 0/1, s1 is 18/19 - MP3, s2 is 16/17 - radio
  //
  Serial.begin(115200);       // Warm up comm ports
  Serial1.begin(9600);        // MP3 player example shows 9600
  //
  // BME280 needs wire
  //
  Wire.begin();
  //  rtc.start();
  //
  //  #if hasBME == true
  //
  // BME280 Temp/humidity/Baro
  //
  sensor.begin(Bme280TwoWireAddress::Primary);
  sensor.setSettings(Bme280Settings::indoor());
  //  #endif
  //
  readBME();    // Auto Failover to DHT-22
  //
  // Seed random number generator with noise
  //
  randomSeed( ( analogRead(Vnoise) +
                analogRead( Wiper)  ) / 2);    // floating inputs are seed
  //
  // Pull config from EEPROM
  //
  clockMode12       = EEPROM.read(promClockMode12 );  // Default to 12 hr time, false for 24,
  AlarmSet          = EEPROM.read(promAlarmSet    );  // Alarm Set/not, save in EEPROM
  tempFC            = EEPROM.read(promTempFC      );  // Farenheit/not celcius
  MatrixBright      = EEPROM.read(promMatrixBright);  // Start near dim
  scrollRateMs      = EEPROM.read(promScrollRateMs);  // scroll rate, lower is faster
  if (scrollRateMs != 40)
  {
    scrollRateMs = 40;
    //    logProm(promScrollRateMs, scrollRateMs);
  }
  //
  alarmHr           = EEPROM.read(promAlarmHr     );  // Hour
  alarmMin          = EEPROM.read(promAlarmMin    );  // Minute
  chimeOnHr         = EEPROM.read(promChimeOnHr   );  // Hour chime starts
  chimeOffHr        = EEPROM.read(promChimeOffHr  );  // Hour chime ends
  bingBong          = EEPROM.read(promBingBong    );  // Last Annunciator State
  LCDmode           = EEPROM.read(promLCDmode     );  // Last LCD display state
  DHT22or11         = EEPROM.read(promDHTtype     );  // Outdoor sensor can be 11 or 22
  STFU              = EEPROM.read(promSTFU        );  // Ads on or off
  backLiteMode      = EEPROM.read(promBackLite    );  // Backlight on or off
  CalBaro           = EEPROM.read(promCalBaro     );  // Offset
  digitalWrite(backLite, backLiteMode);
  //
  // Sanity check's on cal factor
  //
  if ( CalBaro > 39 )
  {
    CalBaro = adjRange / 2;
    logProm(promCalBaro, CalBaro);
  }
  //
  if (DHT22or11 != 11) DHT22or11 = 22;    // Default to type 22
  //
  if ( (LCDmode < 0) || (LCDmode > maxLCDmode) ) LCDmode = 0;  // Sanity check
  // Send this to SD card too
  //
  Serial.println(F("Stored config:")   );
  Serial.println(F("==============")   );
  Serial.print(F("Version:          ") );
  Serial.println(             Version);
  //
  Serial.print(F("AM/PM-24:         ") );
  Serial.println( String( clockMode12) );
  //
  Serial.print(F("Alarm Set:        ") );
  Serial.println( String(    AlarmSet) );
  //
  Serial.print(F("Temp FC:          ") );
  Serial.println( String(      tempFC) );
  //
  Serial.print(F("Matrix Bright:    ") );
  Serial.println( String(MatrixBright) );
  //
  Serial.print(F("Scroll Rate (ms): ") );
  Serial.println( String(scrollRateMs) );
  //
  Serial.print(F("Alarm Hr:         ") );
  Serial.println( String(     alarmHr) );
  //
  Serial.print(F("Alarm Min:        ") );
  Serial.println( String(    alarmMin) );
  //
  Serial.print(F("Chime On Hr:      ") );
  Serial.println( String(   chimeOnHr) );
  //
  Serial.print(F("Chime Off Hr:     ") );
  Serial.println( String(  chimeOffHr) );
  //
  Serial.print(F("Speaker/Buzzer:   ") );
  Serial.println( String(    bingBong) );
  //
  Serial.print(F("LCD Displaying:   ") );
  Serial.println( String(     LCDmode) );
  //
  Serial.print(F("Outdoor DHT type: ") );
  Serial.println( String(   DHT22or11) );
  //
  Serial.print(F("Backlite mode:    ") );
  Serial.println( String(backLiteMode) );
  //
  Serial.print(F("Baro Cal:         ") );
  Serial.println( String(     CalBaro) );
  //
  //  Serial.print(F("RTC Type:         DS") );
  //  Serial.println(String(      RTCtype) );
  //
  // Sanity check for uninitialized EEPROM
  //
  if (clockMode12 != false                        ) clockMode12  = true;
  if (AlarmSet    != false                        ) AlarmSet     = true;
  if (tempFC      != false                        ) tempFC       = true;
  if ((MatrixBright >   15) || (MatrixBright <  0)) MatrixBright =    2;
  //
  if ((scrollRateMs >  200) || (scrollRateMs < 25)) scrollRateMs =   40;
  //
  // Default alarm
  //
  if ((alarmHr      >   23) || (alarmHr      < 0) ) alarmHr      =   11;
  if ((alarmMin     >   59) || (alarmMin     < 0) ) alarmMin     =   00;
  //
  // Fire up RTC
  //
  //  #if oldRTC == true
  rtc.writeProtect(false);  //Gotta unlock before letting it run
  rtc.halt(false);          //Let clock run
  //  #endif
  //
  Time t = rtc.time();      // Device data type - read RTC
  Serial.println(String(t.hr) + ":" + String(t.min) );
  //
  // INPUT is default but we need to make sure they don't float with INPUT_PULLUP
  // Declare all inputs as pullups
  //
  pinMode(AckBtn, INPUT_PULLUP);      // Alarm acknowledge
  //
  // Relay
  //
  pinMode(K2, OUTPUT);        // Coffee pot
  digitalWrite(K2, false);    // Coffee pot, default off
  //
  pinMode(Buzzer,  OUTPUT);   // Harsh
  pinMode(Speaker, OUTPUT);   // Low volume
  //
  // Traffic lights
  //
  pinMode(TL_G, OUTPUT);    // Heartbeat
  pinMode(TL_Y, OUTPUT);    // Silent
  pinMode(TL_R, OUTPUT);    // Alarm set
  //
  digitalWrite(TL_G, LOW);   //Turn off
  digitalWrite(TL_Y, LOW);   //Turn off
  digitalWrite(TL_R, LOW);   //Turn off
  //
  // RGB LED - OUT
  //
  //  pinMode(ledR, OUTPUT);
  //  pinMode(ledG, OUTPUT);
  //  pinMode(ledB, OUTPUT);
  //
  pinMode(backLite, OUTPUT);        // Internal lighting
  //
  limitCheck();   // Range check
  //
  // Pins all set, anything not listed is a hi-Z input
  //
  // ============
  //
  dhtO.begin();     // Fire up Outside DHT22 temp/humidity sensor
  //  dhtI.begin();     // Fire up Inside DHT22 temp/humidity sensor
  Read_DHT();       // Read temp/humidity to prime vars
  Read_DHT();       // Read temp/humidity to prime vars
  //
  // 8x8x4 LED matrix is cool, readable display
  //
  lmd.setEnabled(true);
  lmd.setIntensity(MatrixBright);   // 0 = low, 15 = high - set via pot or LDR or command
  displayRed.setBrightness(MatrixBright);
  displayBlue.setBrightness(MatrixBright);
  //  stopLMD();
  //
  // Small LCD display shows version on boot
  //
  lcdSm.init();  //initialize the lcd
  lcdSm.noBacklight();  // backlight off
  lcdSm.noDisplay();    // Kill display
  lcdSm.noCursor();     // Cursor off
  lcdSm.setCursor ( 0, 0 );
  lcdSm.print( F("A Cindy's Clock"));
  lcdSm.setCursor ( 0, 1 );            // go to the 2nd row
  lcdSm.print( F("Version: ") );
  lcdSm.print(Version);
  lcdSm.display();    // back on
  lcdSm.backlight();  // backlight on
  //
  // Main LCD display show copyright on boot
  //
  lcd.init();  //initialize the lcd
  lcd.noBacklight();  // backlight off
  lcd.noDisplay();    // Kill display
  lcd.noCursor();     // Cursor off
  //
  lcd.setCursor ( 0, 0 );
  lcd.print( F("Cindy's Alarm Clock"));
  lcd.setCursor ( 3, 1 );            // go to the 3rd row
  lcd.print( F("Version: ") );
  lcd.print(Version);
  lcd.setCursor ( 1, 2 );            // go to the 3rd row
  lcd.print( F("Copr. 2022 - MMcL") );
  lcd.setCursor ( 0, 3 );            // go to the 3rd row
  lcd.print( F("All Rights Reserved") );
  //
  lcd.display();    // back on
  lcd.backlight();  // backlight on
  //
  //  chime(1);
  waitTicker("Cindy's Alarm Clock-V" + Version, true);
  //
  // Turn on remote
  //
  irrecv.enableIRIn();
  //
  //
  // Ok fine, we want to test the SD card reader with this code
  //
  //
  if (!SD.begin(sdSel))    // Crank up SD card
  {
    Serial.println(F("SD init failed"));
  }
  else
  {
    String tmpStr = "log" + padIt(t.mon, 0)     +
                    "-"   + String(t.yr - 2000) +
                    ".txt";
    //
    logName = tmpStr + "\0";
    //
    // DOS FAT 8.3 file names only
    //
    Serial.print(F("Current Log file: ") );
    Serial.println(tmpStr);
  }
  //
  append(F("Restart"));
  //
  // We should see updated data
  //
  readPrompts();                  // Read & initialize prompts
  countLinesIn(F("8ball.txt"));   // get number of lines in 8ball file for later
  //
  // linesInFile has line count
  //
  linesIn8BallFile = linesInFile; // Save Result
  //
  countLinesIn(F("sales.txt"));   // get number of lines in sales file for later
  //
  motd = getEvent();
  if (motd != "")
  {
    append("MOTD: " + motd);
    waitTicker("Today is " + motd, true);
  }
  //
  chime(2);         // Let em know we're live
  //
  // Clean up
  //
  lcd.clear();
  lcd.noCursor();
  showLCD();
  //
  // Turn on LED displays
  //
  displayRed.setBrightness(0x0f);
  displayBlue.setBrightness(0x0f);
  displayBlue.showNumberDec(2150, false);
  displayRed.showNumberDec(512, true);
  //
  delay(2000);
}
// ============================================
//
// SETUP ends
//
// ============================================
// ============================================
//
// LOOP BEGINS
//
// ============================================
//
// Declaring vars inside SWITCH blows up
// Globals
//
File aFile;           // File handle
//
int tmp;              // Working var
int lastSec;
//
String colon = ":";   // Time marker default
//
// Indeterminate strings fluff up the heap.
//
String tmpStr;        // Working strings
String IRval;         // Reading from remote
String LastIRCmd;
String trend = " ";
//
// Fall into runtime
//
Time t =   rtc.time();    // Device data type
void loop()
{
  //
  //   DateTime now = rtc.now();    // Supposedly universal
  //
  //buffer can be defined using following combinations:
  //hh - the hour with a leading zero (00 to 23)
  //mm - the minute with a leading zero (00 to 59)
  //ss - the whole second with a leading zero where applicable (00 to 59)
  //YYYY - the year as four digit number
  //YY - the year as two digit number (00-99)
  //MM - the month as number with a leading zero (01-12)
  //MMM - the abbreviated English month name ('Jan' to 'Dec')
  //DD - the day as number with a leading zero (01 to 31)
  //DDD - the abbreviated English day name ('Mon' to 'Sun')
  // Old
  Time t = rtc.time();  // Poll clock - DS1302
  //
  // Let LED matrix running until eom
  //
  if (!lmd.updateTicker()) lmd.stopTicker();  //Stop at end
  //
  // Fast execution items lead
  //
  // We need to watch the RTC and trigger when the seconds change
  //
  startTime = millis();   // Get start
  // hard poll IR while waiting
  //
  while (t.sec == lastSec)  // Wait for clock to tick
  {
    // Safety trap for dead RTC
    //
    if (millis() > (startTime + interval) )
    {
      startTime = millis();   // Get start
      break;
      // ==========
    }
    //
    // Stopwatch here. Start if Ack pressed
    //
    if (!digitalRead(AckBtn))
    {
      //
      // STOPWATCH
      //
      waitRelease();
      //
      // Save current moment
      //
      startTime = millis();   // Get start
      //      chime(1);   // Ack release
      //
      lmd.clear();
      //
      while (digitalRead(AckBtn))
      {
        lmd.printText(0, String((millis() - startTime)) + "mS", true);  //0 index
        lmd.display();
      }
      //
      tmpStr = String(millis() - startTime) + "mS";
      //
      waitRelease();
      //      chime(2);   //Announce stop
      //
      delay(250);
      String localTmp = tmpStr;   //tmpStr gets chewed up
      waitTicker(tmpStr, false);   // Display as scroll
      //      chime(1);   // Ack
      showIt( localTmp );
      //
      waitPress();
      //
      blip();
      //
      //
      append("ET: " + tmpStr);   // Log event
      //      chime(1);   //Announce stop
      //
      // a-a-a-nd we're DONE!
      //
      waitRelease();
      //
    }
    //
    //  End of Stopwatch
    //
    t = rtc.time();         // Get latest
    //
    // IR remote code is big switch.
    // There are 21 different buttons with unique responses
    //
    // IR Remote needs to be shielded from other light sources
    // Umm dunno how fast we should poll this. Its fast polling now.
    // but we might need to slow it down
    //
    // We'll use the pot to control the matrix brightness
    // when not othrewise busy
    //
    if (dimMode) {
      MatrixBright = analogRead(Wiper) / 64;
      lmd.setIntensity(MatrixBright);
      displayRed.setBrightness(MatrixBright / 2 );
      displayBlue.setBrightness(MatrixBright / 2);
    }
    //
    CheckIR();    // Does 21 different functions
    //
    // Update status lights in real time
    //
    digitalWrite(TL_R, digitalRead(K2));    // Reminder that power relay is on is RED
    digitalWrite(TL_Y, AlarmSet);           // Show amber if alarm is set
    //
    if (!lmd.updateTicker()) lmd.stopTicker();  //Stop at end
    //
  }
  //
  // New second passes
  //
  digitalWrite(TL_G, !digitalRead(TL_G));   //Toggle heartbeat on second
  //
  lastSec = t.sec;      // Note this second
  //
  // Alternate : and . as tic-tok on 8x8x4 matrix
  //
  (colon == ":") ? colon = "."
                           : colon = ":" ;
  //
  // Display time
  //
  formTime(true);
  //
  // Ok, from here on out we have our activities.
  // Each looks at the time and does their thing
  //
  // Midnight data dump and reset, use value as flag in case we miss the moment.
  //
  if (  (t.hr  == 0)
        && (t.min == 0)
        && (t.sec == 0)
     )
    //
  {
    logIt();    // Make log entry with days data
    waitTicker(F("A new day has begun ..."), false);
    motd = getEvent();
  }
  // Check to see if the coffee pot is still on after an hour
  // if so, turn it off
  //
  int tempHr = alarmHr + 1;
  if (tempHr > 23) tempHr = tempHr - 24;  //Compensate for past midnight
  //
  if ( ( (tempHr   ==  t.hr)   &&
         (alarmMin == t.min) ) && onbyAlarm)
  {
    digitalWrite(K2, LOW);    //Turn off
    onbyAlarm = false;
    K2State   = false;
    //
    Serial.print(F("K2 Auto off: ") );
    Serial.print(String(tempHr) + " " + String(padIt(t.hr, 0)) );
    //
    blip();                                 // Little noise to get you to look
    waitTicker(F("Aux relay auto off"), false);    // Let user know
    formTime(true);                             // Show time
    //    chime(3);                               // Different
  }
  formTime(true);
  //
  // Check for alarm time being set
  //
  if (  (alarmHr  ==  t.hr) &&
        (alarmMin == t.min) &&
        (t.sec    ==     0) &&
        AlarmSet)
  {
    //
    // Kick on coffee pot relay - we turn this
    // off automatically  after an hour
    //
    blip();                     // prealarm noise
    digitalWrite(K2, HIGH);     // Load comes on with alarm
    K2State   = true;           // Internal state flag - Y not read IO?
    onbyAlarm = true;           // Alarm turned us on
    dimMode   = true;           // bring up display
    //
    // Wakey Wakey!
    //
    waitTicker(promptWake1, false);
    formTime(true);  //Show time
    //
    sloChime(10);
    if (!aborted)
    {
      waitTicker(promptWake2, false);
      formTime(true);  //Show time
      //
      sloChime(10);
      if (!aborted)
      {
        waitTicker(promptWake3, false);
        formTime(true);  //Show time
        sloChime(10);
      }
      //
    }
    //
  }
  //
  // Play commercial at top of minute
  //
  formTime(true);
  if ( (t.sec == 00) && !lmd.updateTicker() )
  {
    formTime(true);
    showLCD();        // Display results
    Serial.println("Top of minute " + String(t.sec) );
    //
    if (!STFU)
    {
      blip();     // Made'ja look!
      delay(100); // Let blip finish
      //
      lmd.ticker(getLine("sales.txt", linesInFile), scrollRateMs - speedUp);  //faster
    }
    //
  }
  //
  // Show temp at 1/4 past minute
  //
  if ( (t.sec == 15) && !lmd.updateTicker() )
  {
    //
    // Now read BME280 Baro sensor
    readBME();        // Indoor from BME280
    Read_DHT();       // Read temp/humidity outside from DHT-22
    limitCheck();     // Check range limits
    showLCD();        // Display results
    //
    tempFC ? lmd.ticker(String(f, 1) + "f " + padIt(h, 0) + "%", scrollRateMs)
    : lmd.ticker(String(c, 1) + "c " + padIt(h, 0) + "%", scrollRateMs) ;
    //
  }
  //
  // Show Date at 30 - add month names
  //
  if ( (t.sec == 30) && !lmd.updateTicker() )
    //
  {
    lmd.ticker( String(  dows[t.day - 1]) + "day, " +
                String(months[t.mon - 1]) +     " " +
                String(t.date)            +
                suffix(t.date)            +    ", " +
                String(t.yr)              +     " " +
                motd, scrollRateMs - speedUp
              );
  }
  //
  // Show RH at 3/4 of minute
  //
  if ( (t.sec == 45) && !lmd.updateTicker() )
  {
    readBME();        // Indoor from BME280
    Read_DHT();       // Read temp/humidity outside from DHT-22
    limitCheck();     // Check range limits
    showLCD();        // Display results
    //
    float actualBaro = baro + (CalBaro - (adjRange / 2) ) / 10.0 + .05;
    lmd.ticker( (!tempFC ? String(actualBaro,          1) +            "mb " + trend
                 : String(actualBaro * .02953, 2) + char(34) + " Hg " + trend
                ), scrollRateMs);
    //
    // .02953 / 33.86 is .gov conversion factor for MB/in HG
  }
  //
  //  Serial.println("Second mark");
  //
  // Min = 0 & sec = 0 = hour strike
  //
  if ( (t.min == 0) &&
       (t.sec == 0) )
    //
  {
    formTime(true);
    Serial.println("Hour mark");
    blip();             //Little noise to make you look
    //
    // Save hourly so flag is slowed
    //
    lastbaro = baro;    // Save for next hour
    //
    motd = getEvent();
    (motd == "") ? waitTicker(promptHr, false)
    : waitTicker(motd,     false) ;
    //
    formTime(true);     // Show time
    //
    tmp = t.hr;         // Time from globals
    //
    if ( (tmp    > 12) &&
         (clockMode12)
       )
      tmp = tmp - 12;   // Force civilian time
    //
    // Start and end times are selectable
    //
    if ( (t.hr >= chimeOnHr ) &&
         (t.hr <= chimeOffHr) )
    {
      formTime(true);     // Show time
      sloChime(tmp);           // MP3 player OUT (for now)
    }
    //
  }
  //
  // Later in execution overrides time display above
  //
  if ( ( (t.hr  == 16) &&
         (t.min == 20) &&
         (t.sec ==  0) && !STFU)
     )
    //
  {
    if ( (promptFumo1 != "") &&
         (promptFumo2 != "")
       )
      //
    {
      blip();     //Little noise to make you look
      waitTicker(promptFumo1, false);
      formTime(true);           // Show time
      sloChime(4);
      //
      waitTicker(promptFumo2, false);
      formTime(true);           // Show time
      sloChime(2);
    }
    //
    if ( ( (t.hr  == 20) &&
           (t.min == 40) &&
           (t.sec ==  0) && !STFU)
       )
      //
    {
      if ( (promptFumo3 != "") &&
           (promptFumo4 != "")
         )
      {
        blip();     //Little noise to make you look
        waitTicker(promptFumo3, false);
        formTime(true);           // Show time
        sloChime(8);
        //
        waitTicker(promptFumo4, false);
        formTime(true);           // Show time
        sloChime(4);
      }
      //
    }
    // End of 420 logic
  }
  // ==================
  //
  // Log data on 1/4 hour
  //
  if ( ( (t.min ==  0) ||
         (t.min == 15) ||
         (t.min == 30) ||
         (t.min == 45)
       ) &&
       (t.sec      ==  0)
     )
    //
  {
    Serial.println("Here at " + String(t.min) );
    Serial.println("Appending to log ...");
    append("@ :" + padIt(t.min, 0));   // Append to log
    //
    // Only do quarter hour chimes during chime hours
    //
    if ( (t.hr >= chimeOnHr ) &&
         (t.hr <= chimeOffHr) &&
         !STFU
       )
    {
      Serial.print("Chiming: ");
      for (int i = 0; i < (t.min / 15); i++)  // 0/15 = 0 so no chime at top of hour
      {
        chime(1);
        Serial.print("Ding! ");
        //          blip();       // Quietly mark each 1/4 hour, 1 @ 15, 2 @ 30, 3 @ 45
        delay(1000);   // Short delay after blip if we're repeating
      }
      Serial.println(".");
      //
    }
    //
  }
  //
  //
}     // End of LOOP
// =============================================================================
// =============================================================================
// Functions
// =============================================================================
// =============================================================================
//
// Function to read and display Temp Humidity from sensor
//
void Read_DHT()
{
  //  Time t = rtc.time();    // Device data type - read RTC
  //
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  //
  // interior temp/humidity is now from BME280
  //
  // Outdoor is DHT-22
  //
  hO = dhtO.readHumidity();         // Get humidity
  cO = dhtO.readTemperature(false); // Read temperature as Celsius (the default)
  fO = dhtO.readTemperature(true);  // Read temperature as Fahrenheit (isFahrenheit = true)
  //
  //  hI = dhtI.readHumidity();         // Get humidity
  //  cI = dhtI.readTemperature(false); // Read temperature as Celsius (the default)
  //  fI = dhtI.readTemperature(true);  // Read temperature as Fahrenheit (isFahrenheit = true)
}
//
// ==================================================
//
// Ring the bell very short
//
// Bing activates buzzer, Bong uses speaker
//
void blip()
{
  if (bingBong)
  {
    digitalWrite(Buzzer, true);
    delay(75);                  // Short chirp
    digitalWrite(Buzzer, false);
  }
  else
  {
    tone(Speaker, 2500, 100);
  }
  //
}
// ==================================================
//
// Ring the bell rings times with .1 sec between beeps
//
void chime(int rings)
{
  if (rings > 12) rings = rings - 12; // Fudge for 24 hr
  if (rings <  1) rings = 12;         // Midnight is 0 but ring 12
  //
  for (int i = 0; i < rings; i++)
  {
    if (!digitalRead(AckBtn)) break;
    //
    if (bingBong)
    {
      digitalWrite(Buzzer, true);
      delay(100);
      digitalWrite(Buzzer, false);
      if (!digitalRead(AckBtn)) break;
      //
      delay(100);
      //
    }
    else
    {
      tone(Speaker, 2000, 250);     // Alternate output
      if (!digitalRead(AckBtn)) break;
      //
      delay(250);
    }
    //
  }
  //
}
// ==================================================
//
// Ring the bell rings times with 1 sec between beeps
//
void sloChime(int rings)
{
  aborted = false;
  //
  if (rings > 12) rings = rings - 12; //Fudge for midnight rollover
  //
  for (int i = 0; i < rings; i++)
  {
    if (!digitalRead(AckBtn))
    {
      aborted = true;
      break;
    }
    //
    if (bingBong)
    {
      //
      // Don't abort in middle of pulse, sound stays on forever
      //
      digitalWrite(Buzzer, true);
      delay(200);
      digitalWrite(Buzzer, false);
    }
    else
    {
      tone(Speaker, 1750, 250);     // Alternate output
      delay(375);
      tone(Speaker, 2750, 250);     // Alternate output
    }
    //
    if (!digitalRead(AckBtn))
    {
      aborted = true;
      break;
    }
    //
    delay(500);
  }
  //
}
// ==================================================
//
void CheckIR()
{
  float actualBaro;       // Calibrated baro
  //  Time t = rtc.time();    // Device data type - read RTC
  //
  // This is the master remote control switch.
  // See discussion on button structure near front
  //
  // Serial.println(F("CheckIR"));
  if (irrecv.decode(&results))
  {
    // Display what we got
    //
    valx     = abs(results.value);   // no negatives
    IRval    = String( abs(valx) );   // no negatives
    IRval.trim();   //Strange Syntax
    LastIRCmd.trim();
    //
    // Catch value
    //
    //  Serial.println("Raw: " + IRval);
    if ( (valx > 1) && (LastIRCmd != IRval))
    {
      if (IRval != "")
      {
        LastIRCmd = IRval;
        LastIRCmd.trim();
      }
      //
    }
    //
    // Live command when received
    //
    valx = abs(valx);
    //  if (valx >1)
    //    {
    //    Serial.print(F("RXD:  = "));
    //    Serial.println(valx);
    //    }
    //
    // Ok, No silly negatives, Each button sends a code, both remotes send
    // the same code for the same physical button
    //
    // =============================
    // =============================
    // Main control switch on remote command
    // =============================
    // =============================
    //
    if (valx >= 1)      // Triple filter out negs
    {
      //
      // At this point all 21 buttons have an
      // assigned (if not yet fully implemented) function
      //
      // ======================
      // Chime    Alarm   AM/PM
      // Hours    Device  24 hr
      // ----------------------
      // Temp     Full    F/C
      // Humidity Date
      // ----------------------
      // Daily    Daily  Set
      // Temp     Humid  Clock
      // ----------------------
      // Show     Toggle  Set
      // Alarm    Alarm   Alarm
      // ----------------------
      // ======================
      //
      switch (valx)    //Needs integer for switch
      {
        //
        // Button codes in table up front - all tested & work
        //
        case Btn_00:
          Serial.println(F("btn 00"));
          chime(1);
          //
          // Ok, set chime start
          //
          waitTicker(F("Chime on Hour:"), false);
          chimeOnHr = waitForIt(stepsPerHr, "", 0, ":00");
          if (aborted) break;
          //
          chime(1);
          //
          waitTicker(F("Chime off Hour:"), false);
          chimeOffHr = waitForIt(stepsPerHr, "", 0, ":00");
          if (aborted) break;
          //
          chime(1);
          delay(250);
          //
          tmpStr  = "Chime From: " + String( chimeOnHr);
          tmpStr +=        " To: " + String(chimeOffHr);
          //
          waitTicker(tmpStr, false);
          chime(1);
          //
          // Save
          //
          // Retain state
          //
          logProm(promChimeOnHr,   chimeOnHr);
          logProm(promChimeOffHr, chimeOffHr);
          //
          chime(1);   // Ack
          //
          //
          break;
        // =================
        case Btn_01:
          Serial.println(F("btn 01"));
          chime(1);
          /*
            // No coffee relay
            //
            K2State = !K2State;   // Toggle state
            K2State ? MatrixText = "Power ON"
                  : MatrixText = "Power Off";
            //
            onbyAlarm = false;        // Manual control, cancel auto flag
            //
            // Save for power blip
            //
            digitalWrite(K2, K2State);    // Tell hardware
            lmd.ticker(MatrixText, scrollRateMs);
            //
          */
          break;
        // =================
        case Btn_02:
          Serial.println(F("btn 02"));
          chime(1);
          //
          // Toggle 12/24 hour
          //
          clockMode12 = !clockMode12;
          clockMode12 ? MatrixText = "AM/PM"
                                     : MatrixText = "24 Hour";
          //
          lmd.ticker(MatrixText, scrollRateMs);
          logProm(promClockMode12, clockMode12);
          //
          break;
        // =================
        // 2nd Row
        //
        case Btn_10:
          Serial.println(F("btn 10"));
          chime(1);
          //
          // Display weather stats
          //
          // Check FC flag to show in C or F
          //
          actualBaro = baro + (CalBaro - (adjRange / 2) ) / 10.0 + .05;
          lmd.ticker( (tempFC ? String(f, 1) + "f "
                       : String(c, 1) + "c "
                      ) +
                      String(h, 0) +  "% " +
                      (!tempFC ? String(actualBaro,          1) +             "mb " + trend
                       : String(actualBaro * .02953, 2) + char(34) + " Hg " + trend
                      ), scrollRateMs);
          //
          break;
        // =================
        case Btn_11:
          Serial.println(F("btn 11"));
          chime(1);
          //
          lmd.ticker( String(  dows[t.day - 1] )      + "day, " +
                      String(months[t.mon - 1] )      +     " " +
                      String(t.date) + suffix(t.date) +    ", " +
                      String(t.yr), scrollRateMs
                    );
          //
          break;
        // =================
        case Btn_12:
          Serial.println(F("btn 12"));
          chime(1);
          //
          // Toggle F/C
          //
          tempFC = !tempFC;  //Flip flag
          tempFC ? lmd.ticker(String(f, 1) + "f", scrollRateMs)
          : lmd.ticker(String(c, 1) + "c", scrollRateMs);
          //
          logProm(promTempFC, tempFC);    // Save for cold start
          showLCD();  //Update LCD to new units
          //
          break;
        // =================
        //
        // 3rd Row
        //
        case Btn_20:
          Serial.println(F("btn 20"));
          chime(1);
          //
          // Display Temp Hi/lo
          //
          lmd.ticker(String(f,          1)      + "f Hi " +
                     String(dailyPeakF, 1)      +    "f " +
                     padTime(peakFHr, peakFMin) +  " Lo " +
                     String(dailyLowF,  1)      +    "f " +
                     padTime(lowFHr, lowFMin), scrollRateMs);
          //
          break;
        // =================
        case Btn_21:
          Serial.println(F("btn 21"));
          chime(1);
          //
          // Display humidity Hi/lo
          //
          lmd.ticker(String(h,               0) + "% Hi " +
                     padIt(dailyPeakH,       0) +    "% " +
                     padTime(peakHHr, peakHMin) +  " Lo " +
                     padIt(dailyLowH,        0) +    "% " +
                     padTime(lowHHr,   lowHMin), scrollRateMs);
          //
          break;
        // =================
        case Btn_22:
          Serial.println(F("btn 22"));
          chime(1);
          //
          // Need to have a check to prevent going
          // thru this needlessly.
          // Allow bail out after time set
          // don't force date setting
          //
          waitTicker(F("Are You Sure?"), false);
          tmp = waitForYN();
          if (aborted) break;
          //
          delay(250);
          chime(1);
          if (tmp == 1)
          {
            //
            // Ok, set Clock Hour
            //
            chime(1);
            waitTicker(F("Dial Hour:"), false);
            clockHr = waitForIt(stepsPerHr, "", 0, ":xx" );
            if (aborted) break;
            //
            chime(1);
            //
            // Now get minute
            //
            waitTicker(F("Dial Minute:"), false);
            // M is wider than normal, need to lose the :
            clockMin = waitForIt(stepsPerMin, String(clockHr) + ":", 0, "");
            if (aborted) break;
            //
            chime(1);
            //
            // Do they wanna bail?
            waitTicker(F("Set Date?"), false);
            if (waitForYN() == 1)
            {
              chime(1);
              //
              // Now get month
              //
              delay(250);
              waitTicker(F("Dial Month:"), false);
              //
              // Hmmm
              //
              clockMon = waitForMon() + 1;  // Result is zero offset, calendar starts with 1
              if (aborted) break;
              //
              chime(1);
              delay(250);
              //
              // Now get date
              //
              waitTicker(F("Dial Date:"), false);
              clockDt = waitForDate();      //1-31
              if (aborted) break;
              //
              chime(1);
              //
              // Year = error trap
              //
              clockYr = 2021;
              while (clockYr < 2022)
              {
                waitTicker(F("Dial Year:"), false);
                clockYr = 2000 + waitForYear();
                if (aborted) break;
                //
              }
              //
              chime(1);
              //
              // Now get day of week
              //
              waitTicker(F("Dial Day of the Week:"), false);
              clockDoW = waitForDoW() + 1;    // Zero offset, but need 1-7
              if (aborted) break;
              //
            }
            else
            {
              //
              // Use current date
              //
              clockYr   = t.yr;
              clockMon  = t.mon;
              clockDt   = t.date;
              clockDoW  = t.day;
            }
            //
            // We need to wait for operator to click
            // to sync setting. Need to write data
            // to RTC after go
            //
            chime(1);
            delay(250);
            waitTicker(F("Press Ack to sync ..."), false);
            chime(1);   // Ack
            showIt( padTime(clockHr, clockMin) );
            //
            waitPress();
            //
            blip();
            //
            // Write time to RTC
            //
            if (aborted) break;
            //
            setRTC(clockYr, clockMon, clockDt, clockHr, clockMin, clockDoW);
            //
            chime(2);   // Ack
            //
            Serial.print(F("Time Set: ") );
            Serial.println( String(  dows[clockDoW - 1]) + ", "
                            + String(months[clockMon - 1]) + " "
                            + String(clockDt            ) + ", "
                            + String(clockYr            ) + " "
                            + String(clockHr            ) + ":"
                            + String(clockMin)
                          );
            //
            append(F("Time Set"));
            //
          }
          //
          //
          break;
        // =================
        // 4th Row
        //
        case Btn_30:
          Serial.println(F("btn 30"));
          chime(1);
          //
          // Display outside Temp Hi/lo
          //
          lmd.ticker("Out " + String(fO, 1) +
                     "f Hi " + String(dailyPeakFO, 1) + "f " + padTime(peakFHrO, peakFMinO) +
                     " Lo " + String(dailyLowFO,  1) + "f " + padTime(lowFHrO,   lowFMinO),
                     scrollRateMs);
          //
          break;
        // =================
        case Btn_31:
          Serial.println(F("btn 31"));
          chime(1);
          //
          // Magic 8-ball
          //
          waitTicker(F("Magic 8-ball sez ..."), 0);
          //
          delay(3000);  //Wait a bit to build suspense
          //
          lmd.ticker(getLine("8ball.txt", linesIn8BallFile - 1), scrollRateMs - speedUp); //faster
          //
          break;
        // =================
        case Btn_32:
          Serial.println(F("btn 32"));
          chime(1);
          //
          // Baro Calibration routine
          //
          // We have a +-20 count adjustment. 0-39 actually.
          // We show the current val with the offset and adjust knob until correct
          // knob value is saved in EEPROM for use in read routine
          //
          lmd.ticker("Adjust Baro", scrollRateMs);
          CalBaro = waitForTweak(baro, 1);
          //
          // Ahhh, negatives throw a monkey wrench in the data
          // Save naked offset and subtract in readbaro routine so all saved here is pos
          //
          logProm(promCalBaro, CalBaro);
          showLCD();
          //
          // Ding on exit
          //
          chime(1);
          //
          break;
        // =================
        // 5th Row
        // Originally this was for FM radio control
        // Since I had no luck getting it to run
        // OUT - these buttons can be used for the
        // MP3 or other control functions
        //
        case Btn_40:
          Serial.println(F("btn 40"));
          chime(1);
          //
          // Toggle STFU mode - kills ads at top of minute
          //
          // no ads
          //
          STFU = !STFU;
          (STFU) ? lmd.ticker("Ads off", scrollRateMs)
          : lmd.ticker("Ads on",  scrollRateMs) ;
          //
          logProm(promSTFU, STFU);
          //
          break;
        // =================
        case Btn_41:
          Serial.println(F("btn 41"));
          chime(1);
          //
          // Toggle backlight
          //
          backLiteMode = !digitalRead(backLite);
          digitalWrite(backLite, backLiteMode );
          logProm(promBackLite, backLiteMode );
          //
          break;
        // =================
        case Btn_42:
          Serial.println(F("btn 42"));
          chime(1);
          // 273.15 is deg K vs deg C
          CPUtemp = temperature() + Kelvin;
          Serial.println("CPU Temp: " + String(CPUtemp, 1) + "c" );
          lmd.ticker("CPU internal temp is " + String(CPUtemp, 1) + "c", scrollRateMs);  //Normal
          break;
        // =================
        // 6th Row
        //
        case Btn_50:
          Serial.println(F("btn 50"));
          chime(1);
          //
          // Alarm show
          //
          MatrixText = "Alarm Time: " + padTime(alarmHr, alarmMin);
          AlarmSet ? MatrixText += " ON"
                                   : MatrixText += " Off";
          //
          lmd.ticker(MatrixText, scrollRateMs);
          //
          break;
        // =================
        case Btn_51:
          Serial.println(F("btn 51"));
          chime(1);
          //
          // Alarm on/off
          //
          AlarmSet = !AlarmSet;
          if (AlarmSet)
          {
            MatrixText = "Alarm Set On: " + padTime(alarmHr, alarmMin);
          }
          else
          {
            MatrixText = "Alarm Off";
            chime(1);   // Twice is off - like car alarm
          }
          //
          onbyAlarm = false;
          lmd.ticker(MatrixText, scrollRateMs);
          logProm(promAlarmSet,   AlarmSet);
          //
          break;
        // =================
        case Btn_52:
          Serial.println(F("btn 52"));
          chime(1);
          //
          // Ok, set Alarm Hour
          //
          waitTicker(F("Dial Hour:"), false);
          alarmHr = waitForIt(stepsPerHr, "", 0, ":xx");
          if (aborted) break;
          //
          chime(1);
          //
          // Now get minute
          //
          waitTicker(F("Dial Min:"), false);
          alarmMin = waitForIt(stepsPerMin, String(alarmHr) + ":", 0, "");
          if (aborted) break;
          //
          chime(1);
          delay(250);
          //
          waitTicker("Alarm On: " + padTime(alarmHr, alarmMin), false);
          chime(1);
          //
          // Save and turn alarm on automatically
          //
          AlarmSet  = true;
          onbyAlarm = false;          // Clear old state
          //
          // Retain state
          //
          logProm(promAlarmHr,   alarmHr);
          logProm(promAlarmMin, alarmMin);
          logProm(promAlarmSet, AlarmSet);
          //
          chime(2);   // Ack
          //
          break;
        // =================
        // 7th and last Row
        //
        case Btn_60:
          Serial.println(F("btn 60"));
          //          chime(1);
          //
          // Mkay, lets toggle the annunciator mode
          //
          bingBong = !bingBong;
          logProm(promBingBong, bingBong);
          chime(2);   // Make new sound
          //
          break;
        // =================
        case Btn_61:
          Serial.println(F("btn 61"));
          chime(1);
          //
          // Toggle matrix
          // Dim/restore display
          //
          if (dimMode)
          {
            // Dark running
            dimMode       = false;      // remote commands
            MatrixBright  = 0;          // Dim LED matrix
            STFU          = true;       // Silence ads but don't retain
            backLiteMode  = false;      // Soft kill backlight
            digitalWrite(backLite, backLiteMode);
            lcd.noBacklight();          // LCD Backlight off
            lcd.noDisplay();            // Kill LCD display entirely
            displayRed.setBrightness(0x00);
            displayBlue.setBrightness(0x00);
            //
          }
          else
          {
            // Turn everything back on if appropriate
            //
            STFU          = EEPROM.read(promSTFU);      // Restore Ad mode
            dimMode       = true;       // knob commands brightness
            backLiteMode  = EEPROM.read(promBackLite);  // See what mode is supposed to be
            digitalWrite(backLite, backLiteMode);       // Set it
            lcd.backlight();            // Bring backlight back on
            lcd.display();              // show display again
            displayRed.setBrightness(0x0f);
            displayBlue.setBrightness(0x0f);
            //
          }
          //
          lmd.setIntensity(MatrixBright);   // 0 = low, 15 = high
          //
          // Since we didn't update EEPROM the preset
          // brightness will restore on next reset
          //
          break;
        // =================
        case Btn_62:
          Serial.println(F("btn 62"));
          chime(1);
          //
          // Advance display mode
          //
          LCDmode++;
          if (LCDmode > maxLCDmode) LCDmode = 0;
          //
          logProm(promLCDmode, LCDmode);    // Save
          //
          lcd.clear();  // Flush old
          lcd.backlight();  // Make sure is lit
          lcd.display();    // Turn on display
          showLCD();    // Display next mode
          //
          break;
          // =================
          //
          // SWITCH VAL ENDS
          //
      }
      //
      //
    }   // If Val ends
    //
  }   // Decode ends
  //
  // Resume IR control - not really sure what this does.
  //
  irrecv.resume();
  irrecv.enableIRIn();
  //
}
// ==================================================
//
// Keep marquee spinning - no DELAY's allowed
//
// Returns:   Nothing
//
void dillyDally(int mills2wait)
{
  //
  int rightNow = millis();
  //
  while (millis() < rightNow + mills2wait)
  {
    if (!lmd.updateTicker())
    {
      stopLMD();
    }
    //
  }
  //
}
// ==================================================
//
// Spin hard letting ticker run - data from globals
//
// Returns:   Nothing
//
void waitTicker(String Msg, int noShow)
{
  aborted = false;
  unsigned long startMillis = millis();
  //
  lmd.ticker(Msg, scrollRateMs);
  //
  while (lmd.updateTicker())
  {
    if (!noShow) formTime(false);    // Keep clock ticking on LCD
    if ( !digitalRead(     AckBtn) ||
         checkDeadman(startMillis)
       ) break;
    //
  }
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned
//
int waitForIt(float steps, String cue, int bias, String Suffix)
{
  //
  aborted = false;
  unsigned long startMillis = millis();
  String tmpCue = cue;
  int myTmp;  //Return var
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    tmpCue = "";   // Unpad for next loop
    myTmp  = (analogRead(Wiper) / steps);
    if (cue == "")
    {
      // 1st col - pad with blank
      if ((myTmp + bias) < 10) tmpCue = " ";
      lmd.printText(0, tmpCue + String(myTmp + bias) + Suffix, true);  //0 index
    }
    else
    {
      // 2nd col - pad with zero
      if (myTmp < 10) tmpCue = "0";
      lmd.printText(0, cue + tmpCue + String(myTmp + bias) + Suffix, true);  //0 index
    }
    //
    showHold();
    //
    if (checkDeadman(startMillis) ) break;
    //
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  //
  chime(1);
  return myTmp + bias;
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned
//
int waitForYN()
{
  //
  aborted = false;
  int myTmp;  //Return var
  unsigned long startMillis = millis();
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    if (analogRead(Wiper) < stepsPerYN)  // Only two states
    {
      lmd.printText(0, "No ", true);    // pad for length
      myTmp = 0;
    }
    else
    {
      lmd.printText(0, "Yes", true);
      myTmp = 1;
    }
    //
    showHold();
    if (checkDeadman(startMillis) ) break;
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  chime(1);
  return myTmp;
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
//
// Scaled value returned
//
int waitForCal()
{
  //
  int myTmp;  //Return var
  aborted = false;
  unsigned long startMillis = millis();
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    myTmp  = (analogRead(Wiper) / stepsPerCal);
    lmd.printText(0, String(cals[myTmp]), true);  //0 index - append constant part
    //
    showHold();
    if (checkDeadman(startMillis) ) break;
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  //
  chime(1);
  return myTmp;
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned +-20 counts
//
// really need to generalize this. Pass index of param
//
// Float here!
//
int waitForTweak(float Param, int decimals)
{
  //
  float myTmp;  // Return var
  aborted = false;    // Global
  unsigned long startMillis = millis();
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    myTmp  = float( (analogRead(Wiper) / stepsPerAdj) );  // Bias - force float for compiler
    //
    // Scale and offset
    //
    lmd.printText(0, String( Param + ( ( myTmp - (adjRange / 2) ) / 10), decimals), true);
    //
    showHold();
    if (checkDeadman(startMillis) ) break;
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  //
  chime(1);
  return int(myTmp);  // return integer with (adjRange / 2) offset
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned
//
int waitForDoW()
{
  //
  int myTmp;  //Return var
  aborted = false;
  unsigned long startMillis = millis();
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    myTmp  = (analogRead(Wiper) / stepsPerDoW);
    lmd.printText(0, String(dows[myTmp]) + "day", true);  //0 index - append constant part
    //
    showHold();
    if (checkDeadman(startMillis) ) break;
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  //
  chime(1);
  return myTmp;
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned
//
int waitForMon()
{
  //
  int myTmp;  //Return var
  aborted = false;
  unsigned long startMillis = millis();
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    myTmp  = (analogRead(Wiper) / stepsPerMon);
    lmd.printText(0, months[myTmp], true);  //0 index - no padding, all strings same len
    //
    showHold();
    if (checkDeadman(startMillis) ) break;
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  //
  chime(1);
  return myTmp;
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned
//
int waitForDate()
{
  //
  int myTmp;  //Return var
  aborted = false;
  unsigned long startMillis = millis();
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    myTmp  = (analogRead(Wiper) / stepsPerDt);
    lmd.printText(0, String(clockMon) + "/" + String(myTmp + 1), true);
    //
    showHold();
    if (checkDeadman(startMillis) ) break;
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  //
  chime(1);
  return (myTmp + 1);
  //
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned
//
int waitForYear()
{
  //
  int myTmp;  //Return var
  aborted = false;
  unsigned long startMillis = millis();
  //
  stopLMD();
  //
  while (digitalRead(AckBtn))
  {
    myTmp  = ( (analogRead(Wiper) / (aiSteps / 78) ) + 22);  // Compensate for base year
    lmd.printText(0, "20" + String(myTmp), true);
    showHold();
    if (checkDeadman(startMillis) ) break;
    formTime(false);    // Keep clock ticking on LCD
  }
  //
  waitRelease();
  //
  chime(1);
  return myTmp;
  //
}
// ==================================================
//
// Sets clock to time/date passed
//
// Returns:   Nothing
//
void setRTC(int yr, int mo, int dt, int hr, int mn, int dow)
{
  //  #if oldRTC == true
  rtc.writeProtect(false);
  rtc.halt(true);  //Shouldn't this be TRUE?
  //  #endif
  //
  // Make a new time object to set the date and time.
  // Full spec needs seconds and DoW
  //
  Time t(yr, mo, dt, hr, mn, 00, dow);
  //
  // Set the time and date on the chip.
  //
  rtc.time(t);
  //
  //  #if oldRTC == true
  rtc.halt(false);          //Let clock run
  rtc.writeProtect(true);   //Only protect AFTER letting it run
  //  #endif
  //
}
// ==================================================
//
// Read current time and display
// respect 24HR/AMPM flag.
// LCD gets seconds
//
void formTime(int mode)
{
  //  Time t = rtc.time();    // Device data type - read RTC
  String outStr;
  int wrk = t.hr;   // Time from globals
  //
  if ( (wrk <  1) && clockMode12) wrk = 12;       // Show midnight as 12:xx
  if ( (wrk > 12) && clockMode12) wrk = wrk - 12;      //Civilian time
  //
  clockMode12 ? outStr = " "
                         : outStr = "0" ;     // Pad leader
  //
  if ( wrk >= 10 ) outStr = "";                    // No pad needed
  //
  // Safety catch for RTC fail
  //
  ( wrk >  23 ) ? outStr  = "--:"
                            : outStr += String(wrk) + colon;     // Tick tock symbol
  //
  ( (t.min >=  0) &&
    (t.min <= 59) ) ? outStr += padIt(t.min, 0)
                                : outStr += "--";
  //
  // Send time to 8X8x4 LED matrix display if not busy
  //
  tmpStr = outStr + ":";      // Save without suffix
  if (clockMode12) ( (t.hr < 12) ? outStr += "a"
                       : outStr += "p");
  //
  if (!lmd.updateTicker() && mode)
  {
    stopLMD();
    lmd.printText(0, outStr, true);       // 0 index static text
    lmd.display();        // Display
  }
  //
  // Append seconds and show on LCD
  //
  ( ( t.sec >=  0 ) &&
    ( t.sec <= 59 ) ) ? tmpStr += padIt(t.sec, 0)
                                  : tmpStr += "--";
  //
  // Suffix a/p or blank. Blank needed if changing from A/P to 24
  //
  if (clockMode12)
  {
    (t.hr < 12) ? tmpStr += "a"
                            : tmpStr += "p" ;    // Append suffix if needed
  }
  else
  {
    tmpStr += " ";    // Pad tail if 24 hr
  }
  //
  lcd.setCursor(0, 0);   // Top left
  lcd.print(tmpStr);
  //
  // Small display gets time w/ seconds
  //
  lcdSm.setCursor(0, 0);   // Top left
  lcdSm.print(outStr);      // No seconds
  //
  //
  // At this point we're adding in the four digit LED
  // to show time on
  //
  displayRed.showNumberDec(t.min * 100 + t.sec, false); // Time on red
  displayBlue.showNumberDec(f,                  false); // Temp on blue
  //
}
// ==================================================
//
// Append to log file
//
void append(String lineOtext)
{
  // Pass a line of text. Filename is given
  //
  //  Time t = rtc.time();    // Device data type - read RTC
  //
  File aFile;
  Serial.print(F("Opening: ") );
  Serial.println(logName);
  //
  aFile = SD.open(logName, FILE_WRITE); //Returns handle or false on fail
  if (aFile)      // Success!
  {
    //
    // Only append if not blank
    //
    if (lineOtext != "")
    {
      // Echo to console and file
      aFile.print( "[" + lineOtext + "] " );
      Serial.print(  "[" + lineOtext + "] " );
    }
    //
    // Write one line
    //
    aFile.println(String(t.mon )       +   "/" +
                  String(t.date)       + " @ " +
                  padTime(t.hr, t.min) +   ":" +
                  padIt(t.sec, 0)      +   " " +
                  String(f,    1)      + "°F " +
                  String(h,    0)      +   "%" +
                  String(baro, 1)      + "mb " + trend
                 );   // Data is integer
    aFile.close();
    //
    Serial.println(F("- Log updated"));
  }
  else
  {
    Serial.print(F("Pbbbt! Failed file open of ") );
    Serial.println(logName);
  }
  //
}
// ==================================================
//
void logIt()
{
  // Pass a line of text. Filename is given
  //
  //  Time t = rtc.time();    // Device data type - read RTC
  //
  Serial.print(F("Opening: ") );
  Serial.println(logName);
  //
  aFile = SD.open(logName, FILE_WRITE); //Returns handle or false on fail
  if (aFile)      // Success!
  {
    //
    echoBoth("Daily Indoor Data: " + String(t.mon) + "/" + String(t.date) );
    echoBoth("High Temp: " + String(dailyPeakF,  1) + "°F at " + padTime(peakFHr, peakFMin ) );
    echoBoth(" Low Temp: " + String(dailyLowF,   1) + "°F at " + padTime(lowFHr,   lowFMin ) );
    echoBoth("High RH %: " + String(dailyPeakH,  0) +  "% at " + padTime(peakHHr, peakHMin ) );
    echoBoth(" Low RH %: " + String(dailyLowH,   0) +  "% at " + padTime(lowHHr,   lowHMin ) );
    echoBoth(F("Daily Outdoor Data:") );
    echoBoth("High Temp: " + String(dailyPeakFO, 1) + "°F at " + padTime(peakFHrO, peakFMinO ) );
    echoBoth(" Low Temp: " + String(dailyLowFO,  1) + "°F at " + padTime(lowFHrO,   lowFMinO ) );
    echoBoth("High RH %: " + String(dailyPeakHO, 0) +  "% at " + padTime(peakHHrO, peakHMinO ) );
    echoBoth(" Low RH %: " + String(dailyLowHO,  0) +  "% at " + padTime(lowHHrO,   lowHMinO ) );
    //
    // Baro stats here
    //
    echoBoth(F("Daily Barometric Data:") );
    echoBoth("High: " + String(hiB, 1) + "mb at " + padTime(hiBHr, hiBMin ) );
    echoBoth(" Low: " + String(loB, 1) + "mb at " + padTime(loBHr, loBMin ) );
    //
    aFile.close();
    //
    motd = "";          // force nullity
    motd = getEvent();  // see if there's a new event
    //
    if (motd != "") append("MOTD: " + motd);
    Serial.println(F("Log updated"));
  }
  else
  {
    Serial.print(F("Pbbbt! Failed file open of ") );
    Serial.println(logName);
  }
  //
  // Reset vars to current for new day
  //
  dailyPeakF  = f;
  dailyLowF   = f;
  dailyPeakH  = h;
  dailyLowH   = h;
  peakFHr     = 0;
  peakFMin    = 0;
  peakHHr     = 0;
  peakHMin    = 0;
  //
  // Out
  //
  dailyPeakFO = fO;
  dailyLowFO  = fO;
  dailyPeakHO = hO;
  dailyLowHO  = hO;
  peakFHrO    = 0;
  peakFMinO   = 0;
  peakHHrO    = 0;
  peakHMinO   = 0;
  //
  // Baro
  //
  hiB         = baro;
  loB         = baro;
  hiBHr       = 0;
  hiBMin      = 0;
  loBHr       = 0;
  loBMin      = 0;
  //
}
// ==================================================
//
// echoBoth - Send global tmpoStr to aFile and Serial.
//
void echoBoth(String tmpTxt)
{
  aFile.println(tmpTxt);
  Serial.println(tmpTxt);
}
// ==================================================
//
void LogLong(int Addr, int Valu)
{
  //
  EEPROM.update(Addr    , lowByte( Valu));
  EEPROM.update(Addr + 1, highByte(Valu));
}
// ==================================================
//
// Updates given EEPROM *BYTE* address with value, ints > 255 need TWO!
//
void logProm(int Addr, int Valu)
{
  Serial.println("EEPROM Update: " + String(Addr, HEX) + "h = " + String(Valu) + " d");
  EEPROM.update(Addr, Valu);
}
// ==================================================
//
// Temp in °F
//
void showF(String prompt, float datum)
{
  prompter(prompt);
  Serial.println(String(datum, 1) + "°F");
}
// ==================================================
//
// RH %
//
void showH(String prompt, float datum)
{
  prompter(prompt);
  Serial.println(String(datum, 2) + "%");
}
// ==================================================
//
// milliBars
//
void showMB(String prompt, float datum)
{
  prompter(prompt);
  Serial.println(String(datum, 2) + "mb");
}
// ==================================================
void prompter(String prompt)
{
  //  Time t = rtc.time();
  Serial.print(padTime(t.hr, t.min));
  Serial.print(" " + prompt + " ");
}
// ==================================================
//
// Read BME280 baro sensor
//
String readBME()
{
  //
  //#if hasBME == true
  //
  c         = sensor.getTemperature();        // Read C
  baro      = sensor.getPressure() / 100.0;   // TWO decimals
  humidity  = sensor.getHumidity();
  /*
    #else
    //read DHT-22 into same vars, fake baro
    humidity  = dhtO.readHumidity();         // Get humidity
    c         = dhtO.readTemperature(false); // Read temperature as Celsius (the default)
    //
    baro = 1013.25;   // Default sea level
    #endif
  */
  //
  f         = (c * 1.8) + 32;                 // Convert to F
  //
  h = int(humidity);
  String measurements = String(f,        1) + "°f " +
                        String(humidity, 1) +  "% " +
                        String(baro, 1)     +  "mb" ;
  //
  //  Serial.println(measurements);
  return measurements;
  //
}
// ==================================================
//
// Routine to append suffix, st, nd, rd, th
// to dates
//
String suffix(int nmbr)
{
  String sufx = "th";  // Most likely answer
  if ( (nmbr == 1) || (nmbr == 21) || (nmbr == 31) ) sufx = "st";
  if ( (nmbr == 2) || (nmbr == 22) )                 sufx = "nd";
  if ( (nmbr == 3) || (nmbr == 23) )                 sufx = "rd";
  return sufx;
}
// ==================================================
//
// Routine to show weather stats on LCD
//
// 20x4
// 00000000011111111112
// 12345678901234567890
// hh:mm:ss  xxxx.xmb - last digit is v, ^, - for direction Rise/Fall/Steady
// ** Time is updated externally to this routine, we just don't step on it.
// H: tt.t hh tt.t hh
//  : tt.t hh tt.t hh
// L: tt.t hh tt.t hh
//
// Global state flag LCDmode
//
// 0 - Display as above
// 1 - Outdoor data only
// 2 - Indoor data only
// 3 - ??? prolly some sort of status display
//
void showLCD()
{
  const float mb2in = .02953;
  //
  // lcd.clear();     // Out Causes flicker
  //
  lcd.noCursor();     // Get rid of annoying cursor
  lcdSm.noCursor();   // Get rid of annoying cursor
  //
  if (baro > 100)
  {
    //
    // Don't display if sensor isn't up
    // xxxx.xmb ^
    // xx.xx"Hg v
    //
    tmp   = (baro - lastbaro) * 10;   // Spread
    trend = " ";
    if (tmp >  0) trend = "^";
    if (tmp <  0) trend = "v";
    //
    // Local var with same name as in other subs
    //
    float actualBaro = baro + (CalBaro - (adjRange / 2) ) / 10.0 + .05;
    //
    (!tempFC) ? tmpStr = String( actualBaro,         1) +            "mb"
                         : tmpStr = String( actualBaro * mb2in, 2) + char(34) + "Hg";
    //
    tmpStr += " " + trend;
    //
  }
  else
  {
    //
    // Show nullity
    //
    tmpStr = "-------  ";
    //
  }
  //
  // Show result
  //
  lcd.setCursor ( 10, 0 );            // Near top right
  lcd.print(tmpStr);
  //
  lcdSm.setCursor (6, 0 );            // Near top right
  lcdSm.print(String(f, 1) + "f");
  //
  // Heading never changes
  //
  // Select which display mode
  switch (LCDmode)
  {
    case 1:
      //
      // Outside only
      //
      lcd.setCursor ( 0, 1 );   // 2nd row
      lcd.print(F("   Outdoor Stats"));
      //
      lcd.setCursor ( 0, 2 );   // 3rd row
      (tempFC) ? lcd.print( "h "  + padIt(dailyPeakFO, 1) +
                            "  "  + padIt(fO, 1)          +
                            " l " + padIt(dailyLowFO, 1)  + "f"
                          )
      : lcd.print( "h "  + padIt(toC(dailyPeakFO), 1) +
                   "  "  + padIt(fO, 1)          +
                   " l " + padIt(dailyLowFO, 1)  + "f"
                 );
      //
      // Repeat for humidity on next line
      //
      lcd.setCursor ( 0, 3 );   // last row
      lcd.print( "h "    + padIt(dailyPeakHO, 0) +
                 "%   "  + padIt(int(hO), 0)     +
                 "%  l " + padIt(dailyLowHO, 0)  + "%  "
               );
      //
      break;
    // ==========
    case 2:
      lcd.setCursor ( 0, 1 );   // 2nd row
      lcd.print(F("    Indoor Stats"));
      //
      // 12345678901234567890
      // Hxx.x Cxx.x Lxx.x  F
      // Hxx%  Cxx%  Lxx%   H
      //
      lcd.setCursor ( 0, 2 );   // 3rd row
      (tempFC) ? lcd.print( "h "  + padIt(dailyPeakF, 1) +
                            "  "  + padIt(f,          1) +
                            " l " + padIt(dailyLowF,  1) + "f"
                          )
      : lcd.print( "h "  + padIt(toC(dailyPeakF), 1) +
                   "  "  + padIt(c,          1) +
                   " l " + padIt(toC(dailyLowF),  1) + "c"
                 );
      //
      // Repeat for humidity on next line
      //
      lcd.setCursor ( 0, 3 );   // last row
      lcd.print( "h "    + padIt(dailyPeakH, 0) +
                 "%   "  + padIt(humidity,   0) +
                 "%  l " + padIt(dailyLowH,  0) + "%rh"
               );
      //
      break;
    // ==========
    case 3:
      lcd.setCursor ( 0, 1 );   // 2nd row
      lcd.print(F("   Summary Stats"));
      //
      // Show just current with some verbosity
      //
      lcd.setCursor ( 0, 2 );   // 3rd row
      (tempFC) ? lcd.print( "In:  "  + padIt(f, 1)        + "f  "
                            + padIt(humidity, 0) + "% rh"
                          )
      : lcd.print( "In:  "  + padIt(c, 1)        + "c  "
                   + padIt(humidity, 0) + "% rh"
                 );

      //
      lcd.setCursor ( 0, 3 );   // 4th row
      (tempFC) ? lcd.print( "Out: "  + padIt(fO, 1) + "f  "
                            + padIt(hO, 0) + "% rh"
                          )
      : lcd.print( "Out: "  + padIt(toC(fO), 1) + "c  "
                   + padIt(hO, 0) + "% rh"
                 );
      //
      break;
    // ==========
    case 4:
      //
      // Display baro stats
      //
      lcd.setCursor ( 2, 1 );   // 2nd row
      lcd.print(F("Barometric Stats"));
      //
      // baro stats display
      //
      if (!tempFC)
      {
        lcd.setCursor ( 1, 2 );   // 2nd row
        lcd.print("H: " + padTime(hiBHr, hiBMin) + " " + String(hiB, 1) + " mb");
        //
        lcd.setCursor ( 1, 3 );   // 2nd row
        lcd.print("L: " + padTime(loBHr, loBMin) + " " + String(loB, 1) + " mb");
      }
      else
      {
        lcd.setCursor ( 1, 2 );   // 2nd row
        lcd.print("H: " + padTime(hiBHr, hiBMin) + " " + String(hiB * mb2in, 2) + char(34) + " Hg ");
        //
        lcd.setCursor ( 1, 3 );   // 2nd row
        lcd.print("L: " + padTime(loBHr, loBMin) + " " + String(loB * mb2in, 2) + char(34) + " Hg ");
      }
      //
      break;
    // ==========
    default:
      //
      // hi:  tt.t hh tt.t hh
      //
      lcd.setCursor ( 0, 1 );   // 2nd row
      //
      if (tempFC)
      {
        // F leg
        tmpStr = "H: ";
        (dailyPeakF  >= 200.0) ? tmpStr += "__._f"
                                           : tmpStr += padIt(dailyPeakF,  1) + "f";
        (dailyPeakH  >= 100  ) ? tmpStr += "__% "
                                           : tmpStr += padIt(dailyPeakH,  0) + "% ";
        (dailyPeakFO ==   0.0) ? tmpStr += "__._f"
                                           : tmpStr += padIt(dailyPeakFO, 1) + "f";
        (dailyPeakHO ==   0  ) ? tmpStr += "__%"
                                           : tmpStr += padIt(dailyPeakHO, 0) + "%";
        //
        lcd.print(tmpStr);
        //
        // Cur: tt.t hh tt.t hh
        //
        lcd.setCursor ( 0, 2 );   // 3rd row
        lcd.print(" : " + padIt(f,              1 ) + "f"
                  + padIt( int(humidity), 0 ) + "% "
                  + padIt(fO,             1 ) + "f"
                  + padIt( int(hO),       0 ) + "%"
                 );
        //
        // lo:  tt.t hh tt.t hh
        //
        lcd.setCursor ( 0, 3 );   // Last row
        //
        tmpStr = "L: ";
        (dailyLowF  >= 200.0) ? tmpStr += "__._f"
                                          : tmpStr += padIt(dailyLowF,  1) + "f";
        (dailyLowH  >= 100  ) ? tmpStr += "__% "
                                          : tmpStr += padIt(dailyLowH,  0) + "% ";
        (dailyLowFO >= 200.0) ? tmpStr += "__._f"
                                          : tmpStr += padIt(dailyLowFO, 1) + "f";
        (dailyLowHO >= 100  ) ? tmpStr += "__%"
                                          : tmpStr += padIt(dailyLowHO, 0) + "%";
        //
      }
      else
      {
        // C leg
        tmpStr = "H: ";
        (dailyPeakF  >= 200.0) ? tmpStr += "__._c"
                                           : tmpStr += padIt(toC(dailyPeakF),  1) + "c";
        (dailyPeakH  >= 100  ) ? tmpStr += "__% "
                                           : tmpStr += padIt(dailyPeakH,  0) + "% ";
        (dailyPeakFO ==   0.0) ? tmpStr += "__._c"
                                           : tmpStr += padIt(toC(dailyPeakFO), 1) + "c";
        (dailyPeakHO ==   0  ) ? tmpStr += "__%"
                                           : tmpStr += padIt(dailyPeakHO, 0) + "%";
        //
        lcd.print(tmpStr);
        //
        // Cur: tt.t hh tt.t hh
        //
        lcd.setCursor ( 0, 2 );   // 3rd row
        lcd.print(" : " + padIt( c,             1 ) + "c"
                  + padIt( int(humidity), 0 ) + "% "
                  + padIt( toC(fO),       1 ) + "c"
                  + padIt( int(hO),       0 ) + "%"
                 );
        //
        // lo:  tt.t hh tt.t hh
        //
        lcd.setCursor ( 0, 3 );   // Last row
        //
        tmpStr = "L: ";
        (dailyLowF  >= 200.0) ? tmpStr += "__._c"
                                          : tmpStr += padIt(toC(dailyLowF),  1) + "c";
        (dailyLowH  >= 100  ) ? tmpStr += "__% "
                                          : tmpStr += padIt(dailyLowH,  0) + "% ";
        (dailyLowFO >= 200.0) ? tmpStr += "__._c"
                                          : tmpStr += padIt(toC(dailyLowFO), 1) + "c";
        (dailyLowHO >= 100  ) ? tmpStr += "__%"
                                          : tmpStr += padIt(dailyLowHO, 0) + "%";
        //
      }
      lcd.print(tmpStr);
      //
      break;
      // =========
  }
  // END SWITCH/CASE
}
// ==================================================
//
void limitCheck()
{
  //
  // Read clock to make vars local
  //
  //  #if oldRTC == true
  //    Time t = rtc.time();    // Device data type - read RTC
  t = rtc.time();
  //  #else
  //   t = rtc.time();  // Poll clock
  //  #endif
  //
  // baro
  //
  float actualBaro = baro + (CalBaro - (adjRange / 2) ) / 10.0 + .05;
  if ( (actualBaro > 100) &&
       (actualBaro < loB) ) //Sanity check for sleepy sensor
    //
  {
    loB    =  actualBaro;
    loBHr  =  t.hr;
    loBMin = t.min;
    showMB( F("New Lo"), actualBaro);
  }
  //
  if (actualBaro > hiB)
  {
    hiB    =  actualBaro;
    hiBHr  =  t.hr;
    hiBMin = t.min;
    showMB( F("New Hi"), actualBaro);
  }
  //
  // Temp, don't trip if temp is out of spec
  //
  if ( (f < dailyLowF) && (f != 0) )
  {
    dailyLowF =     f;
    lowFHr    =  t.hr;
    lowFMin   = t.min;
    showF( F("New Lo"), f);
  }
  //
  if (f > dailyPeakF)
  {
    dailyPeakF =     f;
    peakFHr    =  t.hr;
    peakFMin   = t.min;
    showF( F("New Hi"), f);
  }
  //
  // Repeat for humidity
  //
  if ( (humidity < dailyLowH) && (humidity != 0) )
  {
    dailyLowH = humidity;
    lowHHr    =     t.hr;
    lowHMin   =    t.min;
    showH( F("New Lo"), humidity);
  }
  //
  if (humidity > dailyPeakH)
  {
    dailyPeakH = humidity;
    peakHHr    =     t.hr;
    peakHMin   =    t.min;
    showH( F("New Hi"), dailyPeakH);
  }
  //
  // outside
  //
  if ( (fO < dailyLowFO) && (fO != 0) )
  {
    dailyLowFO =    fO;
    lowFHrO    =  t.hr;
    lowFMinO   = t.min;
    showF( F("New Out Lo"), fO);
  }
  //
  if (fO > dailyPeakFO)
  {
    dailyPeakFO =    fO;
    peakFHrO    =  t.hr;
    peakFMinO   = t.min;
    showF( F("New Out Hi"), fO);
  }
  //
  // Repeat for humidity
  //
  if ( (hO < dailyLowHO) && (hO != 0) )
  {
    dailyLowHO =    hO;
    lowHHrO    =  t.hr;
    lowHMinO   = t.min;
    showH( F("New Out Lo"), hO);
  }
  //
  if (hO > dailyPeakHO)
  {
    dailyPeakHO =    hO;
    peakHHrO    =  t.hr;
    peakHMinO   = t.min;
    showH( F("New Out Hi"), hO);
  }
  //
  // Need to add baro, inside only
  // presumption is baro doesn't vary in/out
  //
}
// ==================================================
//
// Convert to string and pad with blank if < 10
//
String padIt(float num, int dps)
{
  String tmp;
  (dps != 0) ? tmp = String(     num, 1)
                     : tmp = String( int(num)  );
  //
  if (num < 10) tmp = "0" + tmp;
  //
  return tmp;
}
// ==================================================
//
// Take Hrs and Mins and return HH:MMx or 24:00
//
String padTime(int hrs, int mins)
{
  String localTmp  = padIt( hrs, 0 ) + ":";
  String localMins = padIt(mins, 0);
  if (localMins == " 0") localMins = "00";
  return localTmp + localMins;
}
// ==================================================
//
// Clean up
//
void countLinesIn(String fileName)
{
  //
  // Crack open file and return file handle
  //
  int bytes = 0;    // Bytes read
  char aChar = ' '; // create character store
  int lines = 0;    // line count
  //
  // filename is being returned as a number?
  //
  aFile = SD.open(String(fileName)); //Returns handle or false on fail
  if (aFile)      // Success!
  {
    Serial.print(F("Reading: ") );
    Serial.println(fileName);
    //
    // read from the file until there's nothing else in it:
    //
    while (aFile.available())   // not EOF
    {
      // Byte level copy
      //
      tmpStr = readAline(aFile);
      lines++;   // CR bumps line counter
    }
    //
    // close the file
    //
    aFile.close();
    //
    // Summary to serial port
    //
    Serial.println(String(lines) + " lines read");
    //
    linesInFile = lines;      // copy to global
  }
  //
}
// ==================================================
//
// Returns random line from sales.txt file
// linesInFile is global count of # of lines in file
// determined in setup()
//
String getLine(String fileName, int numLines)
{
  int item      = random(numLines);  // Pick a card, any card, 1 offset
  if (item < 1) item = 1;
  char aChar = ' '; // create character store
  String aLine;
  //
  aFile = SD.open(fileName); //Returns handle or false on fail
  if (aFile)      // Success!
  {
    Serial.println("Searching for line #" + String(item) );
    //
    // read from the file until there's nothing else in it:
    //
    while (item > 0)
    {
      aLine = readAline(aFile);
      --item;
    }
    //
  }
  //
  // close the file no matter what path we take
  //
  aFile.close();
  //
  Serial.println(aLine);   // Copy comm port for visibility
  return aLine;
}
// ==================================================
//
// Scan events.txt file and find any entry with our date on it
//
String getEvent()
{
  //
  // Target date is m/d w/o leading zeros. A space follows and then
  // the desired text
  //
  /*
    #if oldRTC == true
    Time t = rtc.time();  // Poll 1302 clock
    #else
    t = rtc.time();  // Poll 3231 clock
    #endif
  */
  char aChar = ' '; // create character store
  String aLine;
  String findMe = String(t.mon) + "/" + String(t.date) + " ";
  //
  Serial.println("Checking for " + findMe);
  aFile = SD.open("events.txt"); //Returns handle or false on fail
  if (aFile)      // Success!
  {
    //
    // read from the file until there's nothing else in it:
    //
    aLine = "";    // Start clean
    while (aFile.available() )   // not EOF
    {
      //
      aLine = readAline(aFile);           // Get next line
      if (aLine.startsWith(findMe, 0) )   // Check for marker
      {
        //
        // We found our header.
        // We wanna trim the header
        //
        aLine = aLine.substring(findMe.length() );
        //
        break;
        // =========
      }
      //
      //      if (aLine != "") Serial.println(aLine);
      //
    }
    //
  }
  //
  // close the file no matter what path we take
  //
  aFile.close();
  //
  Serial.println("Event: " + aLine);   // Copy comm port for visibility
  return aLine;
}
// ==================================================
//
// Check to see if millis has turned over or if 5 minutes has gone by.
// Raise abort flag
//
// 1000 mS  = 1 S
//   60 S   = 1 min
//    5 min = timeOut
//  timeOut = 300000
//
int checkDeadman(unsigned long startMillis)
{
  aborted = false;    // presume falsity
  if ( (millis() <   startMillis)           ||
       (millis() >= (startMillis + timeOut ) )
     )
    //
  {
    append(F("Command Timeout:"));
    aborted = true;   //Pass up food chain in global
  }
  //
  return aborted;
}
// ==================================================
//
// readAline - Pull a line from the currently open file
//
String readAline(File aFile)
{
  String aLine = "";  // Start clean
  char aChar = ' ';   // create binary character store
  //
  while (aFile.available() )   // not EOF
  {
    //
    // Byte level copy
    //
    aChar = aFile.read();       // Binary safe
    if (aChar == EOL) break;    // Binary compare
    //
    if (aChar != 0x0A) aLine += String(aChar);     // Append as string to string & loop
    //
  }
  //
  return aLine;
}
// ==================================================
void showIt(String localStr)
{
  //
  stopLMD();
  lmd.printText(0, localStr, true);  //0 index
  lmd.display();
  //
}
// ==================================================
void stopLMD()
{
  lmd.stopTicker();     // Kill scrolling msg (pro forma)
  lmd.clear();          // Blank
}
// ==================================================
void showHold()
{
  //
  lmd.display();
  delay(100);   //Can't dillydally static display
  lmd.clear();
  //
}
// ==================================================
//
// read Prompts file
//
void readPrompts()
{
  //
  // Four fixed lines in file.
  //
  String fileName = "prompts.txt";
  File aFile;
  Serial.println("Opening " + fileName);
  aFile = SD.open(String(fileName)); //Returns handle or false on fail
  if (aFile)      // Success!
  {
    //
    // read from the file until there's nothing else in it:
    //
    Serial.print("Reading " + fileName);
    promptHr    = readAline(aFile);           // Get next line
    Serial.print(". ");
    promptWake1 = readAline(aFile);           // Get next line
    Serial.print(". ");
    promptWake2 = readAline(aFile);           // Get next line
    Serial.print(". ");
    promptWake3 = readAline(aFile);           // Get next line
    Serial.print(". ");
  }
  //
  // close the file no matter what path we take
  //
  aFile.close();
  Serial.println(fileName + " closed");
  //
  if (promptHr    == "") promptHr    = "Cuckoo! Cuckoo!"                    ;
  if (promptWake1 == "") promptWake1 = "Wakey! Wakey!"                      ;
  if (promptWake2 == "") promptWake2 = "Get up & PEE! The world's on FIRE!" ;
  if (promptWake3 == "") promptWake3 = "Outta bed, sleepy head!"            ;
  //
  // Omit the smoking reference defaults for the non-smoker
  //
}
// ==================================================
//
void waitRelease()
{
  while (!digitalRead(AckBtn))   // Wait on release
  {
    delay(100);
  }
  //
}
// ==================================================
//
void waitPress()
{
  while (digitalRead(AckBtn))   // Wait on press
  {
    delay(100);
  }
  //
}
// ==================================================
float toC(float tempF)
{
  return (tempF - 32) / 1.8;
}
// ==================================================
// ==================================================
// FIN
// ==================================================
// ==================================================
[/code]