// V505		Change P48 min value to allow for negative numbers
// C506   ADD Lidar v979
// 506 add parm 16 lock circuit
// 507 set parm 24 to 1 to use NC interlock on spare 1
// 997 add wifi
// 984 add slave adjust for current balancing
// 980 add log file stuff
// 979 add LiDar
// 508 ..979 release
// 509 ..add wdt chip test
// v510 fix multi sensor *10 bug  added long and short wdt test
// v511 fixed tone function, was causing reset. added serial command I to read unused inputs
// v512 add parm 69 to monitor rtc
// v514 add BOOT button to show motor status
// v515 add exit button check for pgm screen exit
// v550 multi door .. in process
// v516 fix access code question
// v517 add parm[15] as bug screen channel
// v518 add remote bug screen
// v519 add LIDAR test mode
// v520 kill lidar test screen timeout
// v521 add ioExpander relays to run Somfy bug screen
// v522 ad ioExpander relay test TR 00x
// v523 add WAITING FOR COMMAND recovery/ 2 motor feature
// v524 SSID and PASS update
// v525 parm 29 splash screen timeout to blank screen 0 = off
// v526 turn on fault engine
// v527 fix BOOTBUTTON OTA
// fix low batt error on startup
// v528 Fixed FTP name for OTA
//      Added serial number for label printer
//      Fixed high voltage reading on start up
// v529 Updated code to use parms.write instead of parm[xx] = xx
//      Fixed check sum error. change bte to uint8_t
// v530 Fixed LidarTimer in 485 file. Stopped reseting timer after seeing obstruction bit
// v531 Changed default value of parm[18] to 1253
//      Added display information for bug screen
// v532 Moved no display fault restore to 602 to prevent spamming of fault
//      Extended condition to run 'wifiService()' in absence of a display, originally only active in the logo screen.
// v533 Added serial check for \n or \r
//      Increased Parm 69 for rtc
//      Removed NTP code added check for BOOGYTIME
//      Bug fix - added etime to at_QtempData when pushing to cloud
// v534 Added extra Clarity screens/functions
// v535 Fixed parm 1 and 76 limits
// v536 New motor error
//      Added parms for motor timeouts
// v537 Added Clarity text for older display versions
// v538 Bug fix - Changed motor timeout to parm 31
//      Added function to use previous parms from an SD card when upgrading
//**********************************************************************************************/

#include <SPI.h>
//V111
//Removed #SD Include
//Added FileLib.h & HTTPSHandler.h
#include "FS.h"
#include "SD.h"
#include "SPI_ADI.h"
#include "HTTPSHandler.h"
#include "Timers_ADI.h"
#include "FileLib.h"
#include "IO_ADI.h"
#include "Parms_ADI.h"
#include "RF_Keys.h"
#include <esp_task_wdt.h>

bool VB = 0; // verbose mode

#define HEARTBEATLED 33
#define myVersion 117 // 525 //974
// 507 //delivered 505 protoype at Renlita

#ifndef ADI_ESP32_DEV
#error Incorrect platform definition
#endif

#define myCHAN parm[15]
#define BALANCEDEADBAND 300 // v984

bool obstructionIsSensed = 0;
bool lastObstructionPin = 0;
bool onesecond;
int addrcase;
int wiFiOK = 0;

int logicCase = 0;
int forwardCase = 0;
int DOOROPEN = 0;
int DOORCLOSE = 0;
int DOORSTOP = 0;
int DOORTOGGLE = 0;
int cDOOROPEN = 0;
int cDOORCLOSE = 0;
int cDOORSTOP = 0;

bool LASTDIRECTION = 0;

int lastCase = -1;

// float voltageNow;
// float currentNow;
char tmpstr[80];
int runLogic = 0; // start it later
int runCAN = 1;

int doorState = 0;
int lastDoorState = 1;

float voltageNow;
int displayServiceCounter = 0;
unsigned long displayMillis;

int lastBootPin;

//Added Lines & Cert For HTTPS Connection
const char *HTTPS_HOST = "appdig.com";
const char *BIN_SOURCE = "/EVOG2Test/EVOTEST.bin";
const char *UPLOAD_PARMS_DEST = "/EVOG2Test/upload.php";
const char *AUTH_HEADER = "X-Authorization: Basic YXBwZGlnOnB3ZA ==";
const char *UPDATE_BIN = "/Update.bin";

const char *serverCert =
    "-----BEGIN CERTIFICATE-----\n"
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
    "-----END CERTIFICATE-----\n"; 

HTTPSHandler https(serverCert);

uint16_t faultNum, faultData, faultAB;
int16_t lastDisplayedGraphic = -1;

//V111
//Added File LibClass For Parms SD/SPIFF
FileLibClass fileLibSPIFFS(FL_SPIFFS);  // Instance for SPIFFS
FileLibClass fileLibSD(FL_SD);

ParmsLibClass parms(fileLibSPIFFS);

void setup()
{
  Serial.begin(115200);
  delay(500);
  Serial.println("Serial Initialized");

  pinMode(HEARTBEATLED, OUTPUT); // heartbeat LED
  digitalWrite(HEARTBEATLED, HIGH);
  delay(2000);

  init_timers();
  initFaults(); // v525

  spiInit();
  oled_init();

  //Init SPIFFS AND SD & Validate They Work
  Serial.print("Version:"); Serial.println(myVersion);  // Initialize SPIFFS
  if (!fileLibSPIFFS.begin()) {
    Serial.println("Failed to initialize SPIFFS");
  } else {
    Serial.println("SPIFFS initialized");
  }
  
  fileLibSD.setVerbose(1);
  
  if (!fileLibSD.begin()) {
    Serial.println("SD FAILED TO MOUNT");
  } 
  else {
    Serial.println("SD Card initialized");
  }

  // delay(1000);
  // Serial.println(rtcGetTime());
  // print_time();

  parms.write(33, 0); // always turn off exercise
  // parm[16] = 0;

  parms.setVerbose(1);
  parms.loadFromFS();
  parms.setVerbose(0);

  if(parms.read(14) != myVersion) //New Version
    parms.write(14,myVersion);

  initIO();
  serviceIO();
  serviceIO();
  serviceIO();
  voltageNow = io_array[BATT_V];

  init485();

  if (runCAN)
    motorInit();

  delay(1000);
  initRF();
  displayText("RF Init OK");
  delay(1000);
  // sprintf(tmpstr, "Ver: %d-%d %d", myVersion, parm[1], parm[64]);
  // displayText(tmpstr);
  // Serial.println(tmpstr);
  ShowSerialScreen();

  delay(3000);
  displayText("Setup Finished");

  initWiFi();

  // esp_task_wdt_init(10, true); //enable panic so ESP32 restarts 10 seconds
  // esp_task_wdt_add(NULL); //add current thread to WDT watch

  SD_LogWrite("Power Up");

  qualifyFault(0, 0); // push startup
}

void loop()
{
  // Heartbeat
  onesecond = 0;
  if (Timers[HEARTBEAT] == 0)
    digitalWrite(HEARTBEATLED, 1);
  if (Timers[HEARTBEAT] == 2)
    digitalWrite(HEARTBEATLED, 0);
  if (Timers[HEARTBEAT] == 3)
    digitalWrite(HEARTBEATLED, 1);
  if (Timers[HEARTBEAT] == 4)
    digitalWrite(HEARTBEATLED, 0);
  if (Timers[HEARTBEAT] >= 9)
  {
    Timers[HEARTBEAT] = 0;
    onesecond = 1;
    if (isDoorIdle() == 0)
      displayVoltage();
  }

  if (onehundredmstick != 0)
  {
    onehundredmstick = 0;
    timer_update();
  }

  /*
      if (onesecond)
      {

        Serial.println();
        //uint8_t ip =  readMCP23S08(IOEXP_CS, 9);
        for (int ip = 0; ip < 8; ip++)
        Serial.printf(" %X", io_array[ip]);
              Serial.println();
      }
  */

  // if(onesecond) Serial.printf("\n ADDR = %d\n",io_array[ADDRBUTTON]);
  // if(onesecond) Serial.printf(" Pin17 = %d\n",digitalRead(17));

  // setting address
  /*
  switch (addrcase)
  {
    case 0:
      if (io_array[ADDRBUTTON] > 10) //
        addrcase++;
      break;

    case 1:
      if (io_array[ADDRBUTTON] == 0) //released it
      {
        showAddrScreen();

        addrcase++;
        Timers[BOOTPINTIMER] = 0;
      }
      break;

    case 2:
      if (Timers[BOOTPINTIMER] > 200)
        ESP.restart();

      if (digitalRead(0) == 0) //BOOT pressed, save and leave
      {
        SD_ParmWrite();
        ESP.restart();
      }

      if (io_array[ADDRBUTTON] > 10)
      {
        if (++parm[1] > 4) parm[1] = 0;
        addrcase++;
      }
      break;

    case 3:
      if (io_array[ADDRBUTTON] == 0) //released it
      {
        addrcase = 1;
      }
      break;
  }
*/

  if (digitalRead(0) == 0)
  {
    if (Timers[BOOTPINTIMER] == -1)
      Timers[BOOTPINTIMER] = 0;
    if (onesecond)
    {
      sprintf(tmpstr, "Boot button: %d\n", Timers[BOOTPINTIMER]);
      displayText(String(tmpstr));
    }
    if (Timers[BOOTPINTIMER] > 200)
    {
      if (io_array[3] == 0) // Address Setup Button
        parms.factoryReset();
      else
        beginOTA();

      Timers[BOOTPINTIMER] = -1;
    }
  }
  else
  {
    Timers[BOOTPINTIMER] = -1;
  }

  if (lastBootPin == 1) // transition
    if (digitalRead(0) == 0)
    {
      displayVoltage();
    }
  lastBootPin = digitalRead(0);

  if (lastObstructionPin == 0)
  {
    if (digitalRead(OBSTRUCTIONPIN) == 1)
    {
      obstructionCounter++;
    }
  }
  lastObstructionPin = digitalRead(OBSTRUCTIONPIN);

  if (Timers[OBSTRUCTION] >= 5) // check obstruction every .5 sec.
  {
    // Serial.print("Obstruction: ");
    // Serial.println(obstructionCounter);
    Timers[OBSTRUCTION] = 0;
    obstructionIsSensed = 0;
    // noInterrupts();
    if (obstructionCounter < 50)
    {
      obstructionIsSensed = 1;
    }
    obstructionCounter = 0;
    // interrupts();
  }

  if (runLogic == 1)
  {
    doorLogic();
    if (parm[28] == 1)
      screenLogic(); // v521
  }

  if (parm[26] == 1) // v518 remote screen operation
  {

    if (exOptoTransition[2] == 1)
    {
      displayText("HA Screen UP");
      rfSendKey(RF_KEY_UP, myCHAN);
      exOptoTransition[2] == 0;
    }
    if (exOptoTransition[3] == 1)
    {
      displayText("HA Screen DOWN");
      rfSendKey(RF_KEY_DN, myCHAN);
      exOptoTransition[3] == 0;
    }
  }

  serviceIO();
  voltageNow = io_array[BATT_V];

  // drop motor watchdog
  if (motorGetPosition(0) != 9999)
    if (motorGetPosition(1) != 9999)
      io_array[POWER_RLY] ^= 1;

  // comms dropped on a motor
  if (motorGetPosition(0) == 9999)
    setMotorFault(0);
  if (motorGetPosition(1) == 9999)
    setMotorFault(1);

  if (runCAN)
    motorTask();

  if (parm[1] == 0)
    inSerial2();
  else
    runSlave();

  if (millis() - displayMillis >= 20) // 988  50
  {
    if (parm[1] == 0)
      serviceDisplay(); // master

    displayMillis = millis();
  }

  /*

    if (Timers[DISPLAYSERVICETIMER] >= 1)
    {
    Timers[DISPLAYSERVICETIMER] = 0;
    serviceDisplay();
    }
  */
  if (Timers[TENSECOND] >= 100)
  {

    Timers[TENSECOND] = 0;
    runLogic = 1;

    sprintf(tmpstr, "*********************  M0: %d M1: %d\n", motorGetPosition(0), motorGetPosition(1));
    Serial.println(tmpstr);
    sprintf(tmpstr, "Obstruction: %d\n", obstructionIsSensed);
    Serial.println(tmpstr);

    Serial.printf("Voltage: %d,  %d\n", io_array[BATT_V], analogRead(BATTERYINPUTPIN));

    Serial.print("Logic Case: ");
    Serial.println(logicCase);
  }

  if (Timers[AT200DATA] >= parm[23]) // check in
  {
    Timers[AT200DATA] = 0;
    pushCheckin();
    pushDoorStatus();
  }

  rfTask();
  checkSerial();

  if (Timers[VOLTAGETIMER] >= 100) // 10 seconds into travel
  {
    Timers[VOLTAGETIMER] = -1;
    pushCheckin();
  }

  if (doorState != lastDoorState)
  {
    pushDoorStatus();
    lastDoorState = doorState;

    sprintf(tmpstr, "Max Current M: %d.%d S: %d.%d\n", getMaxCurrent(0) / 10, getMaxCurrent(0) % 10, getMaxCurrent(1) / 10, getMaxCurrent(1) % 10);
    Serial.print(tmpstr);
    SD_LogWrite(tmpstr);
    pushMaxCurrents();
  }

  if (isDoorIdle() == 0) // door is busy
    Timers[ISIDLETIMER] = 0;

  if (Timers[ISIDLETIMER] > 10)
    if (Timers[ISIDLETIMER] < 12)
    {
      motorStop();
      displayText("Motor Stop");
      Timers[ISIDLETIMER] = 13;
    }

  //******WiFi Service master

  if (parm[1] == 0) // master
  {
    wiFiOK = 0;
    if (Timers[ISIDLETIMER] > 100) // been idle more than 10 seconds
      if (parm[9] == 1)            // WiFi enabled
      {
        if (lastDisplayedGraphic == 8)
        {
          wiFiOK = 1; // LOGOSCREEN?
        }

        // In logo screen or no display screen
        if (currentDisplayCase() == 22 || currentDisplayCase() == 23 || currentDisplayCase() == 600 || currentDisplayCase() == 602)
        {
          if (Timers[WIFISERVICE] >= parm[22])
          {
            wifiService();
            Timers[WIFISERVICE] = 0;
          }
          if (Timers[AT200DATA] >= parm[23]) // check in
          {
            Timers[AT200DATA] = 0;
            pushCheckin();
            pushDoorStatus();
          }
        }
      }
  }

  //******WiFi Service slave
  if (parm[1] != 0) // slave
  {
    if (Timers[ISIDLETIMER] > 100) // been idle more than 10 seconds
      if (parm[9] == 1)            // WiFi enabled
        if (wiFiOK == 1)
        {
          if (Timers[WIFISERVICE] >= parm[22])
          {
            wifiService();
            Timers[WIFISERVICE] = 0;
          }
          if (Timers[AT200DATA] >= parm[23]) // check in
          {
            Timers[AT200DATA] = 0;
            pushCheckin();
            pushDoorStatus();
          }
        }
  }

  if (onesecond)
    if (VB)
      Serial.printf("WiFiOK -> %d, ISIDLETIMER -> %d\n", wiFiOK, Timers[ISIDLETIMER]);

  // if (onesecond) Serial.printf("Voltage Timer: %d\n", Timers[VOLTAGETIMER]);
  if (Timers[OLEDBLANKTIMER] > 1200) // v1653 to blank oled
  {
    displayText("");
    Timers[OLEDBLANKTIMER] = 0;
  }

  if (Timers[OLEDHOLDTIMER] > 30000)
    Timers[OLEDHOLDTIMER] = 2000;
}

int isDoorIdle(void)
{
  if (
      (logicCase == 1) ||
      (logicCase == 2) ||
      (logicCase == 20) ||
      (logicCase == 10))
    return (1);
  return (0);
}

void doorLogic(void)
{
  if (parm[25] == 0) // v995
  {
    DOOROPEN = 0;
    DOORCLOSE = 0;
    DOORSTOP = 0;
    DOORTOGGLE = 0;
  }

  if (parm[26] == 1)
  {
    DOOROPEN = exOpto[0];
    DOORCLOSE = exOpto[1];
  }
  else
  {
    DOOROPEN = 0;
    DOORCLOSE = 0;
  }

  if (parm[25])
    if (onesecond)
      Serial.printf("ioExpander %d %d %d %d\n", exOpto[0], exOpto[1], exOpto[2], exOpto[3]);

  if (io_array[RF_KEYPAD] > 10) // door toggle
    DOORTOGGLE = 1;

  DOORTOGGLE = 0; // dstest
  /*
    if (io_array[JOG_EXTEND] < 50)
    {
      logicCase = 50;
    }
    if (io_array[JOG_RETRACT] < 50)
    {
      logicCase = 51;
    }
  */

  if (LowBattery() == 0)
    if (parm[33] >= 6000)
      runExercise();

  if (parm[16] == 1)
    io_array[SPARE_OUT] = 0; // v506

  // v507 add interlock spare1
  if ((parm[24] != 0) && (io_array[SPARE_IN] == 0))
    logicCase = 0;

  switch (logicCase)
  {
  //**********
  case 0: // init
    logicCase++;
    // io_array[EXTEND] = 0;
    // io_array[RETRACT] = 0;

    motorStop();
    // doorState = 0;
    break;

  case 1: // make sure all buttons are clear

    if ((DOOROPEN == 0) &&
        (DOORCLOSE == 0) &&
        (cDOOROPEN == 0) &&
        (cDOORCLOSE == 0) &&
        (cDOORSTOP == 0))
      logicCase++;
    break;

  case 2: // idle   //don't know where we are
    // doorState = 0; //v987

    if ((DOOROPEN) || (cDOOROPEN)) // door open
      logicCase = 30;
    if ((DOORCLOSE) || (cDOORCLOSE)) // door close
      logicCase = 40;
    /*
          if (DOORTOGGLE) //door toggle from Spare_IN1 input
          {
            if (!LASTDIRECTION) logicCase = 30;
            if (LASTDIRECTION)  logicCase = 40;
          }
    */
    break;

  //****OPEN
  case 30:             // we gonna open
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    displayText("OPENING");
    logicCase = 31;
    io_array[COMPRESSOR] = 0; // release compressor
    Timers[GASKETTIMER] = 1;

    // if (io_array[EXT_LIMIT] != 0)
    //   logicCase = 10; //duh, already open
    setCurrentBalance(0);
    break;

  case 31:             //
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;
    if (Timers[GASKETTIMER] > parm[2]) // wait for COMPRESSOR delay
      logicCase = 32;
    break;

  case 32:
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    Timers[DOORTRAVELTIMER] = 1; // start max timer for overall travel
    logicCase = 33;

    // parms.write(11, parm[11] + 1); //cycle counter
    // SD_ParmWrite();

    break;

  case 33:
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;
    Serial.println("Begin Open");
    Timers[VOLTAGETIMER] = 0;
    // MOVEDIRECTION = 1;

    // if (motorGetPosition(0) > parm[40]) MOVEDIRECTION = 0; //we above it.. need to travel down
    motorMove(2999);

    // io_array[EXTEND] = 1;//let's open it
    logicCase = 34;
    LASTDIRECTION = 1;
    break;

  case 34:
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;
    logicCase++;
    /*
      if (Timers[DOORTRAVELTIMER] > parm[5]) //must be fully open
      {
        logicCase = 35;
      }
    */
    if ((DOORSTOP) || (cDOORSTOP))
      logicCase = 0;
    if (DOORTOGGLE)
      logicCase = 0;

    break;

  case 35:             //
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;
    // io_array[EXTEND] = 0;
    // io_array[RETRACT] = 0;//let's close it
    Timers[DOORTRAVELTIMER] = 1;
    // logicCase = 60;
    // forwardCase = 36;

    logicCase++;
    break;

  case 36:
    // io_array[EXTEND] = 0;
    // io_array[RETRACT] = 1;//let's close it to stop

    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    logicCase = 37;

    break;

  case 37:
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    if (Timers[DOORTRAVELTIMER] > parm[6]) // just in case
    {
      logicCase = 0;
      Serial.println("Open Travel Timeout");
      displayText("Open Timeout");
      pushDoorStatus();
    }

    // v984 balance current while running
    if (motorGetPosition(0) < (parm[40] - BALANCEDEADBAND))
    {
      setCurrentBalance(parm[49]);
    }
    else
    {
      setCurrentBalance(0);
    }

    // finished
    if (motorGetPosition(0) >= parm[40])
    {
      logicCase = 10;
      Serial.printf("Open Limit reached %d - %d\n", motorGetPosition(0), parm[40]);
      displayText("Open Limit reached");
      motorStop();
      parms.write(11, parm[11] + 1); // cycle counter
    }

    /*
          if (io_array[EXT_LIMIT] > 5)//v108 .. was 10
          {
            logicCase = 10;
            Serial.println("Limit reached");
          }
    */

    if ((DOORSTOP) || (cDOORSTOP))
      logicCase = 0;
    if (DOORTOGGLE)
      logicCase = 0;
    break;

  //****CLOSE
  case 40: // we gonna close

    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    logicCase = 42;
    displayText("CLOSING");
    Serial.println("CLOSING");

    /*             if (io_array[RET_LIMIT] > 10)
                   logicCase = 20; //duh, already closed
    */

    break;

  case 42:
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    Timers[DOORTRAVELTIMER] = 1; // start max timer for overall travel
    logicCase = 43;
    break;

  case 43:
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    // io_array[RETRACT] = 1;//let's close it
    Serial.println("Begin Close");
    Timers[VOLTAGETIMER] = 0;
    motorMove(10);
    logicCase = 46;
    LASTDIRECTION = 0;
    break;

  case 46:
    forwardCase = 30;
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    if (Timers[DOORTRAVELTIMER] > parm[5]) // just in case
    {
      logicCase = 20;
      pushDoorStatus();
      displayText("Close Timeout");
    }

    // v984 balance current while running

    if (motorGetPosition(0) > (parm[39] + BALANCEDEADBAND))
    {
      setCurrentBalance(parm[50]);
    }
    else
    {
      setCurrentBalance(0);
    }

    if (motorGetPosition(0) <= parm[39])
    {
      logicCase = 20;
      motorStop();
      displayText("Limit reached");
      Serial.println("Limit reached");
    }

    if (parm[54] == 0)                                                          // no LiDar v979
      if ((DOORSTOP) || (cDOORSTOP) || ((cDOORCLOSE == 0) && (DOORCLOSE == 0))) // v995
      {
        // forwardCase = 30; //to reverse
        // logicCase = 60;
        logicCase = 0;
      }

    if (parm[54] == 1) // use LiDar v979
      if ((DOORSTOP) || (cDOORSTOP))
      {
        logicCase = 0;
      }

    if (parm[4] != 0)
    {
      if (obstructionIsSensed == 1)
      {
        forwardCase = 30;
        logicCase = 60;
        Serial.println("Photo Obstruction!! Change Direction");
        showObstructionScreen();
        faultNum = 1;
        faultAB = 0;
        faultData = 0;
      }
    }

    if (Timers[LIDARTIMER] > 30)
    {
      if (parm[54] != 0) // use Lidar
      {

        // check for deadzones at ends
        if (motorGetPosition(0) <= (parm[39] + parm[66]))
          clearLidarObstruction(); // 200 up from closed
        if (motorGetPosition(0) >= (parm[40] - parm[65]))
          clearLidarObstruction(); // 400 down from open

        if (checkLidarObstruction())
        {
          forwardCase = 30;
          logicCase = 60;
          Serial.println("Lidar Obstruction!! Change Direction");
          qualifyFault(35, 0);
          restoreFault(35);
          showObstructionScreen();
          faultNum = 5;
          faultAB = 0;
          faultData = 0;
        }
      }
    }
    else
    {
      clearLidarObstruction();
    }

    if (DOORTOGGLE)
    {
      forwardCase = 30;
      logicCase = 60;
    }
    break;

  case 60:             // Reverse delay
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    Timers[DOORTRAVELTIMER] = 0;
    displayText("Reverse");
    motorStop();
    // io_array[EXTEND] = 0;
    // io_array[RETRACT] = 0;
    logicCase++;
    break;

  case 61:
    if (parm[16] == 1) // v506
      io_array[SPARE_OUT] = 1;

    if (Timers[DOORTRAVELTIMER] > 10)
      logicCase = forwardCase;
    break;

  //***********
  case 10: // door is open
    doorState = 1000;
    logicCase = 0;

    // if ((DOORCLOSE) || (cDOORCLOSE))logicCase = 40;
    // if (DOORTOGGLE) logicCase = 40;

    // io_array[EXTEND] = 0;
    // io_array[RETRACT] = 0;
    break;

  //***********
  case 20: // door is closed
    doorState = 0;
    logicCase = 0;
    io_array[COMPRESSOR] = 1; // compressor on

    if ((DOOROPEN) || (cDOOROPEN))
      logicCase = 30;
    if (DOORTOGGLE)
      logicCase = 30;
    // io_array[EXTEND] = 0;
    // io_array[RETRACT] = 0;
    break;

  default:
    logicCase = 0;

    Serial.println("entered door logic default case");
    break;
  }

  if (isDoorIdle() == 0)
    if (parm[27] != 0)                  // turn on current limit
      if (Timers[DOORTRAVELTIMER] > 30) // if we have been running over 3 seconds
      {
        if (motorGetCurrent(0) > parm[27])
        {
          sprintf(tmpstr, "M Overcurrent %d", motorGetCurrent(0));
          displayText(String(tmpstr));
          motorStop();
          parms.write(33, 0);
          logicCase = 0;
          // delay(1000);
        }
        if (motorGetCurrent(1) > parm[27])
        {
          sprintf(tmpstr, "S Overcurrent %d", motorGetCurrent(1));
          displayText(String(tmpstr));
          motorStop();
          parms.write(33, 0);
          logicCase = 0;
          // delay(1000);
        }
      }

  // if (VB)
  if (lastCase != logicCase)
  {
    Serial.print("Logic Case: ");
    Serial.println(logicCase);
    lastCase = logicCase;
  }
}

void runExercise(void)
{
  if (Timers[EXERCISETIMER] < 0)
    Timers[EXERCISETIMER] = 0; // start it
  if (Timers[EXERCISETIMER] > parm[33])
    Timers[EXERCISETIMER] = 0; // start over

  if (cDOORSTOP != 0) // cancel the whole thing
  {
    Timers[EXERCISETIMER] = -1; // stop it
    parms.write(33, 0);
    return;
  }

  // open
  cDOOROPEN = 0;
  if (Timers[EXERCISETIMER] > 20)
    if (Timers[EXERCISETIMER] < 30) // let's open
      cDOOROPEN = 1;

  // close
  cDOORCLOSE = 0;
  if (Timers[EXERCISETIMER] > (parm[33] / 2)) // let's close halfway through timeout
    if (Timers[EXERCISETIMER] < (parm[33] - 30))
      cDOORCLOSE = 1;

  if (onesecond)
    Serial.printf("Exercise timer: %d\n", Timers[EXERCISETIMER]);
}

void checkSerial()
{
  static String fromSerial; // needs to keep its value between calls
  int pnum;
  uint32_t pdata, edata;

  // if something is available - make sure you have line feed enabled
  if (Serial.available())
  {
    char c = Serial.read();
    if (c == '\n' || c == '\r')
    {
      if (fromSerial.length() > 0)
      {
        Serial.println(fromSerial); // for debugging, just echo back what you got
        fromSerial = "";            // Clear the string for the next line
      }
    }
    else
    {
      fromSerial += c; // Append the received character to the string
    }

    // parm write  PW ppp 1234567
    if (fromSerial[0] == 'P' && fromSerial[1] == 'W' && fromSerial.length() == 14)
    { // writing parm - must match length

      // first pull down the string, convert from ASCII to decimal, and combine the bytes
      pnum = ((fromSerial[3] - 48) * 100) + ((fromSerial[4] - 48) * 10) + ((fromSerial[5] - 48) * 1);

      pdata = ((fromSerial[7] - 48) * 1000000) +
              ((fromSerial[8] - 48) * 100000) +
              ((fromSerial[9] - 48) * 10000) +
              ((fromSerial[10] - 48) * 1000) +
              ((fromSerial[11] - 48) * 100) +
              ((fromSerial[12] - 48) * 10) +
              ((fromSerial[13] - 48) * 1);

      if (VB == 1)
      {
        Serial.print("Attempt Parm#  ");
        Serial.print(pnum);
        Serial.print(" = ");
        Serial.println(pdata);
      }

      parms.write(pnum, pdata);
    }

    // parm read  PR ppp
    if (fromSerial[0] == 'P' && fromSerial[1] == 'R' && fromSerial.length() == 6)
    { // reading parm - must match length

      // first pull down the string, convert from ASCII to decimal, and combine the bytes
      pnum = ((fromSerial[3] - 48) * 100) + ((fromSerial[4] - 48) * 10) + ((fromSerial[5] - 48) * 1);

      Serial.print("Parm#  ");
      Serial.print(pnum);
      Serial.print(" = ");
      Serial.println(parm[pnum]);

      if (pnum == 64) // serial# for label printer
        Serial.printf("serial: %d\n", parm[64]);

      delay(2000);
    }

    // toggle ioExpander relays
    if (fromSerial[0] == 'T' && fromSerial[1] == 'R' && fromSerial.length() == 6)
    { // reading parm - must match length

      // first pull down the string, convert from ASCII to decimal, and combine the bytes
      pnum = ((fromSerial[3] - 48) * 100) + ((fromSerial[4] - 48) * 10) + ((fromSerial[5] - 48) * 1);

      if (pnum < 8)
      {
        if (exRly[pnum])
          exRly[pnum] = 0;
        else
          exRly[pnum] = 1;

        Serial.printf("ioExpander Relay %d = %d\n", pnum, exRly[pnum]);
      }
      else
        Serial.println("Invalid relay#");

      delay(2000);
    }

    if (fromSerial[0] == 'I') // V511 read unused inputs
    {
      Serial.printf("Retract Switch %d\n", io_array[RET_LIMIT]);
      Serial.printf("Extend Switch %d\n", io_array[EXT_LIMIT]);
      Serial.printf("Open Limit Switch %d\n", io_array[OPEN_LIMIT]);
      Serial.printf("RF Keypad %d\n", io_array[RF_KEYPAD]);
      Serial.printf("Spare Input %d\n", io_array[SPARE_IN]);
    }

    /*
        //List Directory
        if (fromSerial[0] == 'L' && fromSerial[1] == 'D')
          listDir("/", 3);

        //Create Directory
        if (fromSerial[0] == 'C' && fromSerial[1] == 'D')
          createDir("/data");

            //Move File
            if (fromSerial[0] == 'M' && fromSerial[1] == 'F')
              renameFile("/update.bin", "/data/update.old");

        //Save Queue
        //    if (fromSerial[0] == 'S' && fromSerial[1] == 'Q')
        //    save_aQbuffer();

        //Battery A2D
        //if (fromSerial[0] == 'B' && fromSerial[1] == 'D')
        //{
        //  Serial.print("Battery A2D: ");
        //  Serial.println(analogRead(BATTERYINPUTPIN));
        //}


            //Upload Data
            if (fromSerial[0] == 'U' && fromSerial[1] == 'D')
             {
              if(uploadFtpFile("/50-4-10-13-25-35.atQ","/httpdocs/aQData","50-4-10-13-25-35.atQ"))
                renameFile("/50-4-10-13-25-35.atQ", "/data/50-4-10-13-25-35.uld"); // effectivley.. this moves it to another dir as well
             }
    */
    // Toggle Spare
    if (fromSerial[0] == 'T' && fromSerial[1] == 'S' && fromSerial.length() == 2)
    { // reading
      if (io_array[SPARE_OUT])
        io_array[SPARE_OUT] = 0;
      else
        io_array[SPARE_OUT] = 1;

      Serial.printf(" Spare = %d\n", io_array[SPARE_OUT]);
    }

    // Epoch read  ER
    if (fromSerial[0] == 'E' && fromSerial[1] == 'R' && fromSerial.length() == 2)
    { // reading
      Serial.printf("RTC Epoch Time: %u\n", getEpochRtc());
    }

    // Timer Read
    if (fromSerial[0] == 'T' && fromSerial[1] == 'R' && fromSerial.length() == 6)
    { // reading parm - must match length

      // first pull down the string, convert from ASCII to decimal, and combine the bytes
      pnum = ((fromSerial[3] - 48) * 100) + ((fromSerial[4] - 48) * 10) + ((fromSerial[5] - 48) * 1);

      Serial.print("Timer#  ");
      Serial.print(pnum);
      Serial.print(" = ");
      Serial.println(Timers[pnum]);
    }

    // Epoch write  EW 1234567890
    if (fromSerial[0] == 'E' && fromSerial[1] == 'W' && fromSerial.length() == 13)
    {
      edata =
          ((fromSerial[3] - 48) * 1000000000) +
          ((fromSerial[4] - 48) * 100000000) +
          ((fromSerial[5] - 48) * 10000000) +
          ((fromSerial[6] - 48) * 1000000) +
          ((fromSerial[7] - 48) * 100000) +
          ((fromSerial[8] - 48) * 10000) +
          ((fromSerial[9] - 48) * 1000) +
          ((fromSerial[10] - 48) * 100) +
          ((fromSerial[11] - 48) * 10) +
          ((fromSerial[12] - 48) * 1);

      setEpochRtc(edata);
    }
  }
}

// v521 screen stuff

int screenCase = 0;
bool SCREENOPEN = 0;
bool SCREENCLOSE = 0;
bool SCREENSTOP = 0;

#define SCREEN_UP 0
#define SCREEN_DN 1
#define SCREEN_STP 2

void screenLogic(void)
{
  // Serial.print(screenCase); Serial.print("  "); Serial.println(Timers[SCREENTIMER]);
  switch (screenCase)
  {
  case 0:
    screenCase = 1;
    exRly[SCREEN_UP] = 0;
    exRly[SCREEN_DN] = 0;
    exRly[SCREEN_STP] = 0;
    SCREENOPEN = 0;
    SCREENCLOSE = 0;
    SCREENSTOP = 0;
    break;

  case 1: // idle
    if (SCREENOPEN)
    {
      exRly[SCREEN_UP] = 1;
      Timers[SCREENTIMER] = 1;
      screenCase = 2;
    }
    if (SCREENCLOSE)
    {
      exRly[SCREEN_DN] = 1;
      Timers[SCREENTIMER] = 1;
      screenCase = 2;
    }
    if (SCREENSTOP)
    {
      exRly[SCREEN_STP] = 1;
      Timers[SCREENTIMER] = 1;
      screenCase = 2;
    }
    break;

  case 2:
  {
    if (Timers[SCREENTIMER] > parm[3]) // screen button timeout
      screenCase = 0;
  }
  break;

  default:
    screenCase = 0;
    break;
  }
}

int relayTest;
void ioTest(void)
{
  int ii;

  for (ii = 0; ii < 4; ii++)
  {
    exRly[ii] = 0;
  }
  exRly[relayTest] = 1;
  if (++relayTest >= 4)
    relayTest = 0;
  for (ii = 0; ii < 4; ii++)
  {
    Serial.print(" In");
    Serial.print(exOpto[ii]);
    Serial.print(" Out");
    Serial.print(exRly[ii]);
  }
  Serial.println();
}

void tone(byte pin, int freq, int duration)
{
  int CHANNEL = 1;
  ledcSetup(CHANNEL, freq, 8); // V511 was ledcSetup(pin,freq,8)
  ledcAttachPin(pin, CHANNEL);
  ledcWrite(CHANNEL, 128); // 50% duty cycle
  delay(duration);
  ledcWrite(CHANNEL, 0); // turn off pwm
  ledcDetachPin(pin);
}
