Assistance with HTML and variables

I am having trouble adopting some code I have from Resinchem Tech's parking sensor project, he has some webpages that contain input fields and labels, using a table to format the objects without any visible lines.

I am trying to make it work and so far have a little but now when I want to add functionality I dont understand enough of the variables, their types and where they can be reused this is the error I am getting:

/Users/leo/Documents/Arduino/TDSMonitor_WifiMan_MQTT_v1/TDSMonitor_WifiMan_MQTT_v1.ino:886:40: error: invalid operands of types 'const char [21]' and 'float' to binary 'operator+'
   calSensors += "Current Supply TDS: " + tdsValue_SUP + "<br>";

tdsValue is referenced in these sections of my code and I have shared other ares where I have used it without any errors (NOTE: this is the code snippets that reference the variable in question, its sections pulled apart that work independently in the code, it isnt the whole project code)

float temperature = 25;
float tdsValue_SUP = 0;
float tdsValue_PRE_DI = 0;
float tdsValue_OUT = 0;

int prevtdsValue_SUP;
int prevtdsValue_PRE_DI;
int prevtdsValue_OUT;

//send baseline TDS reading to MQTT
sprintf(mqtt_tds_SUP, "%d",(int)tdsValue_SUP);  //this gives me the number but the rounding isnt as good as the serial print version with tdsValue_SUP, 0
client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
Serial.println("Supply TDS Baseline Telemetry Sent Successfully!");


void GravityTDS_results(){
//Calculate TDS from Supply Water
gravityTds_SUP.setTemperature(temperature);   // set the temperature and execute temperature compensation
gravityTds_SUP.update();                      //sample and calculate
tdsValue_SUP = gravityTds_SUP.getTdsValue();  // then get the value

if(tdsValue_SUP == prevtdsValue_SUP) {
Serial.print("Supply TDS reading hasnt changed (Current/Last Read): ");
Serial.print(tdsValue_SUP, 0); Serial.print("ppm"); //this gives me the number I want to see
Serial.print(" : ");
Serial.print(prevtdsValue_SUP); Serial.println("ppm");
delay(100);
//  return;
} else {
prevtdsValue_SUP = tdsValue_SUP;
Serial.print("SUP TDS: "); Serial.print(tdsValue_SUP, 0); Serial.println("ppm");
sprintf(mqtt_tds_SUP, "%d",(int)tdsValue_SUP);
client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
sprintf(mqtt_tds_SUP2, "TDS-SUP: %d",(int)tdsValue_SUP);
client.publish(mqtt_pub_topic_supTDS, mqtt_tds_SUP2);
Serial.println("Supply TDS Telemetry Sent Successfully!");
delay(100);
}

This is the full snippet of the HTML page, its purpose is intended to:
a) show current values the TDS meter is reading
b) have text input boxes where I can enter a number
c) when I press the button to calibrate sensors I will want to have that value in a variable I can use with the calibration function built into the GravityTDS library

// Settings submit handler - Settings results
void calibration() {

  String saveCalibration;


  //Need to get the TDS Value to display as a label on line 884
  web_currSUPPLY = (int)tdsValue_SUP;
  web_currPREDI = (int)tdsValue_PRE_DI;
  web_currOUT = (int)tdsValue_OUT;
  web_calSUPPLY = server.arg("web_calsupply").toInt();
  web_calPREDI = server.arg("web_calpredi").toInt();
  web_calOUT = server.arg("web_calout").toInt();

  saveCalibration = server.arg("calsave");

  String calSensors = "<html>\
    </head>\
      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
      <title>TDS Monitor - Sensor Calibration</title>\
      <style>\
        body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
      </style>\
    </head>\
    <body>\
    <H1>Calibrate TDS Sensor</H1><br>\
    <H3>Current values are:</H3>";

  //these are labels  
  calSensors += "<b>TDS Sensors</b><br><br>";
  calSensors += "Current Supply TDS: " + tdsValue_SUP + "<br>";
  calSensors += "Current Pre-DI TDS: " + tdsValue_PRE_DI + "<br>";
  calSensors += "Current Output TDS: " + tdsValue_OUT + "<br>"  ;

  //these are input boxes
  calSensors += "<table>\
      <tr>\
      <td><label for=\"web_calsupply\">Calibrated Supply TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"web_calsupply\" style=\"width: 50px\;\" value=\"";
  calSensors += String(web_currSUPPLY);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"web_calpredi\">Calibrated Pre-DI TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"web_calpredi\" style=\"width: 50px\;\" value=\"";
  calSensors += String(web_currPREDI);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"web_calout\">Calibrated Output TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"web_calout\" style=\"width: 50px\;\" value=\"";
  calSensors += String(web_calOUT);
  calSensors += "\"> ppm<br></td></tr>\"";
  calSensors += "<br>";
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "/discovery\">Configure Home Assistant MQTT Discovery</a><br>"; 
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "\">Return to settings</a><br>";

  calSensors += "</td></tr>\
        </table><br>\
        <input type=\"checkbox\" name=\"calsave\" value=\"save\">Store Calibration and Replace Defaults (Controller Will Reboot)<br><br>\
        <input type=\"submit\" value=\"Submit Calibration\">\
      </form>\
      <br>\
      <h2>Controller Commands</h2>\
      Caution: Restart and Reset are executed immediately when the button is clicked.<br>\
      <table border=\"1\" cellpadding=\"10\">\
      <tr>\
      <td><button id=\"btnrestart\" onclick=\"location.href = './restart';\">Restart</button></td><td>This will reboot controller and reload default boot values.</td>\
      </tr><tr>\
      <td><button id=\"btnreset\" style=\"background-color:#FAADB7\" onclick=\"location.href = './reset';\">RESET ALL</button></td><td><b>WARNING</b>: This will clear all settings, including WiFi! You must complete initial setup again.</td>\
      </tr><tr>\
      <td><button id=\"btnupdate\" onclick=\"location.href = './update';\">Firmware Upgrade</button></td><td>Upload and apply new firmware from local file.</td>\
      </tr></table><br>\
      Current version: VAR_CURRRENT_VER\
    </body>\
  </html>";

  calSensors.replace("VAR_DEVICE_NAME", deviceName); 
  calSensors.replace("VAR_CURRRENT_VER", VERSION);
  server.send(200, "text/html", calSensors);
  delay(1000);
}

Post the full sketch and the full error report.

//Include Libraries
#include <Arduino.h>
#include <FS.h>           //Arduino ESP8266 Core - Handles filesystem functions (read/write config file)
#include <LCD_I2C.h>
#include <FastLED.h>    //https://github.com/FastLED/FastLED - LED functionality
#include <EEPROM.h>
#include "GravityTDS.h"
#include <Arduino.h>
#include <WiFiManager.h>  //https://github.com/tzapu/WiFiManager (must be v2.0.8-beta or later) - Wifi Onboarding with Portal
#include <PubSubClient.h> //https://github.com/knolleary/pubsubclient  Provides MQTT functions
#include <ArduinoJson.h>  //https://github.com/bblanchon/ArduinoJson
#include <WebServer.h>    //Used for HTTP callback to enable/disable discovery
#include <HTTPUpdateServer.h>
#include <ArduinoOTA.h>   //https://github.com/jandrassy/ArduinoOTA
#include <ESPmDNS.h>
#include <Update.h>

#ifdef ESP32
  #include <SPIFFS.h>
#endif

#define VERSION "Firebeetle 2 (ESP32)"

//Setup RGB LED for Feedback
// Only one led is connected. The led is connected on IO5
//#define NUM_LEDS 1
//#define DATA_PIN 5
#define LED_DATA_PIN 5
#define WIFIMODE 2                       // 0 = Only Soft Access Point, 1 = Only connect to local WiFi network with UN/PW, 2 = Both
#define MQTTMODE 1                       // 0 = Disable MQTT, 1 = Enable (will only be enabled if WiFi mode = 1 or 2 - broker must be on same network)
//#define SERIAL_DEBUG 0                   // 0 = Disable (must be disabled if using RX/TX pins), 1 = enable
#define NUM_LEDS_MAX 1                   // For initialization - recommend actual max 50 LEDs if built as shown

//Define PinOut
#define TDS_SUP_Pin A0
#define TDS_PRE_DI_Pin A1
#define TDS_OUT_Pin A2

//CRGB leds[NUM_LEDS];

//PubSubClient
WiFiClient espClient;
PubSubClient client(espClient);

//WebServer
WebServer server(80);
//WebServer server;
HTTPUpdateServer httpUpdater;

//const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
const char* host = "TDSMonitor-webupdate";

const char* mqtt_server = "10.0.0.8";
//const char* mqtt_port = "1883";
const char* mqtt_client = "TDSMonitor";
const char* mqtt_pub_topic_supTDS = "Aquarium/TDSMonitor/Sensor/supTDS";
const char* mqtt_pub_topic_prediTDS = "Aquarium/TDSMonitor/Sensor/prediTDS";
const char* mqtt_pub_topic_outTDS = "Aquarium/TDSMonitor/Sensor/outTDS";
const char* mqtt_sub_topic = "Aquarium";
const char* mqtt_supTDS_stat = "homeassistant/stat/TDSMonitor/supTDS";
const char* mqtt_prediTDS_stat = "homeassistant/stat/TDSMonitor/prediTDS";
const char* mqtt_outTDS_stat = "homeassistant/stat/TDSMonitor/outTDS";
const char* mqtt_IP_stat = "homeassistant/stat/TDSMonitor/ipaddress";

//Auto-discover enable/disable option
bool auto_discovery = false;      //default to false and provide end-user interface to allow toggling  

//Arduino OTA
bool ota_flag = true;                       // Must leave this as true for board to broadcast port to IDE upon boot
uint16_t ota_boot_time_window = 2500;       // minimum time on boot for IP address to show in IDE ports, in millisecs
uint16_t ota_time_window = 20000;           // time to start file upload when ota_flag set to true (after initial boot), in millsecs
uint16_t ota_time_elapsed = 0;              // Counter when OTA active
uint16_t ota_time = ota_boot_time_window;

//Define device name
String deviceName = "TDSMonitor";
String wifiHostName = "TDSMonitor";
String otaHostName = "TDSMonitorOTA";
String mqttClient = "TDSMonitor";

// ===============================
//  MQTT Variables
// ===============================
//  MQTT will only be used if a server address other than '0.0.0.0' is entered via portal
byte mqttAddr_1 = 0;
byte mqttAddr_2 = 0;
byte mqttAddr_3 = 0;
byte mqttAddr_4 = 0;
int mqttPort = 0;
String mqttUser = "myusername";
String mqttPW = "mypassword";
uint16_t mqttTelePeriod = 60;
uint32_t mqttLastUpdate = 0;
String mqttTopicSub ="TDSMonitor";  //v0.41 (for now, will always be same as pub)
String mqttTopicPub = "TDSMonitor"; //v0.41 

bool mqttEnabled = false;         //Will be enabled/disabled depending on whether a valid IP address is defined in Settings (0.0.0.0 disables MQTT)
bool mqttConnected = false;       //Will be enabled if defined and successful connnection made.  This var should be checked upon any MQTT action.
bool prevCarStatus = false;       //v0.44 for forcing MQTT update on state change
bool forceMQTTUpdate = false;     //v0.44 for forcing MQTT update on state change

//Variables for creating unique entity IDs and topics (HA discovery)
byte macAddr[6];               //Device MAC address (array is in reverse order)
String strMacAddr;             //MAC address as string and in proper order
char uidPrefix[] = "TDSMonitor";   //Prefix for unique ID generation
char devUniqueID[30];          //Generated Unique ID for this device (uidPrefix + last 6 MAC characters)

//---- Captive Portal -------
//flag for saving data in captive portal
bool shouldSaveConfig = false;

//callback notifying us of the need to save config
void saveConfigCallback () {
  shouldSaveConfig = true;
}
//---------------------------

//Calibration Variables
byte web_currSUPPLY = 0;
byte web_currPREDI = 0;
byte web_currOUT = 0;
byte web_calSUPPLY = 0;
byte web_calPREDI = 0;
byte web_calOUT = 0;
byte calSUPPLY = 0;
byte calPREDI = 0;
byte calOUT = 0;


//VARIABLES FOR PORTAL USE (JSON vars)
char device_name[18];    //v0.41
char wifi_hostname[18];  //v0.41
char ota_hostname[18];   //v0.41

char mqtt_addr_1[4];
char mqtt_addr_2[4];
char mqtt_addr_3[4];
char mqtt_addr_4[4];
char mqtt_port[6];
char mqtt_user[65];
char mqtt_pw[65];
char mqtt_tele_period[4];
char mqtt_topic_sub[18];   //v0.41
char mqtt_topic_pub[18];   //v0.41


String home_page_message = "";

WiFiManager wifiManager;
WiFiManager wm;

String localIP;

GravityTDS gravityTds_SUP;
GravityTDS gravityTds_PRE_DI;
GravityTDS gravityTds_OUT;

char mqtt_tds_SUP[50];
char mqtt_tds_PRE_DI[50];
char mqtt_tds_OUT[50];
char mqtt_tds_SUP2[50];
char mqtt_tds_PRE_DI2[50];
char mqtt_tds_OUT2[50];

float temperature = 25;
float tdsValue_SUP = 0;
float tdsValue_PRE_DI = 0;
float tdsValue_OUT = 0;

int prevtdsValue_SUP;
int prevtdsValue_PRE_DI;
int prevtdsValue_OUT;

//==========================
// LED Setup & Portal Options
//==========================
// Defaults values - these will be set/overwritten by portal or last saved vals on reboot
int numLEDs = 30;        
byte activeBrightness = 100;
byte sleepBrightness = 5;
uint32_t maxOperationTimePark = 60;
uint32_t maxOperationTimeExit = 5;
String ledEffect_m1 = "Out-In";
bool showStandbyLEDs = true;

CRGB LEDs[NUM_LEDS_MAX];

CRGB ledColorOn_m1 = CRGB::White;
CRGB ledColorOff = CRGB::Black;
CRGB ledColorStandby = CRGB::Blue;
CRGB ledColorWake = CRGB::Green;
CRGB ledColorActive = CRGB::Yellow;
CRGB ledColorParked = CRGB::Red;
CRGB ledColorBackup = CRGB::Red;

//************************** Just Some basic Definitions used for the Up Time Logger ************//
long Day=0;
int Hour =0;
int Minute=0;
int Second=0;
int HighMillis=0;
int Rollover=0;
unsigned long previousMillis = 0;
unsigned long interval = 10000;
int TransmitInterval = interval;
// constants do not change:
int CurrentRSSI=100;


/*
void RGB_LED_Setup(){
  // Configure an array of (one) leds without SPI
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
}

void RGB_LED(){
  leds[0] = CRGB::Black;
  FastLED.show();
}
*/


//OTHER GLOBAL VARIABLES
bool blinkOn = false;
int intervalDistance = 0;
bool carDetected = false;
bool isAwake = false;
bool coldStart = true;

byte carDetectedCounter = 0;
byte carDetectedCounterMax = 3;
byte nocarDetectedCounter = 0;
byte nocarDetectedCounterMax = 10;
byte outOfRangeCounter = 0;
uint32_t startTime;
bool exitSleepTimerStarted = false;
bool parkSleepTimerStarted = false;


//Initial distances for default load (only used for initial onboarding)
byte uomDistance = 0;       // 0=inches, 1=centimeters
int wakeDistance = 3048;    // wake/sleep distance (~10ft)
int startDistance = 1829;   // Start countdown distance (~6')
int parkDistance = 610;     // Final parked distacce (~2')
int backupDistance = 457;   // Flash backup distance (~18")

//===============================
// Web pages and handlers
//===============================
// Main Settings page
// Root / Main Settings page handler

void handleRoot() {
  String mainPage = "<html>\
  <head>\
    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
    <title>TDS Monitor - Main</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
    </style>\
  </head>\
  <body>\
    <h1>Controller Settings (VAR_DEVICE_NAME)</h1><br>\
    Changes made here will be used <b><i>until the controller is restarted</i></b>, unless the box to save the settings as new boot defaults is checked.<br><br>\
    To test settings, leave the box unchecked and click 'Update'. Once you have settings you'd like to keep, check the box and click 'Update' to write the settings as the new boot defaults.<br><br>\
    If you want to change wifi settings or the device name, you must use the 'Reset All' command.<br><br>\
    <form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/postform/\">\
      <table>\
      <tr>\
      <b><u>MQTT Settings</u></b>:<br>\
      ONLY enter this information if you already have an MQTT broker configured. <u><i>To disable or remove MQTT functionality, set the IP address to 0.0.0.0</i></u><br>\
      Any changes to MQTT require that you check the box to update boot settings below, which will reboot the controller.  If a successful connection to your MQTT broker is made, \
      a retained message of \"connected\" will be published to the topic \"homeassistant/stat/your_topic/mqtt\".<br><br>";

  mainPage += "<table>\
      <tr>\
      <td><label for=\"mqttaddr1\">Broker IP Address:</label></td>\
      <td><input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr1\" style=\"width: 50px\;\" value=\"";
  mainPage += String(mqttAddr_1);
  mainPage += "\">.<input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr2\" style=\"width: 50px\;\" value=\"";  
  mainPage += String(mqttAddr_2);
  mainPage += "\">.<input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr3\" style=\"width: 50px\;\" value=\"";  
  mainPage += String(mqttAddr_3);
  mainPage += "\">.<input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr4\" style=\"width: 50px\;\" value=\"";  
  mainPage += String(mqttAddr_4);
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqttport\">MQTT Broker Port:</label></td>\
      <td><input type=\"number\" min=\"0\" max=\"65535\" step=\"1\" name=\"mqttport\" style=\"width: 65px\;\" value=\"";
  mainPage += String(mqttPort);
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqttuser\">MQTT User Name:</label></td>\
      <td><input type=\"text\" name=\"mqttuser\" maxlength=\"64\" value=\"";
  mainPage += mqttUser;
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqttpw\">MQTT Password:</label></td>\
      <td><input type=\"password\" name=\"mqttpw\" maxlength=\"64\" value=\"";
  mainPage += mqttPW;
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqtttopic\">MQTT Topic: &nbsp;&nbsp; stat/</label></td>\
      <td><input type=\"text\" name=\"mqtttopic\" maxlength=\"16\" value=\"";
  mainPage += mqttTopicPub;

  mainPage += "\"> (16 alphanumeric chars max - no spaces, no symbols)</td></tr>\
      <tr>\
      <td><label for=\"mqttperiod\">Telemetry Period:</label></td>\
      <td><input type=\"number\" min=\"60\" max=\"600\" step=\"1\" name=\"mqttperiod\" style=\"width: 50px\;\" value=\"";
  mainPage += String(mqttTelePeriod);
  mainPage += "\"> seconds (60 min, 600 max)</td></tr>\
      <tr>\
      <td><label for=\"discovery\">MQTT Discovery:</label></td>\
      <td><a href=\"http://";
  mainPage += localIP;
  mainPage += "/discovery\">Configure Home Assistant MQTT Discovery</a>";    
  mainPage += "</td></tr>\
      <td><label for=\"calibration\">Sensor Calibration:</label></td>\
      <td><a href=\"http://";
  mainPage += localIP;
  mainPage += "/calibration\">Calibrate TDS Sensors</a>";    
  mainPage += "</td></tr>\
      </table><br>\
      <input type=\"checkbox\" name=\"chksave\" value=\"save\">Save all settings as new boot defaults (controller will reboot)<br><br>\
      <input type=\"submit\" value=\"Update\">\
    </form>\
    <br>\
    <h2>Controller Commands</h2>\
    Caution: Restart and Reset are executed immediately when the button is clicked.<br>\
    <table border=\"1\" cellpadding=\"10\">\
    <tr>\
    <td><button id=\"btnrestart\" onclick=\"location.href = './restart';\">Restart</button></td><td>This will reboot controller and reload default boot values.</td>\
    </tr><tr>\
    <td><button id=\"btnreset\" style=\"background-color:#FAADB7\" onclick=\"location.href = './reset';\">RESET ALL</button></td><td><b>WARNING</b>: This will clear all settings, including WiFi! You must complete initial setup again.</td>\
    </tr><tr>\
    <td><button id=\"btnupdate\" onclick=\"location.href = './update';\">Firmware Upgrade</button></td><td>Upload and apply new firmware from local file.</td>\
    </tr></table><br>\
    Current version: VAR_CURRRENT_VER\
  </body>\
</html>";
  mainPage.replace("VAR_DEVICE_NAME", deviceName); 
  mainPage.replace("VAR_CURRRENT_VER", VERSION);
  server.send(200, "text/html", mainPage);
}

// Settings submit handler - Settings results
void handleForm() {
  if (server.method() != HTTP_POST) {
    server.send(405, "text/plain", "Method Not Allowed");
  } else {
    String saveSettings;
    
    mqttAddr_1 = server.arg("mqttaddr1").toInt();
    mqttAddr_2 = server.arg("mqttaddr2").toInt();
    mqttAddr_3 = server.arg("mqttaddr3").toInt();
    mqttAddr_4 = server.arg("mqttaddr4").toInt();
    mqttPort = server.arg("mqttport").toInt();
    mqttUser = server.arg("mqttuser");
    mqttPW = server.arg("mqttpw");
    mqttTelePeriod = server.arg("mqttperiod").toInt();
    mqttTopicSub = server.arg("mqtttopic");
    mqttTopicPub = server.arg("mqtttopic");
    
    saveSettings = server.arg("chksave");
    
    String message = "<html>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>TDS Monitor - Current Settings</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Settings updated!</H1><br>\
      <H3>Current values are:</H3>";
    //example of message return types
    //message += "Device Name: " + deviceName + "<br>";
    //message += "Num LEDs: " + server.arg("leds") + "<br>";
    //message += "Active Brightness: " + server.arg("activebrightness") + "<br>";
    //message += "Standby Brightness: " + server.arg("sleepbrightness") + "<br><br>";
    //message += "<b>LED active On Times</b><br><br>";
    //message += "Park Time: " + server.arg("ledparktime") + " secs.<br>";
    //message += "Exit Time: " + server.arg("ledexittime") + " secs.<br><br>";
    //message += "Effect: " + server.arg("effect1") + "<br><br>";
    
    message += "<b>MQTT Settings</b><br><br>";
    if ((mqttAddr_1 == 0) && (mqttAddr_2 == 0) && (mqttAddr_3) == 0 && (mqttAddr_4 == 0)) {
      message += "MQTT: <b><u>Disabled</u></b> ";
      if (saveSettings != "save") {
        message += "(If you just changed MQTT settings, you must save as new boot defaults for this to take effect)<br>";
      }
    } else {
      message += "MQTT Server: " + server.arg("mqttaddr1") + "." + server.arg("mqttaddr2") +  "." + server.arg("mqttaddr3") + "." + server.arg("mqttaddr4") + "<br>";
      message += "MQTT Port: " + server.arg("mqttport") + "<br>";
      message += "MQTT User: " + server.arg("mqttuser") + "<br>";
      message += "MQTT Password: ***********<br>";
      message += "MQTT Topic: homeassistant/stat/" + server.arg("mqtttopic") + "<br>";
      message += "Telemetry Period: " + server.arg("mqttperiod") + " seconds<br>";
      if (saveSettings != "save") {
        message += "<br>(If you just changed MQTT settings, you must save as new boot defaults for this to take effect)<br>";
      }
    }
    message += "<br>";
    if (saveSettings == "save") {
      message += "<br>";
      message += "<b>New settings saved as boot defaults.</b> Controller will now reboot.<br>";
      message += "You can return to the settings page after boot completes (lights will briefly turn blue then red/green to indicate completed boot).<br>";    
    } else {
      //Wake up system so new setting can be seen/tested... even if car present
      carDetectedCounter = carDetectedCounterMax + 1;
    }
    message += "<br><a href=\"http://";
    message += localIP;
    message += "/discovery\">Configure Home Assistant MQTT Discovery</a><br>"; 
    message += "<br><a href=\"http://";
    message += localIP;
    message += "\">Return to settings</a><br>";
             
    message += "</body></html>";
    server.send(200, "text/html", message);
    delay(1000);
    if (saveSettings == "save") {
      updateSettings(true);
    } else {
      updateSettings(false);
    } 
  }
}

// Firmware update handler
void handleUpdate() {
  String updFirmware = "<html>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>TDS Monitor - Firmware Update</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Firmware Update</H1>\
      <H3>Current firmware version: ";
  updFirmware += VERSION;
  updFirmware += "</H3><br>";
  updFirmware += "Notes:<br>";
  updFirmware += "<ul>";
  updFirmware += "<li>The firmware update will begin as soon as the Update Firmware button is clicked.</li><br>";
  updFirmware += "<li>Your current settings will be retained.</li><br>";
  updFirmware += "<li><b>Please be patient!</b> The update will take a few minutes.  Do not refresh the page or navigate away.</li><br>";
  updFirmware += "<li>If the upload is successful, a brief message will appear and the controller will reboot.</li><br>";
  updFirmware += "<li>After rebooting, you'll automatically be taken back to the main settings page and the update will be complete.</li><br>";
  updFirmware += "</ul><br>";
  updFirmware += "</body></html>";    
  updFirmware += "<form method='POST' action='/update2' enctype='multipart/form-data'>";
  updFirmware += "<input type='file' accept='.bin,.bin.gz' name='Select file' style='width: 300px'><br><br>";
  updFirmware += "<input type='submit' value='Update Firmware'>";
  updFirmware += "</form><br>";
  updFirmware += "<br><a href=\"http://";
  updFirmware += localIP;
  updFirmware += "\">Return to settings</a><br>";
  updFirmware += "</body></html>";
  server.send(200, "text/html", updFirmware); 
}

void updateSettings(bool saveBoot) {
  // This updates the current local settings for current session only.  
  // Will be overwritten with reboot/reset/OTAUpdate
  if (saveBoot) {
    updateBootSettings(saveBoot);
  } else {
    //Update FastLED with new brightness values if changed
    if (isAwake) {
      FastLED.setBrightness(activeBrightness);
    } else {
      FastLED.setBrightness(sleepBrightness);
    }
  }
}

void updateBootSettings(bool restart_ESP) {
  // Writes new settings to SPIFFS (new boot defaults)
  char t_led_count[4];
  char t_led_brightness_active[4];
  char t_led_brightness_sleep[4];
  char t_led_park_time[4];
  char t_led_exit_time[4];
  char t_uom_distance[4];
  char t_wake_mils[6];
  char t_start_mils[6];
  char t_park_mils[6];
  char t_backup_mils[6];
  char t_color_standby[4];
  char t_color_wake[4];
  char t_color_active[4];
  char t_color_parked[4];
  char t_color_backup[4];
  char t_led_effect[16];
  int eff_len = 16;
  char t_mqtt_addr_1[4];
  char t_mqtt_addr_2[4];
  char t_mqtt_addr_3[4];
  char t_mqtt_addr_4[4];
  char t_mqtt_port[6];
  char t_mqtt_user[65];
  char t_mqtt_pw[65];
  char t_mqtt_tele_period[4];
  int user_len = 65;
  int user_pw = 65;
  //v0.41 new values
  char t_device_name[18];
  char t_mqtt_topic_sub[18];
  char t_mqtt_topic_pub[18];
  int dev_name_len = 18;
  int topic_len = 18;
  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.println("Attempting to update boot settings");
  //#endif

  //Convert values into char arrays
  sprintf(t_led_count, "%u", numLEDs);
  sprintf(t_led_brightness_active, "%u", activeBrightness);
  sprintf(t_led_brightness_sleep, "%u", sleepBrightness);
  sprintf(t_led_park_time, "%u", maxOperationTimePark);
  sprintf(t_led_exit_time, "%u", maxOperationTimeExit);
  sprintf(t_uom_distance, "%u", uomDistance);
  sprintf(t_wake_mils, "%u", wakeDistance);
  sprintf(t_start_mils, "%u", startDistance);
  sprintf(t_park_mils, "%u", parkDistance);
  sprintf(t_backup_mils, "%u", backupDistance);
  ledEffect_m1.toCharArray(t_led_effect, eff_len);
  sprintf(t_mqtt_addr_1, "%u", mqttAddr_1);
  sprintf(t_mqtt_addr_2, "%u", mqttAddr_2);
  sprintf(t_mqtt_addr_3, "%u", mqttAddr_3);
  sprintf(t_mqtt_addr_4, "%u", mqttAddr_4);
  sprintf(t_mqtt_port, "%u", mqttPort);
  sprintf(t_mqtt_tele_period, "%u", mqttTelePeriod);
  mqttUser.toCharArray(t_mqtt_user, user_len);
  mqttPW.toCharArray(t_mqtt_pw, user_pw);
  deviceName.toCharArray(t_device_name, dev_name_len);
  mqttTopicSub.toCharArray(t_mqtt_topic_sub, topic_len);
  mqttTopicPub.toCharArray(t_mqtt_topic_pub, topic_len);

#ifdef ARDUINOJSON_VERSION_MAJOR >= 6
    DynamicJsonDocument json(1024);
#else
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
#endif

    json["mqtt_addr_1"] = t_mqtt_addr_1;
    json["mqtt_addr_2"] = t_mqtt_addr_2;
    json["mqtt_addr_3"] = t_mqtt_addr_3;
    json["mqtt_addr_4"] = t_mqtt_addr_4;
    json["mqtt_port"] = t_mqtt_port;
    json["mqtt_tele_period"] = t_mqtt_tele_period;
    json["mqtt_user"] = t_mqtt_user;
    json["mqtt_pw"] = t_mqtt_pw;
    //v0.41
    json["device_name"] = t_device_name;
    json["mqtt_topic_sub"] = t_mqtt_topic_sub;
    json["mqtt_topic_pub"] = t_mqtt_topic_pub;

  if (SPIFFS.begin()) {
      File configFile = SPIFFS.open("/config.json", "w");
      if (!configFile) {
        //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
          Serial.println("failed to open config file for writing");
        //#endif
      }
      serializeJson(json, Serial);
      serializeJson(json, configFile);
      configFile.close();
        //end save
      //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
        Serial.println("Boot settings saved. Rebooting controller.");
      //#endif
  }
  SPIFFS.end();
  if (restart_ESP) {   //Needed so initial onboarding doesn't cause second reboot
    ESP.restart();
  }
}

void handleReset() {
    String resetMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>Controller Reset</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Controller Resetting...</H1><br>\
      <H3>After this process is complete, you must setup your controller again:</H3>\
      <ul>\
      <li>Connect a device to the controller's local access point: ESP_ParkingAsst</li>\
      <li>Open a browser and go to: 192.168.4.1</li>\
      <li>Enter your WiFi information and set other default settings values</li>\
      <li>Click Save. The controller will reboot and join your WiFi</li>\
      </ul><br>\
      Once the above process is complete, you can return to the main settings page by rejoining your WiFi and entering the IP address assigned by your router in a browser.<br>\
      You will need to reenter all of your settings for the system as all values will be reset to original defaults<br><br>\
      <b>This page will NOT automatically reload or refresh</b>\
      </body></html>";
    server.send(200, "text/html", resetMsg);
    delay(1000);
    SPIFFS.begin();
    SPIFFS.format();
    SPIFFS.end();
    wifiManager.resetSettings();
    delay(1000);
    ESP.restart();
}

void handleRestart() {
    String restartMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>Controller Restart</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Controller restarting...</H1><br>\
      <H3>Please wait</H3><br>\
      After the controller completes the boot process (lights will flash blue, followed by red/green for approx. 2 seconds), you may click the following link to return to the main page:<br><br>\
      <a href=\"http://";      
    restartMsg += localIP;
    restartMsg += "\">Return to settings</a><br>";
    restartMsg += "</body></html>";
    server.send(200, "text/html", restartMsg);
    delay(1000);
    ESP.restart();
}

//  =====================================
//  Home Assistant MQTT Discovery - v0.45
//  =====================================

void handleDiscovery() {
  //Main page for enabling/disabling Home Assistant MQTT Discovery
  String discMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title> TDS Monitor - MQTT Discovery</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Home Assistant MQTT Discovery - BETA</H1>\
      (BETA Note: Tested successfully with Home Assistant Core 2024.1 but will be considered a beta feature until broader testing by others has been completed.)<br><br>\
      This feature can be used to add or remove the parking assistant device and MQTT entities to Home Assistant without manual YAML editing.<br><br>";  
  discMsg += "<u><b>Prerequisites and Notes</b></u>";
  discMsg += "<ul><li>It is <b><i>strongly recommended</i></b> that you read the &nbsp"; 
  discMsg += "<a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a> before using this feature.</li>";
  discMsg += "<li>You must have successfully completed the MQTT setup, rebooted and estabished a connection to your broker before these options will work.</li>";
  discMsg += "<li>The Home Assistant MQTT integration must be installed, discovery must not have been disabled nor the default discovery topic changed.</li>";
  discMsg += "<li>The action to enable/disable will occur immediately in Home Assistant without any interaction or prompts.</li>";
  discMsg += "<li>If you have already manually created the Home Assistant MQTT entities, enabling discovery will create duplicate entities with different names.</li></ul>";
  discMsg += "<H3>Enable Discovery</H3>";
  discMsg += "This will immediately create a device called <b>VAR_DEVICE_NAME</b> in your Home Assistant MQTT Integration.<br>";
  discMsg += "It will also create the following entities for this device:\
      <ul>\
      <li>Supply TDS</li>\
      <li>Pre-DI TDS</li>\
      <li>Output TDS</li>\
      <li>IP Address</li>\
      <li>MAC Address</li></ul><br>";
  discMsg += "<button id=\"btnenable\" onclick=\"location.href = './discoveryEnabled';\">Enable Discovery</button>"; 
  discMsg += "<br><hr>";
  discMsg += "<H3>Disable Discovery</H3>";
  discMsg += "This will <b>immediately</b> delete the device and all entities created MQTT Discover for this device.<br>\
      If you have any automations, scripts, dashboard entries or other processes that use these entities, you will need to delete or correct those items in Home Assistant.<br><br>";
  discMsg += "<button id=\"btndisable\" onclick=\"location.href = './discoveryDisabled';\">Disable Discovery</button><br><br>"; 
      
  discMsg += "<br><a href=\"http://";
  discMsg += localIP;
  discMsg += "\">Return to settings</a><br>";
  discMsg += "</body></html>";
  discMsg.replace("VAR_DEVICE_NAME", deviceName);
  server.send(200, "text/html", discMsg); 
}

void enableDiscovery() {
  String discMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title> TDS Monitor MQTT Discovery</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Home Assistant MQTT Discovery</H1><br>";
  if (mqttEnabled) {
    byte retVal = haDiscovery(true);
    if (retVal == 0) {

      discMsg += "<b><p style=\"color: #5a8f3d;\">MQTT Discovery topics successfully sent to Home Assistant.</p></b><br>";
      discMsg += "If the Home Assistant MQTT integration has been configured correctly, you should now find a new device, <b>VAR_DEVICE_NAME</b>, under the MQTT integration.";
      discMsg += "<p style=\"color: #1c5410;\">\
                   <ul>\
                   <li>You can change the name of the device or any entities, if desired, via Home Assistant</li>\
                   <li>To remove (permanently delete) the created device and entities, disable MQTT Discovery</li>\
                   <li>If you disable MQTT Discover and then reenable it, the device and entities will be recreated with their original names.</li>\
                   </ul></p><br>";
      discMsg += "If you do not see the new device under your Home Assistant MQTT integration, please use a utility (e.g. MQTT Explorer or similar) to see if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>";
      discMsg += "For additional troubleshooting tips, please see the <a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a><br><br>\
                  It is recommended that you disable MQTT Discovery in the Parking Assistant app until you resolve any MQTT/Home Assistant issues.<br><br>";
    
    } else if (retVal == 1) {
      //Unable to connect or reconnect to broker
      discMsg += "<b><p style=\"color: red;\">The Parking Assistant was unable to connect or reconnect to your MQTT broker!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery.<br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were created.</p><br><br>";
      
    } else {
      //Unknown error or issue
      discMsg += "<b><p style=\"color: red;\">An unknown error or issue occurred!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery and may need to manually create the Home Assistant entities.<br><br>";
      discMsg += "For additional troubleshooting tips, please see the <a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a><br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were created.</p><br><br>";
    }
  } else {
    discMsg += "<b><p style=\"color: red;\">MQTT is not enabled or was unable to connect to your broker!</p></b><br>";
    discMsg += "Before attempting to enable MQTT Discovery, please be sure you have completed the following:";
    discMsg += "<ul>\
                 <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                 <li>Checked the box to save settings as new boot defaults</li>\
                 <li>Rebooted the controller</li>\
                 </ul><br>";
    discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to see if the controller is successfully connecting to your broker.<br>\ 
                You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                Until you successfully see these topics in your broker, you will not be able to enable MQTT Discovery.<br>";
    
  }
  discMsg += "<br><a href=\"http://";
  discMsg += localIP;
  discMsg += "\">Return to settings</a><br>";
  discMsg += "</body></html>";
  discMsg.replace("VAR_DEVICE_NAME", deviceName);
  server.send(200, "text/html", discMsg); 
  
  //send baseline TDS reading
  sprintf(mqtt_tds_SUP, "%d",(int)tdsValue_SUP);
  client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
  Serial.println("Supply TDS Baseline Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_PRE_DI, "%d",(int)tdsValue_PRE_DI);
  client.publish(mqtt_prediTDS_stat, mqtt_tds_PRE_DI);
  Serial.println("Pre-DI TDS Baseline Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_OUT, "%d",(int)tdsValue_OUT);
  client.publish(mqtt_outTDS_stat, mqtt_tds_OUT);
  Serial.println("Output TDS Baseline Telemetry Sent Successfully!");
  Serial.println("");
}

void disableDiscovery() {
  String discMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title> TDS Monitor MQTT Discovery</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Home Assistant MQTT Discovery</H1><br>";
  if (mqttEnabled) {
    byte retVal = haDiscovery(false);
    if (retVal == 0) {
      discMsg += "<b><p style=\"color: #5a8f3d;\">MQTT Removal topics successfully sent to Home Assistant.</p></b><br>";
      discMsg += "The device <b>VAR_DEVICE_NAME</b> should now be deleted from Home Assistant, along with the following entities:\
        <ul>\
      <li>Supply TDS</li>\
      <li>Pre-DI TDS</li>\
      <li>Output TDS</li>\
      <li>IP Address</li>\
      <li>MAC Address</li></ul><br>";
            
    } else if (retVal == 1) {
      //Unable to connect or reconnect to broker
      discMsg += "<b><p style=\"color: red;\">The Parking Assistant was unable to connect or reconnect to your MQTT broker!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery.<br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were removed.</p><br><br>";
      
    } else {
      //Unknown error or issue
      discMsg += "<b><p style=\"color: red;\">An unknown error or issue occurred!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery and may need to manually create the Home Assistant entities.<br><br>";
      discMsg += "For additional troubleshooting tips, please see the <a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a><br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were removed.</p><br><br>";
      
    }
  } else {
    discMsg += "<b><p style=\"color: red;\">MQTT is not enabled or was unable to connect to your broker!</p></b><br>";
    discMsg += "Before attempting to use MQTT Discovery, please be sure you have completed the following:";
    discMsg += "<ul>\
                 <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                 <li>Checked the box to save settings as new boot defaults</li>\
                 <li>Rebooted the controller</li>\
                 </ul><br>";
    discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to see if the controller is successfully connecting to your broker.<br>\ 
                You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                Until you successfully see these topics in your broker, you will not be able to enable MQTT Discovery.<br>";
    
  }
  discMsg += "<br><a href=\"http://";
  discMsg += localIP;
  discMsg += "\">Return to settings</a><br>";
  discMsg += "</body></html>";
  discMsg.replace("VAR_DEVICE_NAME", deviceName);
  server.send(200, "text/html", discMsg); 

}

// Settings submit handler - Settings results
void calibration() {

  String saveCalibration;
  //String.format("%d", tdsValue_SUP)

  //Need to get the TDS Value to display as a label on line 884
  web_currSUPPLY = (int)tdsValue_SUP
  web_currPREDI = (int)tdsValue_PRE_DI;
  web_currOUT = (int)tdsValue_OUT;
  web_calSUPPLY = server.arg("web_calsupply").toInt();
  web_calPREDI = server.arg("web_calpredi").toInt();
  web_calOUT = server.arg("web_calout").toInt();



  saveCalibration = server.arg("calsave");

  String calSensors = "<html>\
    </head>\
      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
      <title>TDS Monitor - Sensor Calibration</title>\
      <style>\
        body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
      </style>\
    </head>\
    <body>\
    <H1>Calibrate TDS Sensor</H1><br>\
    <H3>Current values are:</H3>";

  //these are labels  
  calSensors += "<b>TDS Sensors</b><br>";
  /*
  calSensors += "Current Supply TDS: " + tdsValue_SUP + "<br>";
  calSensors += "Current Pre-DI TDS: " + tdsValue_PRE_DI + "<br>";
  calSensors += "Current Output TDS: " + tdsValue_OUT + "<br>"  ;
  */

  calSensors += "Current Supply TDS: ";
  calSensors += web_currSUPPLY;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "Current Pre-DI TDS: ";
  calSensors += web_currPREDI;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "Current Output TDS: ";
  calSensors += web_currOUT;
  calSensors += "ppm";
  calSensors += "<br>";



  //these are input boxes
  calSensors += "<table>\
      <tr>\
      <td><label for=\"web_calsupply\">Calibrated Supply TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"web_calsupply\" style=\"width: 50px\;\" value=\"";
  calSensors += String(web_currSUPPLY);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"web_calpredi\">Calibrated Pre-DI TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"web_calpredi\" style=\"width: 50px\;\" value=\"";
  calSensors += String(web_currPREDI);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"web_calout\">Calibrated Output TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"web_calout\" style=\"width: 50px\;\" value=\"";
  calSensors += String(web_calOUT);
  calSensors += "\"> ppm<br></td></tr>\"";
  calSensors += "<br>";
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "/discovery\">Configure Home Assistant MQTT Discovery</a><br>"; 
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "\">Return to settings</a><br>";

  calSensors += "</td></tr>\
        </table><br>\
        <input type=\"checkbox\" name=\"calsave\" value=\"save\">Store Calibration and Replace Defaults (Controller Will Reboot)<br><br>\
        <input type=\"submit\" value=\"Submit Calibration\">\
      </form>\
      <br>\
      <h2>Controller Commands</h2>\
      Caution: Restart and Reset are executed immediately when the button is clicked.<br>\
      <table border=\"1\" cellpadding=\"10\">\
      <tr>\
      <td><button id=\"btnrestart\" onclick=\"location.href = './restart';\">Restart</button></td><td>This will reboot controller and reload default boot values.</td>\
      </tr><tr>\
      <td><button id=\"btnreset\" style=\"background-color:#FAADB7\" onclick=\"location.href = './reset';\">RESET ALL</button></td><td><b>WARNING</b>: This will clear all settings, including WiFi! You must complete initial setup again.</td>\
      </tr><tr>\
      <td><button id=\"btnupdate\" onclick=\"location.href = './update';\">Firmware Upgrade</button></td><td>Upload and apply new firmware from local file.</td>\
      </tr></table><br>\
      Current version: VAR_CURRRENT_VER\
    </body>\
  </html>";

  calSensors.replace("VAR_DEVICE_NAME", deviceName); 
  calSensors.replace("VAR_CURRRENT_VER", VERSION);
  server.send(200, "text/html", calSensors);
  delay(1000);
}



// Not found or invalid page handler
void handleNotFound() {
  String message = "File Not Found or invalid command.\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  server.send(404, "text/plain", message);
}


// ===================================
//  SETUP MQTT AND CALLBACKS
// ===================================


bool setup_mqtt() {
  byte mcount = 0;
  
  IPAddress mymqttServer = IPAddress(mqttAddr_1, mqttAddr_2, mqttAddr_3, mqttAddr_4);
  
  client.setServer(mymqttServer, mqttPort);
  client.setBufferSize(512); 
  client.setCallback(callback);

  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.print("Connecting to MQTT broker.");
  //#endif
  while (!client.connected( )) {
    //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
      Serial.print(".");
    //#endif
    client.connect(mqttClient.c_str(), mqttUser.c_str(), mqttPW.c_str());
    if (mcount >= 60) {
      //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
        Serial.println();
        Serial.println("Could not connect to MQTT broker. MQTT disabled.");
      //#endif
      // Could not connect to MQTT broker
      return false;
    }
    delay(500);
    mcount++;
  }
  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.println();
    Serial.println("Successfully connected to MQTT broker.");
  //#endif
  client.subscribe(("cmnd/" + mqttTopicSub + "/#").c_str());
  client.publish(("homeassistant/stat/" + mqttTopicPub + "/mqtt").c_str(), "connected", true);
  //Publish current IP and MAC addresses
  client.publish(("homeassistant/stat/" + mqttTopicPub + "/ipaddress").c_str(), localIP.c_str(), true);
  client.publish(("homeassistant/stat/" + mqttTopicPub + "/macaddress").c_str(), strMacAddr.c_str(), true);
  mqttConnected = true;

  //send baseline TDS reading
  sprintf(mqtt_tds_SUP, "TDS-SUP: %d",(int)tdsValue_SUP);
  client.publish(mqtt_pub_topic_supTDS, mqtt_tds_SUP);
  Serial.println("Supply TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_PRE_DI, "TDS-PRE_DI: %d",(int)tdsValue_PRE_DI);
  client.publish(mqtt_pub_topic_prediTDS, mqtt_tds_PRE_DI);
  Serial.println("Pre-DI TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_OUT, "TDS-OUT: %d",(int)tdsValue_OUT);
  client.publish(mqtt_pub_topic_outTDS, mqtt_tds_OUT);
  Serial.println("Output TDS Telemetry Sent Successfully!");
  Serial.println("");

  client.publish("Aquarium","TDS Monitor Connected!");
  client.subscribe("Aquarium");
  Serial.println("MQTT Connected - Connection Mesage Sent!");

  
  //send baseline TDS reading
  sprintf(mqtt_tds_SUP, "%d",(int)tdsValue_SUP);
  client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
  Serial.println("Supply TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_PRE_DI, "%d",(int)tdsValue_PRE_DI);
  client.publish(mqtt_prediTDS_stat, mqtt_tds_PRE_DI);
  Serial.println("Pre-DI TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_OUT, "%d",(int)tdsValue_OUT);
  client.publish(mqtt_outTDS_stat, mqtt_tds_OUT);
  Serial.println("Output TDS Telemetry Sent Successfully!");
  Serial.println("");
  
  return true;
}

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  String message = (char*)payload;
  /*
   * Add any commands submitted here
   * Example:
   * if (strcmp(topic, "cmnd/matrix/mode")==0) {
   *   MyVal = message;
   *   Do something
   *   return;
   * };
   */
}

void readConfigFile() {
  if (SPIFFS.begin()) {
    //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
      Serial.println("mounted file system");
    //#endif
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
        Serial.println("reading config file");
      //#endif
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
          Serial.println("opened config file");
        //#endif
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);

        configFile.readBytes(buf.get(), size);

        DynamicJsonDocument json(1024);
        auto deserializeError = deserializeJson(json, buf.get());
        serializeJson(json, Serial);
        if ( ! deserializeError ) {
         //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
            Serial.println("\nparsed json");
         //#endif
          // Read values here from SPIFFS (v0.42 - add defaults for all values in case they don't exist to avoid potential boot loop)
          strcpy(mqtt_addr_1, json["mqtt_addr_1"]|"0");
          strcpy(mqtt_addr_2, json["mqtt_addr_2"]|"0");
          strcpy(mqtt_addr_3, json["mqtt_addr_3"]|"0");
          strcpy(mqtt_addr_4, json["mqtt_addr_4"]|"0");
          strcpy(mqtt_port, json["mqtt_port"]|"0");
          strcpy(mqtt_tele_period, json["mqtt_tele_period"]|"60");
          strcpy(mqtt_user, json["mqtt_user"]|"mqttuser");
          strcpy(mqtt_pw, json["mqtt_pw"]|"mqttpwd");
          strcpy(device_name, json["device_name"]|"parkasst");               // Default needed if upgrading to v0.41, because value won't exist in config
          strcpy(mqtt_topic_sub, json["mqtt_topic_sub"]|"parkasst");         // Default needed if upgrading to v0.41, because value won't exist in config
          strcpy(mqtt_topic_pub, json["mqtt_topic_pub"]|"parkasst");         // Default needed if upgrading to v0.41, because value won't exist in config
          //Need to set wifihostname here
          deviceName = String(device_name);
        } else {
          //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
            Serial.println("failed to load json config");
          //#endif
        }
        configFile.close();
      }
    }
    SPIFFS.end();  
  } else {
    //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
      Serial.println("failed to mount FS");
    //#endif
  }
}

void setup_Wifi() {
  delay(10);
  Serial.println("Booting");
  Serial.println("Connecting to WiFi");
  WiFi.mode(WIFI_STA);
  WiFiManager wm;
  bool res;
  res = wm.autoConnect("TDS Monitor", "password");
  if(!res) {
    Serial.println("Failed to connect");
  }
  else {
    Serial.println("WiFi Connection Success!");
    client.publish(mqtt_IP_stat, localIP.c_str());
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());
  localIP = WiFi.localIP().toString();
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.print(WiFi.localIP());
  Serial.println("");
  }

}

void resetWiFiSettings(){
  //Wifi Reset Button if held for 10 seconds, reset wifi settings
  pinMode(27, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  int wifiReset = digitalRead(27);
  if (wifiReset == HIGH) {
    digitalWrite(2, LOW);
  } else {
    digitalWrite(2, HIGH);
    delay(10000);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    Serial.println("WiFi Reset...TDS Monitor Restarting");
    delay(150);
    wm.resetSettings();
    delay(150);
    //Restarts device after wiping wifi settings
    ESP.restart();
  }
}

void wifiReconnect() {
  // Loop until we're reconnected
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print("Attempting to Re-Establish WiFi Connection...");
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("WiFi Connection Re-Established!");
      client.publish(mqtt_IP_stat, localIP.c_str());
    } else {
      Serial.print("failed, rc=");
      Serial.print(WiFi.status());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
      setup_Wifi();
    }
  }
}


// ===============================
//  LED and Display Functions
// ===============================
void blinkLEDs(CRGB color) {
  if (blinkOn) {
    fill_solid(LEDs, numLEDs, color);
  } else {
    fill_solid(LEDs, numLEDs, CRGB::Black);
  }
  blinkOn = !blinkOn;
}

void updateOutIn(int curDistance) {
   byte numberToLight = 1;
  fill_solid(LEDs, numLEDs, CRGB::Black);

  //Get number of LEDs to light up on each end, based on interval
  numberToLight = (startDistance - curDistance) / intervalDistance;
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  for (int i=0; i < numberToLight; i++) {
    LEDs[i] = ledColorActive;
    LEDs[(numLEDs-1) - i] = ledColorActive;
  }
}

void updateInOut(int curDistance) {
  byte numberToLight = 1;
  byte startLEDLeft = 0;
  byte startLEDRight = 0;
  fill_solid(LEDs, numLEDs, CRGB::Black);
  //Get number of LEDs to light up on each end, based on interval
  numberToLight = ((startDistance - curDistance) / intervalDistance);
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  //Find center LED(s) - single of odd number, two if even number of LEDS
  startLEDLeft = (numLEDs / 2);
  startLEDRight = startLEDLeft;
  if ((startLEDLeft % 2) == 0) {
    startLEDLeft --;
  }
  for (int i=0; i < numberToLight; i++) {
    LEDs[(startLEDLeft - i)] = ledColorActive;
    LEDs[(startLEDRight + i)] = ledColorActive;
  }
}

void updateFullStrip(int curDistance) {
  byte numberToLight = 1;
  fill_solid(LEDs, numLEDs, CRGB::Black);

  //Get number of LEDs to light up from start of LED strip, based on interval
  numberToLight = (startDistance - curDistance) / intervalDistance;
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  for (int i=0; i < numberToLight; i++) {
    LEDs[i] = ledColorActive;
  }
}

void updateFullStripInv(int curDistance) {
  byte numberToLight = 1;
  fill_solid(LEDs, numLEDs, CRGB::Black);

  //Get number of LEDs to light up from end of LED strip, based on interval
  numberToLight = (startDistance - curDistance) / intervalDistance;
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  for (int i=0; i < numberToLight; i++) {
    LEDs[((numLEDs - i)- 1)] = ledColorActive;
  }
}

void updateSolid(int curDistance) {
  fill_solid(LEDs, numLEDs, CRGB::Black);
  if ((curDistance > startDistance) && (curDistance <= wakeDistance)) {
    fill_solid(LEDs, numLEDs, ledColorWake); 
  } else if ((curDistance > parkDistance) && (curDistance <= startDistance)) {
    fill_solid(LEDs, numLEDs, ledColorActive);
  } else if ((curDistance > backupDistance) && (curDistance <= parkDistance)) {
    fill_solid(LEDs, numLEDs, ledColorParked);
  }
}

void updateSleepMode() {
  fill_solid(LEDs, numLEDs, CRGB::Black);
  FastLED.setBrightness(sleepBrightness);
  if (showStandbyLEDs) {
    LEDs[0] = ledColorStandby;
    LEDs[numLEDs - 1] = ledColorStandby;
  }
}

void updateOTA() {
  fill_solid(LEDs, numLEDs, CRGB::Black);
  //Alternate LED colors using red and green
  FastLED.setBrightness(activeBrightness);
  for (int i=0; i < (numLEDs-1); i = i + 2) {
    LEDs[i] = CRGB::Red;
    LEDs[i+1] = CRGB::Green;
  }
  FastLED.show();
}


// ==============================
//  MQTT Functions and Procedures
// ==============================

void createDiscoveryUniqueID() {
  //Generate UniqueID from uidPrefix + last 6 chars of device MAC address
  //This should insure that even multiple devices installed in same HA instance are unique
  
  strcpy(devUniqueID, uidPrefix);
  int preSizeBytes = sizeof(uidPrefix);
  int preSizeElements = (sizeof(uidPrefix) / sizeof(uidPrefix[0]));
  //Now add last 6 chars from MAC address (these are 'reversed' in the array)
  int j = 0;
  //for (int i = 2; i >= 0; i--) {
  for (int i = 3; i < 6; i++) {
    sprintf(&devUniqueID[(preSizeBytes - 1) + (j)], "%02X", macAddr[i]);   //preSizeBytes indicates num of bytes in prefix - null terminator, plus 2 (j) bytes for each hex segment of MAC
    j = j + 2;
  }
  //devUniqueID would now contain something like "TDSMonitor02BE4F" which can be used for topics/payloads
  // End result is a unique ID for this device (e.g. rctdevE350CA) 
  Serial.print("Unique ID: ");
  Serial.println(devUniqueID);  
}

byte haDiscovery (bool enable) {
  char topic[128];
  //if (auto_discovery) {
    char buffer1[512];
    char buffer2[512];
    char buffer3[512];
    char buffer4[512];
    char buffer5[512];
    char uid[128];
    if (mqttEnabled) {
      createDiscoveryUniqueID();
      if (enable) {    
        //Add device and entities
        if (!client.connected()) { 
          if (!mqttReconnect_soft()) {
            return 1;
          }
        }
      //Should have valid MQTT Connection at this point
      DynamicJsonDocument doc(2048);

      Serial.println("Discovering new devices...");

      Serial.println("Adding TDS Sensor...Supply TDS");

      //Create supTDS Sensor with full device details
      //topic
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "supTDS/config");
      //Create unique_id based on devUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "supTDS");
      //JSON payload
      doc.clear();
      doc["name"] = "Supply TDS";
      doc["obj_id"] = "mqtt_TDSmonitor_supTDS";
      doc["dev_cla"] = "volatile_organic_compounds_parts";
      doc["uniq_id"] = uid;
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/supTDS";
      doc["unit_of_meas"] = "ppm";
      doc["ic"] = "mdi:water-pump";
      JsonObject devicesupTDS = doc.createNestedObject("device");
        devicesupTDS["ids"] = "tdsmonitor";
        devicesupTDS["name"] = "TDS Monitor";
        devicesupTDS["mf"] = "DeezNutzInc";
        devicesupTDS["mdl"] = "TDS Monitor Elite";
        devicesupTDS["sw"] = "1.89";
        devicesupTDS["hw"] = "1.0";
        devicesupTDS["cu"] = "http://" + localIP + "/config";  //web interface for device, with discovery toggle
      serializeJson(doc, buffer1);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer1, true);

      //Create prediTDS Sensor
      Serial.println("Adding TDS Sensor...Pre-DI TDS");

      //Create unique Topic based on devUniqueID
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "prediTDS/config");
      //Create unique_id based on decUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "prediTDS");
      //Create JSON payload per HA documentation
      doc.clear();
      doc["name"] = "Pre-DI TDS";
      doc["obj_id"] = "mqtt_TDSmonitor_prediTDS";
      doc["dev_cla"] = "volatile_organic_compounds_parts";
      doc["uniq_id"] = uid;
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/prediTDS";
      doc["unit_of_meas"] = "ppm";
      doc["ic"] = "mdi:toilet";
      JsonObject deviceprediTDS = doc.createNestedObject("device");
      deviceprediTDS["ids"] = "tdsmonitor";
      deviceprediTDS["name"] = "TDS Monitor";
      serializeJson(doc, buffer2);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer2, true);

      //outTDS Sensor
      Serial.println("Adding TDS Sensor...Output TDS");
      //Create unique Topic based on devUniqueID
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "outTDS/config");
      //Create unique_id based on decUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "outTDS");
      //Create JSON payload per HA documentation
      doc.clear();
      doc["name"] = "Output TDS";
      doc["obj_id"] = "mqtt_TDSmonitor_outTDS";
      doc["dev_cla"] = "volatile_organic_compounds_parts";
      doc["uniq_id"] = uid;
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/outTDS";
      doc["unit_of_meas"] = "ppm";
      doc["ic"] = "mdi:water-check";
      JsonObject deviceoutTDS = doc.createNestedObject("device");
      deviceoutTDS["ids"] = "tdsmonitor";
      deviceoutTDS["name"] = "TDS Monitor";
      serializeJson(doc, buffer3);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer3, true);

      //IP Address Diagnostic Sensor
      Serial.println("Adding IP Diagnostic Sensor...");
      //Create unique Topic based on devUniqueID
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "IP/config");
      //Create unique_id based on decUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "IP");
      //Create JSON payload per HA documentation
      doc.clear();
      doc["name"] = "IP Address";
      doc["uniq_id"] = uid;
      doc["ent_cat"] = "diagnostic";
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/ipaddress";
      JsonObject deviceIP = doc.createNestedObject("device");
      deviceIP = doc.createNestedObject("device");
      deviceIP["ids"] = "tdsmonitor";
      deviceIP["name"] = "TDS Monitor";
      serializeJson(doc, buffer4);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer4, true);

      Serial.println("All Devices Added!");


      //Create MAC Address as Diagnostic Sensor
      //topic
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "MAC/config");
      //Unique ID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "MAC");
      //JSON Payload
      doc.clear();
      doc["name"] = deviceName + " MAC Address";
      doc["uniq_id"] = uid;
      doc["ent_cat"] = "diagnostic";
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/macaddress";
      JsonObject deviceMAC = doc.createNestedObject("device");
      deviceMAC["ids"] = "tdsmonitor";
      deviceMAC["name"] = "TDS Monitor";
      serializeJson(doc, buffer5);
      client.publish(topic, buffer5, true);
      return 0;

    } else {

      //Remove all entities by publishing empty payloads
      if (!client.connected()) { 
        if (!mqttReconnect_soft()) {
          return 1;
        }
      }
      //Must use original topic, so recreate from original Unique ID
      //This will immediately remove/delete the device/entities from HA
      Serial.println("Removing discovered devices...");

      //supTDS Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "supTDS/config");
      client.publish(topic, "");

      //prediTDS Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "prediTDS/config");
      client.publish(topic, "");

      //outTDS Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "outTDS/config");
      client.publish(topic, "");

      //IP Diagnostics Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "IP/config");
      client.publish(topic, "");

      //MAC Address Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "MAC/config");
      client.publish(topic, "");



      Serial.println("All Devices Removed...");

      return 0;
    }
  } else {
    //MQTT not enabled... should never hit this as it is checked before calling  
    return 90;
  }
  //catch all
  return 99;
}

/*Might not be required anymore can delete when working and after using baseline tds reading
void mqttConnect(){

    
    client.setServer(mqtt_server, 1883);
    client.setBufferSize(512); 
    client.setCallback(callback);

    if (client.connect("TDSMonitor", "mqtt-user", "21October11")) {
    client.publish("Aquarium","TDS Monitor Connected!");
    client.subscribe("Aquarium");
    Serial.println("MQTT Connected - Connection Mesage Sent!");

    //send baseline TDS reading
    sprintf(mqtt_tds_SUP, "TDS-SUP: %d",(int)tdsValue_SUP);
    client.publish(mqtt_pub_topic_supTDS, mqtt_tds_SUP);
    Serial.println("Supply TDS Telemetry Sent Successfully!");
    Serial.println("");
    sprintf(mqtt_tds_PRE_DI, "TDS-PRE_DI: %d",(int)tdsValue_PRE_DI);
    client.publish(mqtt_pub_topic_prediTDS, mqtt_tds_PRE_DI);
    Serial.println("Pre-DI TDS Telemetry Sent Successfully!");
    Serial.println("");
    sprintf(mqtt_tds_OUT, "TDS-OUT: %d",(int)tdsValue_OUT);
    client.publish(mqtt_pub_topic_outTDS, mqtt_tds_OUT);
    Serial.println("Output TDS Telemetry Sent Successfully!");
    Serial.println("");
  }

}
*/

void mqttReconnect() {
  int retries = 0;
  // Loop until we're reconnected
  while (!client.connected()) {
    if(retries <150)
    {
      //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
      Serial.print("Attempting to Re-Establish MQTT Connection...");
      //#endif
      if (client.connect(mqttClient.c_str(), mqttUser.c_str(), mqttPW.c_str())) 
      {
        //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
          Serial.println("TDS Monitor Re-connected After Failure!");
        //#endif
        // ... and resubscribe
        client.subscribe("Aquarium/#");
      } 
      else 
      {
        //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
          Serial.print("failed, rc=");
          Serial.print(client.state());
          Serial.println(" try again in 5 seconds");
        //#endif
        retries++;
        // Wait 5 seconds before retrying
        delay(5000);
      }
    }
    if ((retries > 149) && (mqttEnabled))
    {
    ESP.restart();
    }
  }
}

bool mqttReconnect_soft() {
  //Attempt MQTT reconnect.  If fails, return false instead of forcing ESP Reboot
  int retries = 0;
  while (!client.connected()) {
    if(retries < 10)
    {
      //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
        Serial.print("Attempting to Re-Establish MQTT Connection...");
      //#endif
      if (client.connect(mqttClient.c_str(), mqttUser.c_str(), mqttPW.c_str())) 
      {
        //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
          Serial.println("TDS Monitor Re-connected After Failure!");
        //#endif
        // ... and resubscribe
        client.subscribe("Aquarium/#");
        return true;
      } 
      else 
      {
        //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
          Serial.print("failed, rc=");
          Serial.print(client.state());
          Serial.println(" try again in 5 seconds");
        //#endif
        retries++;
        // Wait 3 seconds before retrying
        delay(3000);
        yield();
      }
    } 
    if ((retries > 9) && (mqttEnabled)) 
    {
       return false;
    }
  }
}

/* old mqtt call back, delete when working
void mqttCallback(char* topic, byte* payload, unsigned int length) {
    // message received
}
*/

void GravityTDS_setup(){

  gravityTds_SUP.setPin(TDS_SUP_Pin);
  gravityTds_SUP.setAref(5.0);       //reference voltage on ADC, default 5.0V on Arduino UNO
  gravityTds_SUP.setAdcRange(1024);  //1024 for 10bit ADC;4096 for 12bit ADC
  gravityTds_SUP.begin();            //initialization
  gravityTds_PRE_DI.setPin(TDS_PRE_DI_Pin);
  gravityTds_PRE_DI.setAref(5.0);       //reference voltage on ADC, default 5.0V on Arduino UNO
  gravityTds_PRE_DI.setAdcRange(1024);  //1024 for 10bit ADC;4096 for 12bit ADC
  gravityTds_PRE_DI.begin();            //initialization
  gravityTds_OUT.setPin(TDS_OUT_Pin);
  gravityTds_OUT.setAref(5.0);       //reference voltage on ADC, default 5.0V on Arduino UNO
  gravityTds_OUT.setAdcRange(1024);  //1024 for 10bit ADC;4096 for 12bit ADC
  gravityTds_OUT.begin();            //initialization

}

void GravityTDS_results(){

  //temperature = readTemperature();  //add your temperature sensor and read it

  //Calculate TDS from Supply Water
  gravityTds_SUP.setTemperature(temperature);   // set the temperature and execute temperature compensation
  gravityTds_SUP.update();                      //sample and calculate
  tdsValue_SUP = gravityTds_SUP.getTdsValue();  // then get the value

  //Calculate TDS from Output of Membrane
  gravityTds_PRE_DI.setTemperature(temperature);      // set the temperature and execute temperature compensation
  gravityTds_PRE_DI.update();                         //sample and calculate
  tdsValue_PRE_DI = gravityTds_PRE_DI.getTdsValue();  // then get the value
  
  //Calculate TDS from Output
  gravityTds_OUT.setTemperature(temperature);   // set the temperature and execute temperature compensation
  gravityTds_OUT.update();                      //sample and calculate
  tdsValue_OUT = gravityTds_OUT.getTdsValue();  // then get the value

  if(tdsValue_SUP == prevtdsValue_SUP) {
    Serial.print("Supply TDS reading hasnt changed (Current/Last Read): ");
    Serial.print(tdsValue_SUP, 0); Serial.print("ppm");
    Serial.print(" : ");
    Serial.print(prevtdsValue_SUP); Serial.println("ppm");
    delay(100);
  //  return;
  } else {
    prevtdsValue_SUP = tdsValue_SUP;
    Serial.print("SUP TDS: "); Serial.print(tdsValue_SUP, 0); Serial.println("ppm");
   
    sprintf(mqtt_tds_SUP, "%d",(int)tdsValue_SUP);
 	  client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
   
    sprintf(mqtt_tds_SUP2, "TDS-SUP: %d",(int)tdsValue_SUP);
    client.publish(mqtt_pub_topic_supTDS, mqtt_tds_SUP2);

    Serial.println("Supply TDS Telemetry Sent Successfully!");
    delay(100);
  }

  if(tdsValue_PRE_DI == prevtdsValue_PRE_DI) {
    Serial.print("Pre-DI TDS reading hasnt changed (Current/Last Read): ");
    Serial.print(tdsValue_PRE_DI, 0); Serial.print("ppm");
    Serial.print(" : ");
    Serial.print(prevtdsValue_PRE_DI); Serial.println("ppm");
    delay(100);
  //  return;
  } else {
    prevtdsValue_PRE_DI = tdsValue_PRE_DI;
    Serial.print("PRE-DI TDS: "); Serial.print(tdsValue_PRE_DI, 0); Serial.print("ppm");
    
    sprintf(mqtt_tds_PRE_DI, "%d",(int)tdsValue_PRE_DI);
  	client.publish(mqtt_prediTDS_stat, mqtt_tds_PRE_DI);
    
    sprintf(mqtt_tds_PRE_DI2, "TDS-PRE_DI: %d",(int)tdsValue_PRE_DI);
    client.publish(mqtt_pub_topic_prediTDS, mqtt_tds_PRE_DI2);
    
    Serial.println("Pre-DI TDS Telemetry Sent Successfully!");
    delay(100);
  }
    if(tdsValue_OUT == prevtdsValue_OUT) {
    Serial.print("Output TDS reading hasnt changed (Current/Last Read): ");
    Serial.print(tdsValue_OUT, 0); Serial.print("ppm");
    Serial.print(" : ");
    Serial.print(prevtdsValue_OUT); Serial.println("ppm");
    delay(100);
  //  return;
  } else {
    prevtdsValue_OUT = tdsValue_OUT;
    Serial.print("OUT TDS: "); Serial.print(tdsValue_OUT, 0); Serial.println("ppm");
    
    sprintf(mqtt_tds_OUT, "%d",(int)tdsValue_OUT);
  	client.publish(mqtt_outTDS_stat, mqtt_tds_OUT);
    
    sprintf(mqtt_tds_OUT2, "TDS-OUT: %d",(int)tdsValue_OUT);
    client.publish(mqtt_pub_topic_outTDS, mqtt_tds_OUT2);
    
    Serial.println("Output TDS Telemetry Sent Successfully!");
    delay(100);
  }

  delay(3000);
}

void setup() {

  // Serial monitor
  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.begin(115200);
    Serial.println("Booting...");
  //#endif

  //RGB_LED_Setup();

  // -------------
  // SETUP FASTLED  
  // -------------
  FastLED.addLeds<WS2812B, LED_DATA_PIN, GRB>(LEDs, NUM_LEDS_MAX);
  FastLED.setDither(false);
  FastLED.setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(activeBrightness);

  setup_Wifi();
  //mqttConnect();
  client.setCallback(callback);
  resetWiFiSettings();
  GravityTDS_setup();
  MDNS.begin(host);
  //Get MAC address when joining wifi and place into char array
  WiFi.macAddress(macAddr);
  //Call routing (or embed here) to create initial Unique ID
  createDiscoveryUniqueID();
  //Handle web callbacks for enabling or disabling discovery (using this method is just one of many ways to do this)

  // Default Wifi to station mode - fixes issue #16
  // Local AP will be handed by WifiManager for onboarding
  WiFi.mode(WIFI_STA);
  // -----------------------------------------
  //  Captive Portal and Wifi Onboarding Setup
  // -----------------------------------------
  //clean FS, for testing - uncomment next line ONLY if you wish to wipe current FS
  //SPIFFS.format();
  // *******************************
  // read configuration from FS json
  // *******************************
  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.println("mounting FS...");
  //#endif
  readConfigFile();

  // The extra parameters to be configured (can be either global or just in the setup)
  // After connecting, parameter.getValue() will get you the configured value
  // id/name placeholder/prompt default length
  
  WiFiManagerParameter custom_text("<p>Each parking assistant must have a unique name. Letters and numbers only, no spaces, 16 characters max. See the wiki documentation for more info.</p>");
  WiFiManagerParameter custom_dev_id("devName", "Unique Device Name", "parkasst", 16, " 16 chars/alphanumeric only");

  //set config save notify callback
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  //set static ip
  //wifiManager.setSTAStaticIPConfig(IPAddress(10, 0, 1, 99), IPAddress(10, 0, 1, 1), IPAddress(255, 255, 255, 0));

  //add all your parameters here
  wifiManager.addParameter(&custom_text);
  wifiManager.addParameter(&custom_dev_id);

  //reset settings - for testing
  //wifiManager.resetSettings();

  //sets timeout until configuration portal gets turned off
  //useful to make it all retry or go to sleep
  //in seconds
  wifiManager.setTimeout(360);

  //fetches ssid and pass and tries to connect
  //if it does not connect it starts an access point with the specified name "ESP_ParkingAsst"
  //If not supplied, will use ESP + last 7 digits of MAC
  //and goes into a blocking loop awaiting configuration. If a password
  //is desired for the AP, add it after the AP name (e.g. autoConnect("MyApName", "12345678")
  if (!wifiManager.autoConnect("TDS Monitor")) {  //
    //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
      Serial.println("failed to connect and hit timeout");
    //#endif
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
    ESP.restart();
    delay(5000);
  }


  //Get custom device name and set wifi and OTA host name, mqttclient and 
  if (shouldSaveConfig) {
    strcpy(device_name, custom_dev_id.getValue());
  }
  deviceName = String(device_name);
  //Use device name to define host names and mqttclient name
  wifiHostName = deviceName;
  otaHostName = deviceName + "OTA";
  mqttClient = deviceName;

  //if you get here you have connected to the WiFi
  WiFi.hostname(wifiHostName.c_str());
  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.println("connected to your wifi...yay!");
  //#endif
  

  //If callback was excuted, flag was set to save parameters
  if (shouldSaveConfig) {
    updateBootSettings(false);  //don't reboot
    delay(1000);
    readConfigFile();  //Load settings
  }

  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.println("local ip");
    Serial.println(WiFi.localIP());
  //#endif
  localIP = WiFi.localIP().toString();
  WiFi.macAddress(macAddr);         //Array for Unique ID gen
  strMacAddr = WiFi.macAddress();   //String for MQTT
  // ----------------------------


  mqttAddr_1 = (String(mqtt_addr_1)).toInt();
  mqttAddr_2 = (String(mqtt_addr_2)).toInt();
  mqttAddr_3 = (String(mqtt_addr_3)).toInt();
  mqttAddr_4 = (String(mqtt_addr_4)).toInt();
  //Disable MQTT if IP = 0.0.0.0
  if ((mqttAddr_1 == 0) && (mqttAddr_2 == 0) && (mqttAddr_3 == 0) && (mqttAddr_4 == 0)) {
    mqttPort = 0;
    mqttEnabled = false;         
    mqttConnected = false;       
    
  } else {
    mqttPort = (String(mqtt_port)).toInt();
    mqttTelePeriod = (String(mqtt_tele_period)).toInt();
    mqttUser = String(mqtt_user);
    mqttPW = String(mqtt_pw);
    mqttTopicSub = String(mqtt_topic_sub);
    mqttTopicPub = String(mqtt_topic_pub);
    mqttEnabled = true;
  }

  //------------------------------
  // Setup handlers for web calls
  //------------------------------
  server.on("/", handleRoot);

  server.on("/postform/", handleForm);

  server.onNotFound(handleNotFound);

  server.on("/update", handleUpdate);

  server.on("/restart", handleRestart);

  server.on("/reset", handleReset);

  server.on("/discovery", handleDiscovery);

  server.on("/discoveryEnabled", enableDiscovery);

  server.on("/discoveryDisabled", disableDiscovery);

  server.on("/calibration", calibration);

  server.on("/otaupdate",[]() {
    //Called directly from browser address (//ip_address/otaupdate) to put controller in ota mode for uploadling from Arduino IDE
    server.send(200, "text/html", "<h1>Ready for upload...<h1><h3>Start upload from IDE now</h3>");
    ota_flag = true;
    ota_time = ota_time_window;
    ota_time_elapsed = 0;
  });

  //Firmware Update Handler
  httpUpdater.setup(&server, "/update2");
  httpUpdater.setup(&server);
  server.begin();
  //#if defined(SERIAL_DEBUG) && (SERIAL_DEBUG == 1)
    Serial.println("Setup complete - starting main loop");
  //#endif

  //PROBABLY DONT NEED BUT KEEP TIL WORKING
  server.on("/discovery_on",[]() {
    server.send(200, "text/html", "<h1>Discovery ON...<h1><h3>Home Assistant MQTT Discovery enabled</h3>");
    delay(200);
    auto_discovery = true;
    byte retVal = haDiscovery(true);
  });
  server.on("/discovery_off",[]() {
    server.send(200, "text/html", "<h1>Discovery OFF...<h1><h3>Home Assistant MQTT Discovery disabled. Previous entities removed.</h3>");
    delay(200);
    auto_discovery = false;
    byte retVal = haDiscovery(false);
  });

  // =====================
  //  MQTT Setup
  // =====================

  if (mqttEnabled) {
    //Attempt to connect to MQTT broker - if fails, disable MQTT
    if (!setup_mqtt()) {
      mqttEnabled = false;
    }
  }

  //-----------------------------
  // Setup OTA Updates
  //-----------------------------
  ArduinoOTA.setHostname(otaHostName.c_str());
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_FS
      type = "filesystem";
    }
    // NOTE: if updating FS this would be the place to unmount FS using FS.end()
  });
  ArduinoOTA.begin();

}
 
void loop() {

  if (WiFi.status() != WL_CONNECTED) {
    wifiReconnect();
  }


  //RGB_LED();
  GravityTDS_results();
  resetWiFiSettings();
  server.handleClient();

//Handle OTA updates when OTA flag set via HTML call to http://ip_address/otaupdate
  if (ota_flag) {
    updateOTA();  //Show update on LED strip
    uint32_t ota_time_start = millis();
    while (ota_time_elapsed < ota_time) {
      ArduinoOTA.handle();  
      ota_time_elapsed = millis()-ota_time_start;   
      delay(10); 
    }
    ota_flag = false;
    updateSleepMode();
  }

  uint32_t currentMillis = millis();
  int16_t distance = 0;

/*
******** UPDATE WITH SOMETHING LIKE IF STATE CHANGE FOR TDS

  //v0.44 - force MQTT update if car state changes
  if (carDetected != prevCarStatus) {
    prevCarStatus = carDetected;
    forceMQTTUpdate = true;
  }

*/
 
  //Put system to sleep if parking or exit time elapsed 
  uint32_t elapsedTime = currentMillis - startTime;
  if (((elapsedTime > (maxOperationTimePark * 1000)) && (parkSleepTimerStarted)) || ((elapsedTime > (maxOperationTimeExit * 1000)) && (exitSleepTimerStarted  ))) {
    updateSleepMode();
    isAwake = false;
    startTime = currentMillis;
    exitSleepTimerStarted = false;
    parkSleepTimerStarted = false;
  }

  //Handle MQTT calls (if enabled)
  if (mqttEnabled) {
  #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2))
    if (!client.connected()) 
    {
      mqttReconnect();
    }
    client.loop();
  #endif
    
  }

  //Update MQTT Stats per tele period
  if (mqttEnabled) {
    if (((currentMillis - mqttLastUpdate) > (mqttTelePeriod * 1000)) || (forceMQTTUpdate)) {
      mqttLastUpdate = currentMillis;
      forceMQTTUpdate = false;
      if (!client.connected()) {
        mqttReconnect();
      }
    
    //Keep to use with own criteria
    /*
      // Publish MQTT values
      char outMsg[6];
      byte carStatus = 0;
      float measureDistance = 0;
      if (carDetected) carStatus = 1;

      sprintf(outMsg, "%1u",carStatus);
      client.publish(("homeassistant/stat/" + mqttTopicPub + "/cardetected").c_str(), outMsg, true);
      sprintf(outMsg, "%2.1f", measureDistance);
      client.publish(("homeassistant/stat/" + mqttTopicPub + "/parkdistance").c_str(), outMsg, true);
    
    */    
    }
  }
  //Show/Refresh LED Strip
  FastLED.show();

  delay(200);
}










The right part of the assignment is evaluated first before being added (concatenated) to calSensors

The challenge is that "Current Supply TDS: " is a string literal (a const char * or const char [21] in this case) and tdsValue_SUP is a float.

You can do math on pointers but not with floats and you don't want pointer arithmetics but concatenation.

What you want to do, assuming calSensors is a String instance, is to either tell the compiler the + on the right side is for concatenation of Strings, for example (with 3 digits in tdsValue_SUP representation)

calSensors += String("Current Supply TDS: ") + String (tdsValue_SUP,3) + String("<br>");

or proceed by smaller steps (better for memory)

calSensors += "Current Supply TDS: ";
calSensors +=  tdsValue_SUP;
calSensors +=  "<br>";

This way the + in += is seen as a concatenation of a String and something else and the overloaded + operator performing the concatenation with various types is called. (the default for the float will be to get you 2 digits).

thanks for this was taking a bit to figure out what my next question is because just after posting the above I tried what you shared and it worked before you even posted which was a bit exciting for me lol.

But then I realised I needed to convert the float into a string to be able to display without a decimal, something like

  sprintf(s_tdsValue_SUP, "%.0f", tdsValue_SUP);

I am still experimenting but once I convert the float to a string with the above, what is the rule with the HTML code:

  sprintf(s_tdsValue_SUP, "%.0f", tdsValue_SUP);

<html> (ignore this, just for visual representation that the string conversion is above the html code)

calSensors += "Current Supply TDS: ";
calSensors +=  s_tdsValue_SUP;
calSensors +=  "<br>";

I think this is right? testing it now, but even if it works I would like to know if you think this is a long way of doing something that can be achieved with less conversion and switches

Not sure what you mean by html?

If you want to stick to Strings you could do

calSensors += String(tdsValue_SUP,0);

That’s building a string representation of the float with 0 digit after the decimal point

Alternatively you could cast the float into a integral for the concatenation. Note that the cast will truncate and not round the float to the nearest integral number.

calSensors += (long) tdsValue_SUP;

PS: on smaller arduinos like the UNO or MEGA sprintf() does not support %f

This is my code and it is serving a html page

void calibration() {

  String saveCalibration;
  
  sprintf(w_tdsValue_SUP, "%.0f", tdsValue_SUP);
  sprintf(w_tdsValue_PREDI, "%.0f", tdsValue_PREDI);
  sprintf(w_tdsValue_OUT, "%.0f", tdsValue_OUT);

  saveCalibration = server.arg("calsave");

  String calSensors = "<html>\
    </head>\
      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
      <title>TDS Monitor - Sensor Calibration</title>\
      <style>\
        body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
      </style>\
    </head>\
    <body>\
    <H1>Calibrate TDS Sensor</H1><br>\
    <H3>Current values are:</H3>";
  //these are labels  
  calSensors += "<b>TDS Sensors</b><br>";

  calSensors += "Current Supply TDS: ";
  calSensors += w_tdsValue_SUP;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "Current Pre-DI TDS: ";
  calSensors += w_tdsValue_PREDI;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "Current Output TDS: ";
  calSensors += w_tdsValue_OUT;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "<form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/postcalibration/\">\
      <table>\
      <tr>\
      <td><label for=\"calkValue_SUP\">Calibrated Supply TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"calkValue_SUP\" style=\"width: 50px\;\" value=\"";
  calSensors += String(kValue_SUP);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"calkValue_PREDI\">Calibrated Pre-DI TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"calkValue_PREDI\" style=\"width: 50px\;\" value=\"";
  calSensors += String(kValue_PREDI);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"calkValue_OUT\">Calibrated Output TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"calkValue_OUT\" style=\"width: 50px\;\" value=\"";
  calSensors += String(kValue_OUT);
  calSensors += "\"> ppm<br></td></tr>\"";
  calSensors += "<br>";
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "/discovery\">Configure Home Assistant MQTT Discovery</a><br>"; 
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "\">Return to settings</a><br>";
  calSensors += "</td></tr>\
        </table><br>\
        <input type=\"checkbox\" name=\"calsave\" value=\"save\">Store Calibration and Replace Defaults? (Controller Will Reboot)<br><br>\
        <input type=\"submit\" value=\"Submit Calibration\">\
      </form>\
      <br>\
      <h2>Controller Commands</h2>\
      Caution: Restart and Reset are executed immediately when the button is clicked.<br>\
      <table border=\"1\" cellpadding=\"10\">\
      <tr>\
      <td><button id=\"btnrestart\" onclick=\"location.href = './restart';\">Restart</button></td><td>This will reboot controller and reload default boot values.</td>\
      </tr><tr>\
      <td><button id=\"btnreset\" style=\"background-color:#FAADB7\" onclick=\"location.href = './reset';\">RESET ALL</button></td><td><b>WARNING</b>: This will clear all settings, including WiFi! You must complete initial setup again.</td>\
      </tr><tr>\
      <td><button id=\"btnupdate\" onclick=\"location.href = './update';\">Firmware Upgrade</button></td><td>Upload and apply new firmware from local file.</td>\
      </tr></table><br>\
      Current version: VAR_CURRRENT_VER\
    </body>\
  </html>";

  calSensors.replace("VAR_DEVICE_NAME", deviceName); 
  calSensors.replace("VAR_CURRRENT_VER", VERSION);
  server.send(200, "text/html", calSensors);
  delay(1000);
  if (saveCalibration == "save") {
    updateSettings(true);
  } else {
    updateSettings(false);
  } 
}

that's rebuilding a heck of a long String in memory every time you call calibration()...

so why do you have some sprintf() and

that you concatenate

  calSensors += w_tdsValue_SUP;

also some direct String manipulation?

  calSensors += String(kValue_OUT);

Lol I never said that I am doing it efficiently :stuck_out_tongue:

  sprintf(w_tdsValue_SUP, "%.0f", tdsValue_SUP);
  sprintf(w_tdsValue_PREDI, "%.0f", tdsValue_PREDI);
  sprintf(w_tdsValue_OUT, "%.0f", tdsValue_OUT);

to give me:

image

^^ notice the rogue " lol

  calSensors += w_tdsValue_SUP;
  calSensors += String(kValue_OUT);

Intentions with the above is to have an input box to allow input, that when I click save and calibrate it will update the config file which is loaded on startup, essentially giving me a new reference point in my TDS calculation

image

nor consistently either :slight_smile:
(that's OK as long as you are happy with the result)

lol i thought I was but happy for you to tell me how to do it better because for example I am finding I am backing myself into a corner with the following

image

void readConfigFile() {
  if (SPIFFS.begin()) {
    
      Serial.println("mounted file system");
    //#endif
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      
        Serial.println("reading config file");
      //#endif
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        
          Serial.println("opened config file");
        //#endif
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);

        configFile.readBytes(buf.get(), size);

        DynamicJsonDocument json(1024);
        auto deserializeError = deserializeJson(json, buf.get());
        serializeJson(json, Serial);
        if ( ! deserializeError ) {
         
            Serial.println("\nparsed json");
         //#endif
          // Read values here from SPIFFS (v0.42 - add defaults for all values in case they don't exist to avoid potential boot loop)
          strcpy(mqtt_addr_1, json["mqtt_addr_1"]|"0");
          strcpy(mqtt_addr_2, json["mqtt_addr_2"]|"0");
          strcpy(mqtt_addr_3, json["mqtt_addr_3"]|"0");
          strcpy(mqtt_addr_4, json["mqtt_addr_4"]|"0");
          strcpy(mqtt_port, json["mqtt_port"]|"0");
          strcpy(calkValue_SUP, json["calkValue_SUP"]|"1.0");
          strcpy(calkValue_PREDI, json["calkValue_PREDI"]|"1.0");
          strcpy(calkValue_OUT, json["calkValue_OUT"]|"1.0");

          //Need to set wifihostname here
          deviceName = String(device_name);
        } else {
          
            Serial.println("failed to load json config");
          //#endif
        }
        configFile.close();
      }
    }
    SPIFFS.end();  

void calibrateTDSprobe() {
  char *cmdReceivedBufferPtr;
  static boolean ecCalibrationFinish = 0;
  static boolean enterCalibrationFlag = 0;
  float KValueTemp_SUP,rawECsolution_SUP;

  rawECsolution_SUP = strtod(cmdReceivedBufferPtr,NULL)/(float)(TdsFactor);
  rawECsolution_SUP = rawECsolution_SUP*(1.0+0.02*(temperature-25.0));
  Serial.println("Calibrating TDS Sensors: Supply, Pre-DI, and Output......");
  Serial.print("rawECsolution:");
  Serial.print(rawECsolution_SUP);
  Serial.print("  ecvalue:");
  Serial.println(ecValue_SUP);
          
  KValueTemp_SUP = rawECsolution_SUP/(133.42*voltage_SUP*voltage_SUP*voltage_SUP - 255.86*voltage_SUP*voltage_SUP + 857.39*voltage_SUP);  //calibrate in the  buffer solution, such as 707ppm(1413us/cm)@25^c
  if((rawECsolution_SUP>0) && (rawECsolution_SUP<2000) && (KValueTemp_SUP>0.25) && (KValueTemp_SUP<4.0))
  {
  Serial.println();
  Serial.print("Calibration Successful");
  Serial.print(KValueTemp_SUP);
  calkValue_SUP =  KValueTemp_SUP;
  updateSettings(true);

  } else {
  updateSettings(false);
  Serial.print("Calibration Failed");
  }
}

void setup() {
  readConfigFile();
  kValue_SUP = (String(calkValue_SUP)).toFloat();
  kValue_PREDI = (String(calkValue_PREDI)).toFloat();
  kValue_OUT = (String(calkValue_OUT)).toFloat();


getting the error

error: incompatible types in assignment of 'float' to 'char [4]'
   calkValue_SUP =  KValueTemp_SUP;

I guessing this is due to my inability to understand the differences in datatypes and because I am changing them to suit my eye but not my code :stuck_out_tongue:

I believe the part where this is important, is to write to the config file so it needs to be there, just not sure how

post the full code so that we cans see what the types are

I would design a static page and use AJAX to get the values to show in the various fields as a separate get request. This way the page can be in flash and you don't need to use precious RAM

if you have an ESP32 I would use the ESPAsyncWebServer to handle all the web communication.

Thanks, the above is all the code that references that variable, but I will post the full project I was just saving you some scrolling through all the junk :slight_smile:

//Include Libraries
#include <Arduino.h>
#include <FS.h>           //Arduino ESP8266 Core - Handles filesystem functions (read/write config file)
#include <LCD_I2C.h>
#include <FastLED.h>    //https://github.com/FastLED/FastLED - LED functionality
#include <Arduino.h>
#include <WiFiManager.h>  //https://github.com/tzapu/WiFiManager (must be v2.0.8-beta or later) - Wifi Onboarding with Portal
#include <PubSubClient.h> //https://github.com/knolleary/pubsubclient  Provides MQTT functions
#include <ArduinoJson.h>  //https://github.com/bblanchon/ArduinoJson
#include <WebServer.h>    //Used for HTTP callback to enable/disable discovery
#include <HTTPUpdateServer.h>
#include <ArduinoOTA.h>   //https://github.com/jandrassy/ArduinoOTA
#include <ESPmDNS.h>
#include <Update.h>

#ifdef ESP32
  #include <SPIFFS.h>
#endif

#define VERSION "Firebeetle 2 (ESP32)"

//Setup RGB LED for Feedback
// Only one led is connected. The led is connected on IO5
//#define NUM_LEDS 1
//#define DATA_PIN 5
#define LED_DATA_PIN 5
#define WIFIMODE 2                       // 0 = Only Soft Access Point, 1 = Only connect to local WiFi network with UN/PW, 2 = Both
#define MQTTMODE 1                       // 0 = Disable MQTT, 1 = Enable (will only be enabled if WiFi mode = 1 or 2 - broker must be on same network)
#define NUM_LEDS_MAX 1                   // For initialization - recommend actual max 50 LEDs if built as shown

//Define PinOut
#define TDS_SUP_Pin A0
#define TDS_PREDI_Pin A1
#define TDS_OUT_Pin A4
#define TdsFactor 0.5  // tds = ec / 2

//CRGB leds[NUM_LEDS];

//PubSubClient
WiFiClient espClient;
PubSubClient client(espClient);

//WebServer
WebServer server(80);
//WebServer server;
HTTPUpdateServer httpUpdater;

//const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
const char* host = "TDSMonitor-webupdate";

const char* mqtt_server = "10.0.0.8";
//const char* mqtt_port = "1883";
const char* mqtt_client = "TDSMonitor";
const char* mqtt_pub_topic_supTDS = "Aquarium/TDSMonitor/Sensor/supTDS";
const char* mqtt_pub_topic_prediTDS = "Aquarium/TDSMonitor/Sensor/prediTDS";
const char* mqtt_pub_topic_outTDS = "Aquarium/TDSMonitor/Sensor/outTDS";
const char* mqtt_sub_topic = "Aquarium";
const char* mqtt_supTDS_stat = "homeassistant/stat/TDSMonitor/supTDS";
const char* mqtt_prediTDS_stat = "homeassistant/stat/TDSMonitor/prediTDS";
const char* mqtt_outTDS_stat = "homeassistant/stat/TDSMonitor/outTDS";
const char* mqtt_IP_stat = "homeassistant/stat/TDSMonitor/ipaddress";

//Auto-discover enable/disable option
bool auto_discovery = false;      //default to false and provide end-user interface to allow toggling  

//Arduino OTA
bool ota_flag = true;                       // Must leave this as true for board to broadcast port to IDE upon boot
uint16_t ota_boot_time_window = 2500;       // minimum time on boot for IP address to show in IDE ports, in millisecs
uint16_t ota_time_window = 20000;           // time to start file upload when ota_flag set to true (after initial boot), in millsecs
uint16_t ota_time_elapsed = 0;              // Counter when OTA active
uint16_t ota_time = ota_boot_time_window;

//Define device name
String deviceName = "TDSMonitor";
String wifiHostName = "TDSMonitor";
String otaHostName = "TDSMonitorOTA";
String mqttClient = "TDSMonitor";

// ===============================
//  MQTT Variables
// ===============================
//  MQTT will only be used if a server address other than '0.0.0.0' is entered via portal
byte mqttAddr_1 = 0;
byte mqttAddr_2 = 0;
byte mqttAddr_3 = 0;
byte mqttAddr_4 = 0;
int mqttPort = 0;
String mqttUser = "myusername";
String mqttPW = "mypassword";
uint16_t mqttTelePeriod = 60;
uint32_t mqttLastUpdate = 0;
String mqttTopicSub ="TDSMonitor";  //v0.41 (for now, will always be same as pub)
String mqttTopicPub = "TDSMonitor"; //v0.41 

bool mqttEnabled = false;         //Will be enabled/disabled depending on whether a valid IP address is defined in Settings (0.0.0.0 disables MQTT)
bool mqttConnected = false;       //Will be enabled if defined and successful connnection made.  This var should be checked upon any MQTT action.
bool prevCarStatus = false;       //v0.44 for forcing MQTT update on state change
bool forceMQTTUpdate = false;     //v0.44 for forcing MQTT update on state change

//Variables for creating unique entity IDs and topics (HA discovery)
byte macAddr[6];               //Device MAC address (array is in reverse order)
String strMacAddr;             //MAC address as string and in proper order
char uidPrefix[] = "TDSMonitor";   //Prefix for unique ID generation
char devUniqueID[30];          //Generated Unique ID for this device (uidPrefix + last 6 MAC characters)

//---- Captive Portal -------
//flag for saving data in captive portal
bool shouldSaveConfig = false;

//callback notifying us of the need to save config
void saveConfigCallback () {
  shouldSaveConfig = true;
}
//---------------------------


//VARIABLES FOR PORTAL USE (JSON vars)
char device_name[18];    //v0.41
char wifi_hostname[18];  //v0.41
char ota_hostname[18];   //v0.41

char mqtt_addr_1[4];
char mqtt_addr_2[4];
char mqtt_addr_3[4];
char mqtt_addr_4[4];
char mqtt_port[6];
char mqtt_user[65];
char mqtt_pw[65];
char mqtt_tele_period[4];
char mqtt_topic_sub[18];   //v0.41
char mqtt_topic_pub[18];   //v0.41

char calkValue_SUP[4];
char calkValue_PREDI[4];
char calkValue_OUT[4];


String home_page_message = "";

WiFiManager wifiManager;
WiFiManager wm;

String localIP;

char mqtt_tds_SUP[50];
char mqtt_tds_PREDI[50];
char mqtt_tds_OUT[50];
char mqtt_tds_SUP2[50];
char mqtt_tds_PREDI2[50];
char mqtt_tds_OUT2[50];

//Define TDS Global Variables
float tdstemp = 25; 
float tdsAref = 5.0; 
float tdsAdcRange = 1024.0; 
float temperature;

//Define Supply TDS Variables
float tdsValue_SUP = 0;
float kValue_SUP = 1.0; 
float analogValue_SUP; 
float voltage_SUP; 
float ecValue_SUP; 
float ecValue25_SUP; 
int kValueAddress_SUP[4];
int prevtdsValue_SUP;

//Define PRE-DI TDS Variables
float tdsValue_PREDI = 0;
float kValue_PREDI = 1.0; 
float analogValue_PREDI; 
float voltage_PREDI; 
float ecValue_PREDI; 
float ecValue25_PREDI; 
int kValueAddress_PREDI[8];
int prevtdsValue_PREDI;

//Define Output TDS Variables
float tdsValue_OUT = 0;
float kValue_OUT = 1.0; 
float analogValue_OUT; 
float voltage_OUT; 
float ecValue_OUT; 
float ecValue25_OUT; 
int kValueAddress_OUT[12];
int prevtdsValue_OUT;

char w_tdsValue_SUP[8];
char w_tdsValue_PREDI[8];
char w_tdsValue_OUT[8];

char SUP_kValue[4];
char PREDI_kValue[4];
char OUT_kValue[4];

//==========================
// LED Setup & Portal Options
//==========================
// Defaults values - these will be set/overwritten by portal or last saved vals on reboot
int numLEDs = 30;        
byte activeBrightness = 100;
byte sleepBrightness = 5;
uint32_t maxOperationTimePark = 60;
uint32_t maxOperationTimeExit = 5;
String ledEffect_m1 = "Out-In";
bool showStandbyLEDs = true;

CRGB LEDs[NUM_LEDS_MAX];

CRGB ledColorOn_m1 = CRGB::White;
CRGB ledColorOff = CRGB::Black;
CRGB ledColorStandby = CRGB::Blue;
CRGB ledColorWake = CRGB::Green;
CRGB ledColorActive = CRGB::Yellow;
CRGB ledColorParked = CRGB::Red;
CRGB ledColorBackup = CRGB::Red;

//************************** Just Some basic Definitions used for the Up Time Logger ************//
long Day=0;
int Hour =0;
int Minute=0;
int Second=0;
int HighMillis=0;
int Rollover=0;
unsigned long previousMillis = 0;
unsigned long interval = 10000;
int TransmitInterval = interval;
// constants do not change:
int CurrentRSSI=100;


/*
void RGB_LED_Setup(){
  // Configure an array of (one) leds without SPI
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
}

void RGB_LED(){
  leds[0] = CRGB::Black;
  FastLED.show();
}
*/


//OTHER GLOBAL VARIABLES
bool blinkOn = false;
int intervalDistance = 0;
bool carDetected = false;
bool isAwake = false;
bool coldStart = true;

byte carDetectedCounter = 0;
byte carDetectedCounterMax = 3;
byte nocarDetectedCounter = 0;
byte nocarDetectedCounterMax = 10;
byte outOfRangeCounter = 0;
uint32_t startTime;
bool exitSleepTimerStarted = false;
bool parkSleepTimerStarted = false;


//Initial distances for default load (only used for initial onboarding)
byte uomDistance = 0;       // 0=inches, 1=centimeters
int wakeDistance = 3048;    // wake/sleep distance (~10ft)
int startDistance = 1829;   // Start countdown distance (~6')
int parkDistance = 610;     // Final parked distacce (~2')
int backupDistance = 457;   // Flash backup distance (~18")

//===============================
// Web pages and handlers
//===============================
// Main Settings page
// Root / Main Settings page handler

void handleRoot() {
  String mainPage = "<html>\
  <head>\
    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
    <title>TDS Monitor - Main</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
    </style>\
  </head>\
  <body>\
    <h1>Controller Settings (VAR_DEVICE_NAME)</h1><br>\
    Changes made here will be used <b><i>until the controller is restarted</i></b>, unless the box to save the settings as new boot defaults is checked.<br><br>\
    To test settings, leave the box unchecked and click 'Update'. Once you have settings you'd like to keep, check the box and click 'Update' to write the settings as the new boot defaults.<br><br>\
    If you want to change wifi settings or the device name, you must use the 'Reset All' command.<br><br>\
    <form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/postform/\">\
      <table>\
      <tr>\
      <b><u>MQTT Settings</u></b>:<br>\
      ONLY enter this information if you already have an MQTT broker configured. <u><i>To disable or remove MQTT functionality, set the IP address to 0.0.0.0</i></u><br>\
      Any changes to MQTT require that you check the box to update boot settings below, which will reboot the controller.  If a successful connection to your MQTT broker is made, \
      a retained message of \"connected\" will be published to the topic \"homeassistant/stat/your_topic/mqtt\".<br><br>";

  mainPage += "<table>\
      <tr>\
      <td><label for=\"mqttaddr1\">Broker IP Address:</label></td>\
      <td><input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr1\" style=\"width: 50px\;\" value=\"";
  mainPage += String(mqttAddr_1);
  mainPage += "\">.<input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr2\" style=\"width: 50px\;\" value=\"";  
  mainPage += String(mqttAddr_2);
  mainPage += "\">.<input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr3\" style=\"width: 50px\;\" value=\"";  
  mainPage += String(mqttAddr_3);
  mainPage += "\">.<input type=\"number\" min=\"0\" max=\"255\" step=\"1\" name=\"mqttaddr4\" style=\"width: 50px\;\" value=\"";  
  mainPage += String(mqttAddr_4);
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqttport\">MQTT Broker Port:</label></td>\
      <td><input type=\"number\" min=\"0\" max=\"65535\" step=\"1\" name=\"mqttport\" style=\"width: 65px\;\" value=\"";
  mainPage += String(mqttPort);
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqttuser\">MQTT User Name:</label></td>\
      <td><input type=\"text\" name=\"mqttuser\" maxlength=\"64\" value=\"";
  mainPage += mqttUser;
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqttpw\">MQTT Password:</label></td>\
      <td><input type=\"password\" name=\"mqttpw\" maxlength=\"64\" value=\"";
  mainPage += mqttPW;
  mainPage += "\"></td></tr>\
      <tr>\
      <td><label for=\"mqtttopic\">MQTT Topic: &nbsp;&nbsp; stat/</label></td>\
      <td><input type=\"text\" name=\"mqtttopic\" maxlength=\"16\" value=\"";
  mainPage += mqttTopicPub;

  mainPage += "\"> (16 alphanumeric chars max - no spaces, no symbols)</td></tr>\
      <tr>\
      <td><label for=\"mqttperiod\">Telemetry Period:</label></td>\
      <td><input type=\"number\" min=\"60\" max=\"600\" step=\"1\" name=\"mqttperiod\" style=\"width: 50px\;\" value=\"";
  mainPage += String(mqttTelePeriod);
  mainPage += "\"> seconds (60 min, 600 max)</td></tr>\
      <tr>\
      <td><label for=\"discovery\">MQTT Discovery:</label></td>\
      <td><a href=\"http://";
  mainPage += localIP;
  mainPage += "/discovery\">Configure Home Assistant MQTT Discovery</a>";    
  mainPage += "</td></tr>\
      <td><label for=\"calibration\">Sensor Calibration:</label></td>\
      <td><a href=\"http://";
  mainPage += localIP;
  mainPage += "/calibration\">Calibrate TDS Sensors</a>";    
  mainPage += "</td></tr>\
      </table><br>\
      <input type=\"checkbox\" name=\"chksave\" value=\"save\">Save all settings as new boot defaults (controller will reboot)<br><br>\
      <input type=\"submit\" value=\"Update\">\
    </form>\
    <br>\
    <h2>Controller Commands</h2>\
    Caution: Restart and Reset are executed immediately when the button is clicked.<br>\
    <table border=\"1\" cellpadding=\"10\">\
    <tr>\
    <td><button id=\"btnrestart\" onclick=\"location.href = './restart';\">Restart</button></td><td>This will reboot controller and reload default boot values.</td>\
    </tr><tr>\
    <td><button id=\"btnreset\" style=\"background-color:#FAADB7\" onclick=\"location.href = './reset';\">RESET ALL</button></td><td><b>WARNING</b>: This will clear all settings, including WiFi! You must complete initial setup again.</td>\
    </tr><tr>\
    <td><button id=\"btnupdate\" onclick=\"location.href = './update';\">Firmware Upgrade</button></td><td>Upload and apply new firmware from local file.</td>\
    </tr></table><br>\
    Current version: VAR_CURRRENT_VER\
  </body>\
</html>";
  mainPage.replace("VAR_DEVICE_NAME", deviceName); 
  mainPage.replace("VAR_CURRRENT_VER", VERSION);
  server.send(200, "text/html", mainPage);
}

// Settings submit handler - Settings results
void handleForm() {
  if (server.method() != HTTP_POST) {
    server.send(405, "text/plain", "Method Not Allowed");
  } else {
    String saveSettings;
    
    mqttAddr_1 = server.arg("mqttaddr1").toInt();
    mqttAddr_2 = server.arg("mqttaddr2").toInt();
    mqttAddr_3 = server.arg("mqttaddr3").toInt();
    mqttAddr_4 = server.arg("mqttaddr4").toInt();
    mqttPort = server.arg("mqttport").toInt();
    mqttUser = server.arg("mqttuser");
    mqttPW = server.arg("mqttpw");
    mqttTelePeriod = server.arg("mqttperiod").toInt();
    mqttTopicSub = server.arg("mqtttopic");
    mqttTopicPub = server.arg("mqtttopic");
    
    saveSettings = server.arg("chksave");
    
    String message = "<html>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>TDS Monitor - Current Settings</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Settings updated!</H1><br>\
      <H3>Current values are:</H3>";
    //example of message return types
    //message += "Device Name: " + deviceName + "<br>";
    //message += "Num LEDs: " + server.arg("leds") + "<br>";
    //message += "Active Brightness: " + server.arg("activebrightness") + "<br>";
    //message += "Standby Brightness: " + server.arg("sleepbrightness") + "<br><br>";
    //message += "<b>LED active On Times</b><br><br>";
    //message += "Park Time: " + server.arg("ledparktime") + " secs.<br>";
    //message += "Exit Time: " + server.arg("ledexittime") + " secs.<br><br>";
    //message += "Effect: " + server.arg("effect1") + "<br><br>";
    
    message += "<b>MQTT Settings</b><br><br>";
    if ((mqttAddr_1 == 0) && (mqttAddr_2 == 0) && (mqttAddr_3) == 0 && (mqttAddr_4 == 0)) {
      message += "MQTT: <b><u>Disabled</u></b> ";
      if (saveSettings != "save") {
        message += "(If you just changed MQTT settings, you must save as new boot defaults for this to take effect)<br>";
      }
    } else {
      message += "MQTT Server: " + server.arg("mqttaddr1") + "." + server.arg("mqttaddr2") +  "." + server.arg("mqttaddr3") + "." + server.arg("mqttaddr4") + "<br>";
      message += "MQTT Port: " + server.arg("mqttport") + "<br>";
      message += "MQTT User: " + server.arg("mqttuser") + "<br>";
      message += "MQTT Password: ***********<br>";
      message += "MQTT Topic: homeassistant/stat/" + server.arg("mqtttopic") + "<br>";
      message += "Telemetry Period: " + server.arg("mqttperiod") + " seconds<br>";
      if (saveSettings != "save") {
        message += "<br>(If you just changed MQTT settings, you must save as new boot defaults for this to take effect)<br>";
      }
    }
    message += "<br>";
    if (saveSettings == "save") {
      message += "<br>";
      message += "<b>New settings saved as boot defaults.</b> Controller will now reboot.<br>";
      message += "You can return to the settings page after boot completes (lights will briefly turn blue then red/green to indicate completed boot).<br>";    
    } else {
      //Wake up system so new setting can be seen/tested... even if car present
      carDetectedCounter = carDetectedCounterMax + 1;
    }
    message += "<br><a href=\"http://";
    message += localIP;
    message += "/discovery\">Configure Home Assistant MQTT Discovery</a><br>"; 
    message += "<br><a href=\"http://";
    message += localIP;
    message += "\">Return to settings</a><br>";
             
    message += "</body></html>";
    server.send(200, "text/html", message);
    delay(1000);
    if (saveSettings == "save") {
      updateSettings(true);
    } else {
      updateSettings(false);
    } 
  }
}

// Firmware update handler
void handleUpdate() {
  String updFirmware = "<html>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>TDS Monitor - Firmware Update</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Firmware Update</H1>\
      <H3>Current firmware version: ";
  updFirmware += VERSION;
  updFirmware += "</H3><br>";
  updFirmware += "Notes:<br>";
  updFirmware += "<ul>";
  updFirmware += "<li>The firmware update will begin as soon as the Update Firmware button is clicked.</li><br>";
  updFirmware += "<li>Your current settings will be retained.</li><br>";
  updFirmware += "<li><b>Please be patient!</b> The update will take a few minutes.  Do not refresh the page or navigate away.</li><br>";
  updFirmware += "<li>If the upload is successful, a brief message will appear and the controller will reboot.</li><br>";
  updFirmware += "<li>After rebooting, you'll automatically be taken back to the main settings page and the update will be complete.</li><br>";
  updFirmware += "</ul><br>";
  updFirmware += "</body></html>";    
  updFirmware += "<form method='POST' action='/update2' enctype='multipart/form-data'>";
  updFirmware += "<input type='file' accept='.bin,.bin.gz' name='Select file' style='width: 300px'><br><br>";
  updFirmware += "<input type='submit' value='Update Firmware'>";
  updFirmware += "</form><br>";
  updFirmware += "<br><a href=\"http://";
  updFirmware += localIP;
  updFirmware += "\">Return to settings</a><br>";
  updFirmware += "</body></html>";
  server.send(200, "text/html", updFirmware); 
}

void updateSettings(bool saveBoot) {
  // This updates the current local settings for current session only.  
  // Will be overwritten with reboot/reset/OTAUpdate
  if (saveBoot) {
    updateBootSettings(saveBoot);
  } else {
    //Update FastLED with new brightness values if changed
    if (isAwake) {
      FastLED.setBrightness(activeBrightness);
    } else {
      FastLED.setBrightness(sleepBrightness);
    }
  }
}

void updateBootSettings(bool restart_ESP) {
  // Writes new settings to SPIFFS (new boot defaults)
  char t_led_count[4];
  char t_led_brightness_active[4];
  char t_led_brightness_sleep[4];
  char t_led_park_time[4];
  char t_led_exit_time[4];
  char t_uom_distance[4];
  char t_wake_mils[6];
  char t_start_mils[6];
  char t_park_mils[6];
  char t_backup_mils[6];
  char t_color_standby[4];
  char t_color_wake[4];
  char t_color_active[4];
  char t_color_parked[4];
  char t_color_backup[4];
  char t_led_effect[16];
  int eff_len = 16;
  char t_mqtt_addr_1[4];
  char t_mqtt_addr_2[4];
  char t_mqtt_addr_3[4];
  char t_mqtt_addr_4[4];
  char t_mqtt_port[6];
  char t_mqtt_user[65];
  char t_mqtt_pw[65];
  char t_mqtt_tele_period[4];
  int user_len = 65;
  int user_pw = 65;
  //v0.41 new values
  char t_device_name[18];
  char t_mqtt_topic_sub[18];
  char t_mqtt_topic_pub[18];
  int dev_name_len = 18;
  int topic_len = 18;
  char t_kValue_SUP[4];
  char t_kValue_PREDI[4];
  char t_kValue_OUT[4];
  
    Serial.println("Attempting to update boot settings");
  //#endif

  //Convert values into char arrays
  sprintf(t_led_count, "%u", numLEDs);
  sprintf(t_led_brightness_active, "%u", activeBrightness);
  sprintf(t_led_brightness_sleep, "%u", sleepBrightness);
  sprintf(t_led_park_time, "%u", maxOperationTimePark);
  sprintf(t_led_exit_time, "%u", maxOperationTimeExit);
  sprintf(t_uom_distance, "%u", uomDistance);
  sprintf(t_wake_mils, "%u", wakeDistance);
  sprintf(t_start_mils, "%u", startDistance);
  sprintf(t_park_mils, "%u", parkDistance);
  sprintf(t_backup_mils, "%u", backupDistance);
  ledEffect_m1.toCharArray(t_led_effect, eff_len);
  sprintf(t_mqtt_addr_1, "%u", mqttAddr_1);
  sprintf(t_mqtt_addr_2, "%u", mqttAddr_2);
  sprintf(t_mqtt_addr_3, "%u", mqttAddr_3);
  sprintf(t_mqtt_addr_4, "%u", mqttAddr_4);
  sprintf(t_mqtt_port, "%u", mqttPort);
  sprintf(t_mqtt_tele_period, "%u", mqttTelePeriod);
  sprintf(t_kValue_SUP, "%u", kValue_SUP);
  sprintf(t_kValue_PREDI, "%u", kValue_PREDI);
  sprintf(t_kValue_OUT, "%u", kValue_OUT);


  mqttUser.toCharArray(t_mqtt_user, user_len);
  mqttPW.toCharArray(t_mqtt_pw, user_pw);
  deviceName.toCharArray(t_device_name, dev_name_len);
  mqttTopicSub.toCharArray(t_mqtt_topic_sub, topic_len);
  mqttTopicPub.toCharArray(t_mqtt_topic_pub, topic_len);

#ifdef ARDUINOJSON_VERSION_MAJOR >= 6
    DynamicJsonDocument json(1024);
#else
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
#endif

    json["mqtt_addr_1"] = t_mqtt_addr_1;
    json["mqtt_addr_2"] = t_mqtt_addr_2;
    json["mqtt_addr_3"] = t_mqtt_addr_3;
    json["mqtt_addr_4"] = t_mqtt_addr_4;
    json["mqtt_port"] = t_mqtt_port;
    json["mqtt_tele_period"] = t_mqtt_tele_period;
    json["mqtt_user"] = t_mqtt_user;
    json["mqtt_pw"] = t_mqtt_pw;
    json["calkValue_SUP"] = t_kValue_SUP;
    json["calkValue_PREDI"] = t_kValue_PREDI;
    json["calkValue_OUT"] = t_kValue_OUT;
    //v0.41
    json["device_name"] = t_device_name;
    json["mqtt_topic_sub"] = t_mqtt_topic_sub;
    json["mqtt_topic_pub"] = t_mqtt_topic_pub;

  if (SPIFFS.begin()) {
      File configFile = SPIFFS.open("/config.json", "w");
      if (!configFile) {
        
          Serial.println("failed to open config file for writing");
        //#endif
      }
      serializeJson(json, Serial);
      serializeJson(json, configFile);
      configFile.close();
        //end save
      
        Serial.println("Boot settings saved. Rebooting controller.");
      //#endif
  }
  SPIFFS.end();
  if (restart_ESP) {   //Needed so initial onboarding doesn't cause second reboot
    ESP.restart();
  }
}

void handleReset() {
    String resetMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>Controller Reset</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Controller Resetting...</H1><br>\
      <H3>After this process is complete, you must setup your controller again:</H3>\
      <ul>\
      <li>Connect a device to the controller's local access point: ESP_ParkingAsst</li>\
      <li>Open a browser and go to: 192.168.4.1</li>\
      <li>Enter your WiFi information and set other default settings values</li>\
      <li>Click Save. The controller will reboot and join your WiFi</li>\
      </ul><br>\
      Once the above process is complete, you can return to the main settings page by rejoining your WiFi and entering the IP address assigned by your router in a browser.<br>\
      You will need to reenter all of your settings for the system as all values will be reset to original defaults<br><br>\
      <b>This page will NOT automatically reload or refresh</b>\
      </body></html>";
    server.send(200, "text/html", resetMsg);
    delay(1000);
    SPIFFS.begin();
    SPIFFS.format();
    SPIFFS.end();
    wifiManager.resetSettings();
    delay(1000);
    ESP.restart();
}

void handleRestart() {
    String restartMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>Controller Restart</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Controller restarting...</H1><br>\
      <H3>Please wait</H3><br>\
      After the controller completes the boot process (lights will flash blue, followed by red/green for approx. 2 seconds), you may click the following link to return to the main page:<br><br>\
      <a href=\"http://";      
    restartMsg += localIP;
    restartMsg += "\">Return to settings</a><br>";
    restartMsg += "</body></html>";
    server.send(200, "text/html", restartMsg);
    delay(1000);
    ESP.restart();
}

//  =====================================
//  Home Assistant MQTT Discovery - v0.45
//  =====================================

void handleDiscovery() {
  //Main page for enabling/disabling Home Assistant MQTT Discovery
  String discMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title> TDS Monitor - MQTT Discovery</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Home Assistant MQTT Discovery</H1><br><br>\
      This feature can be used to add or remove the TDS Monitor device and MQTT entities to Home Assistant without manual YAML editing.<br><br>";  
  discMsg += "<u><b>Prerequisites and Notes</b></u>";
  discMsg += "<ul><li>It is <b><i>strongly recommended</i></b> that you read the &nbsp"; 
  discMsg += "<a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a> before using this feature.</li>";
  discMsg += "<li>You must have successfully completed the MQTT setup, rebooted and estabished a connection to your broker before these options will work.</li>";
  discMsg += "<li>The Home Assistant MQTT integration must be installed, discovery must not have been disabled nor the default discovery topic changed.</li>";
  discMsg += "<li>The action to enable/disable will occur immediately in Home Assistant without any interaction or prompts.</li>";
  discMsg += "<li>If you have already manually created the Home Assistant MQTT entities, enabling discovery will create duplicate entities with different names.</li></ul>";
  discMsg += "<H3>Enable Discovery</H3>";
  discMsg += "This will immediately create a device called <b>VAR_DEVICE_NAME</b> in your Home Assistant MQTT Integration.<br>";
  discMsg += "It will also create the following entities for this device:\
      <ul>\
      <li>Supply TDS</li>\
      <li>Pre-DI TDS</li>\
      <li>Output TDS</li>\
      <li>IP Address</li>\
      <li>MAC Address</li></ul><br>";
  discMsg += "<button id=\"btnenable\" onclick=\"location.href = './discoveryEnabled';\">Enable Discovery</button>"; 
  discMsg += "<br><hr>";
  discMsg += "<H3>Disable Discovery</H3>";
  discMsg += "This will <b>immediately</b> delete the device and all entities created MQTT Discover for this device.<br>\
      If you have any automations, scripts, dashboard entries or other processes that use these entities, you will need to delete or correct those items in Home Assistant.<br><br>";
  discMsg += "<button id=\"btndisable\" onclick=\"location.href = './discoveryDisabled';\">Disable Discovery</button><br><br>"; 
      
  discMsg += "<br><a href=\"http://";
  discMsg += localIP;
  discMsg += "\">Return to settings</a><br>";
  discMsg += "</body></html>";
  discMsg.replace("VAR_DEVICE_NAME", deviceName);
  server.send(200, "text/html", discMsg); 
}

void enableDiscovery() {
  String discMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title> TDS Monitor MQTT Discovery</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Home Assistant MQTT Discovery</H1><br>";
  if (mqttEnabled) {
    byte retVal = haDiscovery(true);
    if (retVal == 0) {

      discMsg += "<b><p style=\"color: #5a8f3d;\">MQTT Discovery topics successfully sent to Home Assistant.</p></b><br>";
      discMsg += "If the Home Assistant MQTT integration has been configured correctly, you should now find a new device, <b>VAR_DEVICE_NAME</b>, under the MQTT integration.";
      discMsg += "<p style=\"color: #1c5410;\">\
                   <ul>\
                   <li>You can change the name of the device or any entities, if desired, via Home Assistant</li>\
                   <li>To remove (permanently delete) the created device and entities, disable MQTT Discovery</li>\
                   <li>If you disable MQTT Discover and then reenable it, the device and entities will be recreated with their original names.</li>\
                   </ul></p><br>";
      discMsg += "If you do not see the new device under your Home Assistant MQTT integration, please use a utility (e.g. MQTT Explorer or similar) to see if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>";
      discMsg += "For additional troubleshooting tips, please see the <a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a><br><br>\
                  It is recommended that you disable MQTT Discovery in the Parking Assistant app until you resolve any MQTT/Home Assistant issues.<br><br>";
    
    } else if (retVal == 1) {
      //Unable to connect or reconnect to broker
      discMsg += "<b><p style=\"color: red;\">The Parking Assistant was unable to connect or reconnect to your MQTT broker!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery.<br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were created.</p><br><br>";
      
    } else {
      //Unknown error or issue
      discMsg += "<b><p style=\"color: red;\">An unknown error or issue occurred!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery and may need to manually create the Home Assistant entities.<br><br>";
      discMsg += "For additional troubleshooting tips, please see the <a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a><br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were created.</p><br><br>";
    }
  } else {
    discMsg += "<b><p style=\"color: red;\">MQTT is not enabled or was unable to connect to your broker!</p></b><br>";
    discMsg += "Before attempting to enable MQTT Discovery, please be sure you have completed the following:";
    discMsg += "<ul>\
                 <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                 <li>Checked the box to save settings as new boot defaults</li>\
                 <li>Rebooted the controller</li>\
                 </ul><br>";
    discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to see if the controller is successfully connecting to your broker.<br>\ 
                You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                Until you successfully see these topics in your broker, you will not be able to enable MQTT Discovery.<br>";
    
  }
  discMsg += "<br><a href=\"http://";
  discMsg += localIP;
  discMsg += "\">Return to settings</a><br>";
  discMsg += "</body></html>";
  discMsg.replace("VAR_DEVICE_NAME", deviceName);
  server.send(200, "text/html", discMsg); 
  
  //send baseline TDS reading
  sprintf(mqtt_tds_SUP, "%.0f",tdsValue_SUP);
  client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
  Serial.println("Supply TDS Baseline Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_PREDI, "%.0f", tdsValue_PREDI);
  client.publish(mqtt_prediTDS_stat, mqtt_tds_PREDI);
  Serial.println("Pre-DI TDS Baseline Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_OUT, "%.0f", tdsValue_OUT);
  client.publish(mqtt_outTDS_stat, mqtt_tds_OUT);
  Serial.println("Output TDS Baseline Telemetry Sent Successfully!");
  Serial.println("");
}

void disableDiscovery() {
  String discMsg = "<HTML>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title> TDS Monitor MQTT Discovery</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Home Assistant MQTT Discovery</H1><br>";
  if (mqttEnabled) {
    byte retVal = haDiscovery(false);
    if (retVal == 0) {
      discMsg += "<b><p style=\"color: #5a8f3d;\">MQTT Removal topics successfully sent to Home Assistant.</p></b><br>";
      discMsg += "The device <b>VAR_DEVICE_NAME</b> should now be deleted from Home Assistant, along with the following entities:\
        <ul>\
      <li>Supply TDS</li>\
      <li>Pre-DI TDS</li>\
      <li>Output TDS</li>\
      <li>IP Address</li>\
      <li>MAC Address</li></ul><br>";
            
    } else if (retVal == 1) {
      //Unable to connect or reconnect to broker
      discMsg += "<b><p style=\"color: red;\">The Parking Assistant was unable to connect or reconnect to your MQTT broker!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery.<br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were removed.</p><br><br>";
      
    } else {
      //Unknown error or issue
      discMsg += "<b><p style=\"color: red;\">An unknown error or issue occurred!</p></b><br>";
      discMsg += "Please be sure your broker is running and accessible on the same network and that you have completed the following:";
      discMsg += "<ul>\
                   <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                   <li>Checked the box to save settings as new boot defaults</li>\
                   <li>Rebooted the controller</li>\
                   </ul><br>";
      discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to test if the controller is successfully connecting to your broker.<br>\ 
                  You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                  Until you successfully see these topics in your broker, you will not be able to utilize MQTT Discovery and may need to manually create the Home Assistant entities.<br><br>";
      discMsg += "For additional troubleshooting tips, please see the <a href=\"https://github.com/Resinchem/ESP-Parking-Assistant/wiki/08-MQTT-and-Home-Assistant\" target=\"_blank\" rel=\"noopener noreferrer\">MQTT Documentation</a><br><br>";
      discMsg += "<p style=\"color: red;\">No Home Assistant devices or entities were removed.</p><br><br>";
      
    }
  } else {
    discMsg += "<b><p style=\"color: red;\">MQTT is not enabled or was unable to connect to your broker!</p></b><br>";
    discMsg += "Before attempting to use MQTT Discovery, please be sure you have completed the following:";
    discMsg += "<ul>\
                 <li>Entered the proper MQTT broker settings and the correct User Name and Password</li>\
                 <li>Checked the box to save settings as new boot defaults</li>\
                 <li>Rebooted the controller</li>\
                 </ul><br>";
    discMsg += "Please use a utility (e.g. MQTT Explorer or similar) to see if the controller is successfully connecting to your broker.<br>\ 
                You should see an MQTT connected message, along with the IP and MAC addresses of your controller under the topic specified on the main settings page.<br><br>\
                Until you successfully see these topics in your broker, you will not be able to enable MQTT Discovery.<br>";
    
  }
  discMsg += "<br><a href=\"http://";
  discMsg += localIP;
  discMsg += "\">Return to settings</a><br>";
  discMsg += "</body></html>";
  discMsg.replace("VAR_DEVICE_NAME", deviceName);
  server.send(200, "text/html", discMsg); 

}

// Settings submit handler - Settings results
void calibration() {

  String saveCalibration;
  
  sprintf(w_tdsValue_SUP, "%.0f", tdsValue_SUP);
  sprintf(w_tdsValue_PREDI, "%.0f", tdsValue_PREDI);
  sprintf(w_tdsValue_OUT, "%.0f", tdsValue_OUT);

  saveCalibration = server.arg("calsave");

  String calSensors = "<html>\
    </head>\
      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
      <title>TDS Monitor - Sensor Calibration</title>\
      <style>\
        body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
      </style>\
    </head>\
    <body>\
    <H1>Calibrate TDS Sensor</H1><br>\
    <H3>Current values are:</H3>";
  //these are labels  
  calSensors += "<b>TDS Sensors</b><br>";

  calSensors += "Current Supply TDS: ";
  calSensors += w_tdsValue_SUP;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "Current Pre-DI TDS: ";
  calSensors += w_tdsValue_PREDI;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "Current Output TDS: ";
  calSensors += w_tdsValue_OUT;
  calSensors += "ppm";
  calSensors += "<br>";
  calSensors += "<form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/postcalibration/\">\
      <table>\
      <tr>\
      <td><label for=\"calkValue_SUP\">Calibrated Supply TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"calkValue_SUP\" style=\"width: 50px\;\" value=\"";
  calSensors += String(kValue_SUP);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"calkValue_PREDI\">Calibrated Pre-DI TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"calkValue_PREDI\" style=\"width: 50px\;\" value=\"";
  calSensors += String(kValue_PREDI);
  calSensors += "\"> ppm<br>\
      <tr>\
      <td><label for=\"calkValue_OUT\">Calibrated Output TDS Value:</label></td>\
      <td><input type=\"number\" min=\"1\" max=\"1000\" step=\"1\" name=\"calkValue_OUT\" style=\"width: 50px\;\" value=\"";
  calSensors += String(kValue_OUT);
  calSensors += "\"> ppm<br></td></tr>\"";
  calSensors += "<br>";
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "/discovery\">Configure Home Assistant MQTT Discovery</a><br>"; 
  calSensors += "<br><a href=\"http://";
  calSensors += localIP;
  calSensors += "\">Return to settings</a><br>";
  calSensors += "</td></tr>\
        </table><br>\
        <input type=\"checkbox\" name=\"calsave\" value=\"save\">Store Calibration and Replace Defaults? (put sensor in reference solution before submitting calibration)<br><br>\
        <input type=\"submit\" value=\"Submit Calibration\">\
      </form>\
      <br>\
      <h2>Controller Commands</h2>\
      Caution: Restart and Reset are executed immediately when the button is clicked.<br>\
      <table border=\"1\" cellpadding=\"10\">\
      <tr>\
      <td><button id=\"btnrestart\" onclick=\"location.href = './restart';\">Restart</button></td><td>This will reboot controller and reload default boot values.</td>\
      </tr><tr>\
      <td><button id=\"btnreset\" style=\"background-color:#FAADB7\" onclick=\"location.href = './reset';\">RESET ALL</button></td><td><b>WARNING</b>: This will clear all settings, including WiFi! You must complete initial setup again.</td>\
      </tr><tr>\
      <td><button id=\"btnupdate\" onclick=\"location.href = './update';\">Firmware Upgrade</button></td><td>Upload and apply new firmware from local file.</td>\
      </tr></table><br>\
      Current version: VAR_CURRRENT_VER\
    </body>\
  </html>";

  calSensors.replace("VAR_DEVICE_NAME", deviceName); 
  calSensors.replace("VAR_CURRRENT_VER", VERSION);
  server.send(200, "text/html", calSensors);
  delay(1000);
  if (saveCalibration == "save") {
    updateSettings(true);
  } else {
    updateSettings(false);
  } 
}

// Settings submit handler - Settings results
void handleCalibration() {
  if (server.method() != HTTP_POST) {
    server.send(405, "text/plain", "Method Not Allowed");
  } else {
    String saveCalibration;
    
    kValue_SUP = server.arg("calkValue_SUP").toInt();
    kValue_PREDI = server.arg("calkValue_PREDI").toInt();
    kValue_OUT = server.arg("calkValue_OUT").toInt();
    
    saveCalibration = server.arg("calsave");
    
    String calform = "<html>\
      </head>\
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
        <title>TDS Monitor - Current Settings</title>\
        <style>\
          body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
        </style>\
      </head>\
      <body>\
      <H1>Settings updated!</H1><br>\
      <H3>Current values are:</H3>";
    
    calform += "<b>Calibration Settings</b><br><br>";
    if ((kValue_SUP == 0) && (kValue_PREDI == 0) && (kValue_OUT == 0 )) {
      calform += "TDS: <b><u>Not Calibrated</u></b> ";
      if (saveCalibration != "save") {
        calform += "(If you just calibrated your TDS, you must save as new boot defaults for this to take effect)<br>";
      }
    } else {
      calform += "Supply TDS Calibrated to: " + server.arg("calkValue_SUP") + "ppm" + "<br>";
      calform += "Pre-DI TDS Calibrated to: " + server.arg("calkValue_PREDI") + "ppm" + "<br>";
      calform += "Output TDS Calibrated to: " + server.arg("calkValue_OUT") + "ppm" + "<br>";
      calibrateTDSprobe();
      if (saveCalibration != "save") {
        calform += "<br>(If you just calibrated your TDS, you must save as new boot defaults for this to take effect)<br>";
        calibrateTDSprobe();
      }
    }
    calform += "<br>";
    if (saveCalibration == "save") {
      calform += "<br>";
      calform += "<b>New settings saved as boot defaults.</b> Controller will now reboot.<br>";
      calform += "You can return to the settings page after boot completes (lights will briefly turn blue then red/green to indicate completed boot).<br>"; 
      calibrateTDSprobe();   
    } else {
      //Wake up system so new setting can be seen/tested... even if car present
      carDetectedCounter = carDetectedCounterMax + 1;
    }
    calform += "<br><a href=\"http://";
    calform += localIP;
    calform += "/discovery\">Configure Home Assistant MQTT Discovery</a><br>"; 
    calform += "<br><a href=\"http://";
    calform += localIP;
    calform += "/calibration\">Calibrate TDS Sensors</a><br>"; 
    calform += "<br><a href=\"http://";
    calform += localIP;
    calform += "\">Return to settings</a><br>";
             
    calform += "</body></html>";
    server.send(200, "text/html", calform);
    delay(1000);
    if (saveCalibration == "save") {
      updateSettings(true);
    } else {
      updateSettings(false);
    } 
  }
}

void calibrateTDSprobe() {
  char *cmdReceivedBufferPtr;
  static boolean ecCalibrationFinish = 0;
  static boolean enterCalibrationFlag = 0;
  float KValueTemp_SUP,rawECsolution_SUP;

  rawECsolution_SUP = strtod(cmdReceivedBufferPtr,NULL)/(float)(TdsFactor);
  rawECsolution_SUP = rawECsolution_SUP*(1.0+0.02*(temperature-25.0));
  Serial.println("Calibrating TDS Sensors: Supply, Pre-DI, and Output......");
  Serial.print("rawECsolution:");
  Serial.print(rawECsolution_SUP);
  Serial.print("  ecvalue:");
  Serial.println(ecValue_SUP);
          
  KValueTemp_SUP = rawECsolution_SUP/(133.42*voltage_SUP*voltage_SUP*voltage_SUP - 255.86*voltage_SUP*voltage_SUP + 857.39*voltage_SUP);  //calibrate in the  buffer solution, such as 707ppm(1413us/cm)@25^c
  if((rawECsolution_SUP>0) && (rawECsolution_SUP<2000) && (KValueTemp_SUP>0.25) && (KValueTemp_SUP<4.0))
  {
  Serial.println();
  Serial.print("Calibration Successful");
  Serial.print(KValueTemp_SUP);
  calkValue_SUP =  KValueTemp_SUP;
  updateSettings(true);

  } else {
  updateSettings(false);
  Serial.print("Calibration Failed");
  }
}

// Not found or invalid page handler
void handleNotFound() {
  String message = "File Not Found or invalid command.\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  server.send(404, "text/plain", message);
}

// ===================================
//  SETUP MQTT AND CALLBACKS
// ===================================

bool setup_mqtt() {
  byte mcount = 0;
  
  IPAddress mymqttServer = IPAddress(mqttAddr_1, mqttAddr_2, mqttAddr_3, mqttAddr_4);
  
  client.setServer(mymqttServer, mqttPort);
  client.setBufferSize(512); 
  client.setCallback(callback);

  
    Serial.print("Connecting to MQTT broker.");
  //#endif
  while (!client.connected( )) {
    
      Serial.print(".");
    //#endif
    client.connect(mqttClient.c_str(), mqttUser.c_str(), mqttPW.c_str());
    if (mcount >= 60) {
      
        Serial.println();
        Serial.println("Could not connect to MQTT broker. MQTT disabled.");
      //#endif
      // Could not connect to MQTT broker
      return false;
    }
    delay(500);
    mcount++;
  }
  
    Serial.println();
    Serial.println("Successfully connected to MQTT broker.");
  //#endif
  client.subscribe(("cmnd/" + mqttTopicSub + "/#").c_str());
  client.publish(("homeassistant/stat/" + mqttTopicPub + "/mqtt").c_str(), "connected", true);
  //Publish current IP and MAC addresses
  client.publish(("homeassistant/stat/" + mqttTopicPub + "/ipaddress").c_str(), localIP.c_str(), true);
  client.publish(("homeassistant/stat/" + mqttTopicPub + "/macaddress").c_str(), strMacAddr.c_str(), true);
  mqttConnected = true;

  //send baseline TDS reading
  sprintf(mqtt_tds_SUP, "TDS-SUP: %.0f", tdsValue_SUP);
  client.publish(mqtt_pub_topic_supTDS, mqtt_tds_SUP);
  Serial.println("Supply TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_PREDI, "TDS-PRE_DI: %.0f", tdsValue_PREDI);
  client.publish(mqtt_pub_topic_prediTDS, mqtt_tds_PREDI);
  Serial.println("Pre-DI TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_OUT, "TDS-OUT: %.0f", tdsValue_OUT);
  client.publish(mqtt_pub_topic_outTDS, mqtt_tds_OUT);
  Serial.println("Output TDS Telemetry Sent Successfully!");
  Serial.println("");

  client.publish("Aquarium","TDS Monitor Connected!");
  client.subscribe("Aquarium");
  Serial.println("MQTT Connected - Connection Mesage Sent!");

  
  //send baseline TDS reading
  sprintf(mqtt_tds_SUP, "%.0f", tdsValue_SUP);
  client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
  Serial.println("Supply TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_PREDI, "%.0f", tdsValue_PREDI);
  client.publish(mqtt_prediTDS_stat, mqtt_tds_PREDI);
  Serial.println("Pre-DI TDS Telemetry Sent Successfully!");
  Serial.println("");
  sprintf(mqtt_tds_OUT, "%.0f", tdsValue_OUT);
  client.publish(mqtt_outTDS_stat, mqtt_tds_OUT);
  Serial.println("Output TDS Telemetry Sent Successfully!");
  Serial.println("");
  
  return true;
}

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  String message = (char*)payload;
  /*
   * Add any commands submitted here
   * Example:
   * if (strcmp(topic, "cmnd/matrix/mode")==0) {
   *   MyVal = message;
   *   Do something
   *   return;
   * };
   */
}

void readConfigFile() {
  if (SPIFFS.begin()) {
    
      Serial.println("mounted file system");
    //#endif
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      
        Serial.println("reading config file");
      //#endif
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        
          Serial.println("opened config file");
        //#endif
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);

        configFile.readBytes(buf.get(), size);

        DynamicJsonDocument json(1024);
        auto deserializeError = deserializeJson(json, buf.get());
        serializeJson(json, Serial);
        if ( ! deserializeError ) {
         
            Serial.println("\nparsed json");
         //#endif
          // Read values here from SPIFFS (v0.42 - add defaults for all values in case they don't exist to avoid potential boot loop)
          strcpy(mqtt_addr_1, json["mqtt_addr_1"]|"0");
          strcpy(mqtt_addr_2, json["mqtt_addr_2"]|"0");
          strcpy(mqtt_addr_3, json["mqtt_addr_3"]|"0");
          strcpy(mqtt_addr_4, json["mqtt_addr_4"]|"0");
          strcpy(mqtt_port, json["mqtt_port"]|"0");
          strcpy(mqtt_tele_period, json["mqtt_tele_period"]|"60");
          strcpy(mqtt_user, json["mqtt_user"]|"mqttuser");
          strcpy(mqtt_pw, json["mqtt_pw"]|"mqttpwd");
          strcpy(device_name, json["device_name"]|"TDSMonitor");               // Default needed if upgrading to v0.41, because value won't exist in config
          strcpy(mqtt_topic_sub, json["mqtt_topic_sub"]|"tdsmonitor");         // Default needed if upgrading to v0.41, because value won't exist in config
          strcpy(mqtt_topic_pub, json["mqtt_topic_pub"]|"tdsmonitor");         // Default needed if upgrading to v0.41, because value won't exist in config
          strcpy(calkValue_SUP, json["calkValue_SUP"]|"1.0");
          strcpy(calkValue_PREDI, json["calkValue_PREDI"]|"1.0");
          strcpy(calkValue_OUT, json["calkValue_OUT"]|"1.0");

          //Need to set wifihostname here
          deviceName = String(device_name);
        } else {
          
            Serial.println("failed to load json config");
          //#endif
        }
        configFile.close();
      }
    }
    SPIFFS.end();  
  } else {
    
      Serial.println("failed to mount FS");
    //#endif
  }
}

void setup_Wifi() {
  delay(10);
  Serial.println("Booting");
  Serial.println("Connecting to WiFi");
  WiFi.mode(WIFI_STA);
  WiFiManager wm;
  bool res;
  res = wm.autoConnect("TDS Monitor", "password");
  if(!res) {
    Serial.println("Failed to connect");
  }
  else {
    Serial.println("WiFi Connection Success!");
    client.publish(mqtt_IP_stat, localIP.c_str());
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());
  localIP = WiFi.localIP().toString();
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.print(WiFi.localIP());
  Serial.println("");
  }

}

void resetWiFiSettings(){
  //Wifi Reset Button if held for 10 seconds, reset wifi settings
  pinMode(27, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  int wifiReset = digitalRead(27);
  if (wifiReset == HIGH) {
    digitalWrite(2, LOW);
  } else {
    digitalWrite(2, HIGH);
    delay(10000);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    digitalWrite(2, HIGH);
    delay(150);
    digitalWrite(2, LOW);
    delay(150);
    Serial.println("WiFi Reset...TDS Monitor Restarting");
    delay(150);
    wm.resetSettings();
    delay(150);
    //Restarts device after wiping wifi settings
    ESP.restart();
  }
}

void wifiReconnect() {
  // Loop until we're reconnected
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print("Attempting to Re-Establish WiFi Connection...");
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("WiFi Connection Re-Established!");
      client.publish(mqtt_IP_stat, localIP.c_str());
    } else {
      Serial.print("failed, rc=");
      Serial.print(WiFi.status());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
      setup_Wifi();
    }
  }
}


// ===============================
//  LED and Display Functions
// ===============================
void blinkLEDs(CRGB color) {
  if (blinkOn) {
    fill_solid(LEDs, numLEDs, color);
  } else {
    fill_solid(LEDs, numLEDs, CRGB::Black);
  }
  blinkOn = !blinkOn;
}

void updateOutIn(int curDistance) {
   byte numberToLight = 1;
  fill_solid(LEDs, numLEDs, CRGB::Black);

  //Get number of LEDs to light up on each end, based on interval
  numberToLight = (startDistance - curDistance) / intervalDistance;
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  for (int i=0; i < numberToLight; i++) {
    LEDs[i] = ledColorActive;
    LEDs[(numLEDs-1) - i] = ledColorActive;
  }
}

void updateInOut(int curDistance) {
  byte numberToLight = 1;
  byte startLEDLeft = 0;
  byte startLEDRight = 0;
  fill_solid(LEDs, numLEDs, CRGB::Black);
  //Get number of LEDs to light up on each end, based on interval
  numberToLight = ((startDistance - curDistance) / intervalDistance);
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  //Find center LED(s) - single of odd number, two if even number of LEDS
  startLEDLeft = (numLEDs / 2);
  startLEDRight = startLEDLeft;
  if ((startLEDLeft % 2) == 0) {
    startLEDLeft --;
  }
  for (int i=0; i < numberToLight; i++) {
    LEDs[(startLEDLeft - i)] = ledColorActive;
    LEDs[(startLEDRight + i)] = ledColorActive;
  }
}

void updateFullStrip(int curDistance) {
  byte numberToLight = 1;
  fill_solid(LEDs, numLEDs, CRGB::Black);

  //Get number of LEDs to light up from start of LED strip, based on interval
  numberToLight = (startDistance - curDistance) / intervalDistance;
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  for (int i=0; i < numberToLight; i++) {
    LEDs[i] = ledColorActive;
  }
}

void updateFullStripInv(int curDistance) {
  byte numberToLight = 1;
  fill_solid(LEDs, numLEDs, CRGB::Black);

  //Get number of LEDs to light up from end of LED strip, based on interval
  numberToLight = (startDistance - curDistance) / intervalDistance;
  if (numberToLight ==0 ) numberToLight = 1;  //Assure at least 1 light if integer truncation results in 0
  for (int i=0; i < numberToLight; i++) {
    LEDs[((numLEDs - i)- 1)] = ledColorActive;
  }
}

void updateSolid(int curDistance) {
  fill_solid(LEDs, numLEDs, CRGB::Black);
  if ((curDistance > startDistance) && (curDistance <= wakeDistance)) {
    fill_solid(LEDs, numLEDs, ledColorWake); 
  } else if ((curDistance > parkDistance) && (curDistance <= startDistance)) {
    fill_solid(LEDs, numLEDs, ledColorActive);
  } else if ((curDistance > backupDistance) && (curDistance <= parkDistance)) {
    fill_solid(LEDs, numLEDs, ledColorParked);
  }
}

void updateSleepMode() {
  fill_solid(LEDs, numLEDs, CRGB::Black);
  FastLED.setBrightness(sleepBrightness);
  if (showStandbyLEDs) {
    LEDs[0] = ledColorStandby;
    LEDs[numLEDs - 1] = ledColorStandby;
  }
}

void updateOTA() {
  fill_solid(LEDs, numLEDs, CRGB::Black);
  //Alternate LED colors using red and green
  FastLED.setBrightness(activeBrightness);
  for (int i=0; i < (numLEDs-1); i = i + 2) {
    LEDs[i] = CRGB::Red;
    LEDs[i+1] = CRGB::Green;
  }
  FastLED.show();
}

// ==============================
//  MQTT Functions and Procedures
// ==============================

void createDiscoveryUniqueID() {
  //Generate UniqueID from uidPrefix + last 6 chars of device MAC address
  //This should insure that even multiple devices installed in same HA instance are unique
  
  strcpy(devUniqueID, uidPrefix);
  int preSizeBytes = sizeof(uidPrefix);
  int preSizeElements = (sizeof(uidPrefix) / sizeof(uidPrefix[0]));
  //Now add last 6 chars from MAC address (these are 'reversed' in the array)
  int j = 0;
  //for (int i = 2; i >= 0; i--) {
  for (int i = 3; i < 6; i++) {
    sprintf(&devUniqueID[(preSizeBytes - 1) + (j)], "%02X", macAddr[i]);   //preSizeBytes indicates num of bytes in prefix - null terminator, plus 2 (j) bytes for each hex segment of MAC
    j = j + 2;
  }
  //devUniqueID would now contain something like "TDSMonitor02BE4F" which can be used for topics/payloads
  // End result is a unique ID for this device (e.g. rctdevE350CA) 
  Serial.print("Unique ID: ");
  Serial.println(devUniqueID);  
}

byte haDiscovery (bool enable) {
  char topic[128];
  //if (auto_discovery) {
    char buffer1[512];
    char buffer2[512];
    char buffer3[512];
    char buffer4[512];
    char buffer5[512];
    char uid[128];
    if (mqttEnabled) {
      createDiscoveryUniqueID();
      if (enable) {    
        //Add device and entities
        if (!client.connected()) { 
          if (!mqttReconnect_soft()) {
            return 1;
          }
        }
      //Should have valid MQTT Connection at this point
      DynamicJsonDocument doc(2048);

      Serial.println("Discovering new devices...");

      Serial.println("Adding TDS Sensor...Supply TDS");

      //Create supTDS Sensor with full device details
      //topic
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "supTDS/config");
      //Create unique_id based on devUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "supTDS");
      //JSON payload
      doc.clear();
      doc["name"] = "Supply TDS";
      doc["obj_id"] = "mqtt_TDSmonitor_supTDS";
      doc["dev_cla"] = "volatile_organic_compounds_parts";
      doc["uniq_id"] = uid;
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/supTDS";
      doc["unit_of_meas"] = "ppm";
      doc["ic"] = "mdi:water-pump";
      JsonObject devicesupTDS = doc.createNestedObject("device");
        devicesupTDS["ids"] = "tdsmonitor";
        devicesupTDS["name"] = "TDS Monitor";
        devicesupTDS["mf"] = "DeezNutzInc";
        devicesupTDS["mdl"] = "TDS Monitor Elite";
        devicesupTDS["sw"] = "1.89";
        devicesupTDS["hw"] = "1.0";
        devicesupTDS["cu"] = "http://" + localIP + "/config";  //web interface for device, with discovery toggle
      serializeJson(doc, buffer1);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer1, true);

      //Create prediTDS Sensor
      Serial.println("Adding TDS Sensor...Pre-DI TDS");

      //Create unique Topic based on devUniqueID
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "prediTDS/config");
      //Create unique_id based on decUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "prediTDS");
      //Create JSON payload per HA documentation
      doc.clear();
      doc["name"] = "Pre-DI TDS";
      doc["obj_id"] = "mqtt_TDSmonitor_prediTDS";
      doc["dev_cla"] = "volatile_organic_compounds_parts";
      doc["uniq_id"] = uid;
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/prediTDS";
      doc["unit_of_meas"] = "ppm";
      doc["ic"] = "mdi:toilet";
      JsonObject deviceprediTDS = doc.createNestedObject("device");
      deviceprediTDS["ids"] = "tdsmonitor";
      deviceprediTDS["name"] = "TDS Monitor";
      serializeJson(doc, buffer2);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer2, true);

      //outTDS Sensor
      Serial.println("Adding TDS Sensor...Output TDS");
      //Create unique Topic based on devUniqueID
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "outTDS/config");
      //Create unique_id based on decUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "outTDS");
      //Create JSON payload per HA documentation
      doc.clear();
      doc["name"] = "Output TDS";
      doc["obj_id"] = "mqtt_TDSmonitor_outTDS";
      doc["dev_cla"] = "volatile_organic_compounds_parts";
      doc["uniq_id"] = uid;
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/outTDS";
      doc["unit_of_meas"] = "ppm";
      doc["ic"] = "mdi:water-check";
      JsonObject deviceoutTDS = doc.createNestedObject("device");
      deviceoutTDS["ids"] = "tdsmonitor";
      deviceoutTDS["name"] = "TDS Monitor";
      serializeJson(doc, buffer3);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer3, true);

      //IP Address Diagnostic Sensor
      Serial.println("Adding IP Diagnostic Sensor...");
      //Create unique Topic based on devUniqueID
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "IP/config");
      //Create unique_id based on decUniqueID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "IP");
      //Create JSON payload per HA documentation
      doc.clear();
      doc["name"] = "IP Address";
      doc["uniq_id"] = uid;
      doc["ent_cat"] = "diagnostic";
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/ipaddress";
      JsonObject deviceIP = doc.createNestedObject("device");
      deviceIP = doc.createNestedObject("device");
      deviceIP["ids"] = "tdsmonitor";
      deviceIP["name"] = "TDS Monitor";
      serializeJson(doc, buffer4);
      //Publish discovery topic and payload (with retained flag)
      client.publish(topic, buffer4, true);

      Serial.println("All Devices Added!");


      //Create MAC Address as Diagnostic Sensor
      //topic
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "MAC/config");
      //Unique ID
      strcpy(uid, "TDSMonitor");
      strcat(uid, "MAC");
      //JSON Payload
      doc.clear();
      doc["name"] = deviceName + " MAC Address";
      doc["uniq_id"] = uid;
      doc["ent_cat"] = "diagnostic";
      doc["stat_t"] = "homeassistant/stat/TDSMonitor/macaddress";
      JsonObject deviceMAC = doc.createNestedObject("device");
      deviceMAC["ids"] = "tdsmonitor";
      deviceMAC["name"] = "TDS Monitor";
      serializeJson(doc, buffer5);
      client.publish(topic, buffer5, true);
      return 0;

    } else {

      //Remove all entities by publishing empty payloads
      if (!client.connected()) { 
        if (!mqttReconnect_soft()) {
          return 1;
        }
      }
      //Must use original topic, so recreate from original Unique ID
      //This will immediately remove/delete the device/entities from HA
      Serial.println("Removing discovered devices...");

      //supTDS Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "supTDS/config");
      client.publish(topic, "");

      //prediTDS Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "prediTDS/config");
      client.publish(topic, "");

      //outTDS Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "outTDS/config");
      client.publish(topic, "");

      //IP Diagnostics Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "IP/config");
      client.publish(topic, "");

      //MAC Address Sensor
      strcpy(topic, "homeassistant/sensor/");
      strcat(topic, "TDSMonitor");
      strcat(topic, "MAC/config");
      client.publish(topic, "");



      Serial.println("All Devices Removed...");

      return 0;
    }
  } else {
    //MQTT not enabled... should never hit this as it is checked before calling  
    return 90;
  }
  //catch all
  return 99;
}

void mqttReconnect() {
  int retries = 0;
  // Loop until we're reconnected
  while (!client.connected()) {
    if(retries <150)
    {
      
      Serial.print("Attempting to Re-Establish MQTT Connection...");
      //#endif
      if (client.connect(mqttClient.c_str(), mqttUser.c_str(), mqttPW.c_str())) 
      {
        
          Serial.println("TDS Monitor Re-connected After Failure!");
        //#endif
        // ... and resubscribe
        client.subscribe("Aquarium/#");
      } 
      else 
      {
        
          Serial.print("failed, rc=");
          Serial.print(client.state());
          Serial.println(" try again in 5 seconds");
        //#endif
        retries++;
        // Wait 5 seconds before retrying
        delay(5000);
      }
    }
    if ((retries > 149) && (mqttEnabled))
    {
    ESP.restart();
    }
  }
}

bool mqttReconnect_soft() {
  //Attempt MQTT reconnect.  If fails, return false instead of forcing ESP Reboot
  int retries = 0;
  while (!client.connected()) {
    if(retries < 10)
    {
      
        Serial.print("Attempting to Re-Establish MQTT Connection...");
      //#endif
      if (client.connect(mqttClient.c_str(), mqttUser.c_str(), mqttPW.c_str())) 
      {
        
          Serial.println("TDS Monitor Re-connected After Failure!");
        //#endif
        // ... and resubscribe
        client.subscribe("Aquarium/#");
        return true;
      } 
      else 
      {
        
          Serial.print("failed, rc=");
          Serial.print(client.state());
          Serial.println(" try again in 5 seconds");
        //#endif
        retries++;
        // Wait 3 seconds before retrying
        delay(3000);
        yield();
      }
    } 
    if ((retries > 9) && (mqttEnabled)) 
    {
       return false;
    }
  }
}

void setup_TDS(){
  //Supply TDS Setup
  pinMode(TDS_SUP_Pin,INPUT);
  pinMode(TDS_PREDI_Pin,INPUT);
  pinMode(TDS_OUT_Pin,INPUT);
  readKValues();  
}

void readKValues(){ 
  kValue_SUP = 1.0;   // default value: K = 1.0
  kValue_PREDI = 1.0;   // default value: K = 1.0
  kValue_OUT = 1.0;   // default value: K = 1.0
}

void update_TDS(){ 

	analogValue_SUP = analogRead(TDS_SUP_Pin);
	voltage_SUP = analogValue_SUP/tdsAdcRange*tdsAref;
	ecValue_SUP=(133.42*voltage_SUP*voltage_SUP*voltage_SUP - 255.86*voltage_SUP*voltage_SUP + 857.39*voltage_SUP)*kValue_SUP;
	ecValue25_SUP  =  ecValue_SUP / (1.0+0.02*(temperature-25.0));  //temperature compensation
	tdsValue_SUP = ecValue25_SUP * TdsFactor;

	analogValue_PREDI = analogRead(TDS_PREDI_Pin);
	voltage_PREDI = analogValue_PREDI/tdsAdcRange*tdsAref;
	ecValue_PREDI=(133.42*voltage_PREDI*voltage_PREDI*voltage_PREDI - 255.86*voltage_PREDI*voltage_PREDI + 857.39*voltage_PREDI)*kValue_PREDI;
	ecValue25_PREDI  =  ecValue_PREDI / (1.0+0.02*(temperature-25.0));  //temperature compensation
	tdsValue_PREDI = ecValue25_PREDI * TdsFactor;

	analogValue_OUT = analogRead(TDS_OUT_Pin);
	voltage_OUT = analogValue_OUT/tdsAdcRange*tdsAref;
	ecValue_OUT=(133.42*voltage_OUT*voltage_OUT*voltage_OUT - 255.86*voltage_OUT*voltage_OUT + 857.39*voltage_OUT)*kValue_OUT;
	ecValue25_OUT  =  ecValue_OUT / (1.0+0.02*(temperature-25.0));  //temperature compensation
	tdsValue_OUT = ecValue25_OUT * TdsFactor;

}        

void calculate_TDS(){
  temperature = tdstemp;
  update_TDS();
    if(tdsValue_SUP == prevtdsValue_SUP) {
    Serial.print("Supply TDS reading hasnt changed (Current/Last Read): ");
    Serial.print(tdsValue_SUP, 0); Serial.print("ppm");
    Serial.print(" : ");
    Serial.print(prevtdsValue_SUP); Serial.println("ppm");
    delay(100);
  } else {
    prevtdsValue_SUP = tdsValue_SUP;
    Serial.print("SUP TDS: "); Serial.print(tdsValue_SUP, 0); Serial.println("ppm");
   
    sprintf(mqtt_tds_SUP, "%.0f", tdsValue_SUP);
 	  client.publish(mqtt_supTDS_stat, mqtt_tds_SUP);
   
    sprintf(mqtt_tds_SUP2, "TDS-SUP: %.0f", tdsValue_SUP);
    client.publish(mqtt_pub_topic_supTDS, mqtt_tds_SUP2);

    Serial.println("Supply TDS Telemetry Sent Successfully!");
    delay(100);
  }

  if(tdsValue_PREDI == prevtdsValue_PREDI) {
    Serial.print("Pre-DI TDS reading hasnt changed (Current/Last Read): ");
    Serial.print(tdsValue_PREDI, 0); Serial.print("ppm");
    Serial.print(" : ");
    Serial.print(prevtdsValue_PREDI); Serial.println("ppm");
    delay(100);
  } else {
    prevtdsValue_PREDI = tdsValue_PREDI;
    Serial.print("PRE-DI TDS: "); Serial.print(tdsValue_PREDI, 0); Serial.println("ppm");
    
    sprintf(mqtt_tds_PREDI, "%.0f", tdsValue_PREDI);
  	client.publish(mqtt_prediTDS_stat, mqtt_tds_PREDI);
    
    sprintf(mqtt_tds_PREDI2, "TDS-PRE_DI: %.0f", tdsValue_PREDI);
    client.publish(mqtt_pub_topic_prediTDS, mqtt_tds_PREDI2);
    
    Serial.println("Pre-DI TDS Telemetry Sent Successfully!");
    delay(100);
  }
    if(tdsValue_OUT == prevtdsValue_OUT) {
    Serial.print("Output TDS reading hasnt changed (Current/Last Read): ");
    Serial.print(tdsValue_OUT, 0); Serial.print("ppm");
    Serial.print(" : ");
    Serial.print(prevtdsValue_OUT); Serial.println("ppm");
    delay(100);
  } else {
    prevtdsValue_OUT = tdsValue_OUT;
    Serial.print("OUT TDS: "); Serial.print(tdsValue_OUT, 0); Serial.println("ppm");
    
    sprintf(mqtt_tds_OUT, "%.0f", tdsValue_OUT);
  	client.publish(mqtt_outTDS_stat, mqtt_tds_OUT);
    
    sprintf(mqtt_tds_OUT2, "TDS-OUT: %.0f", tdsValue_OUT);
    client.publish(mqtt_pub_topic_outTDS, mqtt_tds_OUT2);
    
    Serial.println("Output TDS Telemetry Sent Successfully!");
    delay(100);
  }

  delay(3000);
}

void setup() {

  // Serial monitor
  
    Serial.begin(115200);
    Serial.println("Booting...");
  //#endif

  //RGB_LED_Setup();

  // -------------
  // SETUP FASTLED  
  // -------------
  FastLED.addLeds<WS2812B, LED_DATA_PIN, GRB>(LEDs, NUM_LEDS_MAX);
  FastLED.setDither(false);
  FastLED.setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(activeBrightness);

  setup_Wifi();
  //mqttConnect();
  client.setCallback(callback);
  resetWiFiSettings();
  setup_TDS();
  MDNS.begin(host);
  //Get MAC address when joining wifi and place into char array
  WiFi.macAddress(macAddr);
  //Call routing (or embed here) to create initial Unique ID
  createDiscoveryUniqueID();
  //Handle web callbacks for enabling or disabling discovery (using this method is just one of many ways to do this)

  // Default Wifi to station mode - fixes issue #16
  // Local AP will be handed by WifiManager for onboarding
  WiFi.mode(WIFI_STA);
  // -----------------------------------------
  //  Captive Portal and Wifi Onboarding Setup
  // -----------------------------------------
  //clean FS, for testing - uncomment next line ONLY if you wish to wipe current FS
  //SPIFFS.format();
  // *******************************
  // read configuration from FS json
  // *******************************
  
    Serial.println("mounting FS...");
  //#endif
  readConfigFile();

  // The extra parameters to be configured (can be either global or just in the setup)
  // After connecting, parameter.getValue() will get you the configured value
  // id/name placeholder/prompt default length
  
  WiFiManagerParameter custom_text("<p>Each parking assistant must have a unique name. Letters and numbers only, no spaces, 16 characters max. See the wiki documentation for more info.</p>");
  WiFiManagerParameter custom_dev_id("devName", "Unique Device Name", "parkasst", 16, " 16 chars/alphanumeric only");

  //set config save notify callback
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  //set static ip
  //wifiManager.setSTAStaticIPConfig(IPAddress(10, 0, 1, 99), IPAddress(10, 0, 1, 1), IPAddress(255, 255, 255, 0));

  //add all your parameters here
  wifiManager.addParameter(&custom_text);
  wifiManager.addParameter(&custom_dev_id);

  //reset settings - for testing
  //wifiManager.resetSettings();

  //sets timeout until configuration portal gets turned off
  //useful to make it all retry or go to sleep
  //in seconds
  wifiManager.setTimeout(360);

  //fetches ssid and pass and tries to connect
  //if it does not connect it starts an access point with the specified name "ESP_ParkingAsst"
  //If not supplied, will use ESP + last 7 digits of MAC
  //and goes into a blocking loop awaiting configuration. If a password
  //is desired for the AP, add it after the AP name (e.g. autoConnect("MyApName", "12345678")
  if (!wifiManager.autoConnect("TDS Monitor")) {  //
    
      Serial.println("failed to connect and hit timeout");
    //#endif
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
    ESP.restart();
    delay(5000);
  }


  //Get custom device name and set wifi and OTA host name, mqttclient and 
  if (shouldSaveConfig) {
    strcpy(device_name, custom_dev_id.getValue());
  }
  deviceName = String(device_name);
  //Use device name to define host names and mqttclient name
  wifiHostName = deviceName;
  otaHostName = deviceName + "OTA";
  mqttClient = deviceName;

  //if you get here you have connected to the WiFi
  WiFi.hostname(wifiHostName.c_str());
  
    Serial.println("connected to your wifi...yay!");
  //#endif
  

  //If callback was excuted, flag was set to save parameters
  if (shouldSaveConfig) {
    updateBootSettings(false);  //don't reboot
    delay(1000);
    readConfigFile();  //Load settings
  }

  
    Serial.println("local ip");
    Serial.println(WiFi.localIP());
  //#endif
  localIP = WiFi.localIP().toString();
  WiFi.macAddress(macAddr);         //Array for Unique ID gen
  strMacAddr = WiFi.macAddress();   //String for MQTT
  // ----------------------------


  mqttAddr_1 = (String(mqtt_addr_1)).toInt();
  mqttAddr_2 = (String(mqtt_addr_2)).toInt();
  mqttAddr_3 = (String(mqtt_addr_3)).toInt();
  mqttAddr_4 = (String(mqtt_addr_4)).toInt();

  kValue_SUP = (String(calkValue_SUP)).toFloat();
  kValue_PREDI = (String(calkValue_PREDI)).toFloat();
  kValue_OUT = (String(calkValue_OUT)).toFloat();


  //Disable MQTT if IP = 0.0.0.0
  if ((mqttAddr_1 == 0) && (mqttAddr_2 == 0) && (mqttAddr_3 == 0) && (mqttAddr_4 == 0)) {
    mqttPort = 0;
    mqttEnabled = false;         
    mqttConnected = false;       
    
  } else {
    mqttPort = (String(mqtt_port)).toInt();
    mqttTelePeriod = (String(mqtt_tele_period)).toInt();
    mqttUser = String(mqtt_user);
    mqttPW = String(mqtt_pw);
    mqttTopicSub = String(mqtt_topic_sub);
    mqttTopicPub = String(mqtt_topic_pub);
    mqttEnabled = true;
  }

  //------------------------------
  // Setup handlers for web calls
  //------------------------------
  server.on("/", handleRoot);

  server.on("/postform/", handleForm);

  server.onNotFound(handleNotFound);

  server.on("/update", handleUpdate);

  server.on("/restart", handleRestart);

  server.on("/reset", handleReset);

  server.on("/discovery", handleDiscovery);

  server.on("/discoveryEnabled", enableDiscovery);

  server.on("/discoveryDisabled", disableDiscovery);

  server.on("/calibration", calibration);

  server.on("/postcalibration/", handleCalibration);

  server.on("/otaupdate",[]() {
    //Called directly from browser address (//ip_address/otaupdate) to put controller in ota mode for uploadling from Arduino IDE
    server.send(200, "text/html", "<h1>Ready for upload...<h1><h3>Start upload from IDE now</h3>");
    ota_flag = true;
    ota_time = ota_time_window;
    ota_time_elapsed = 0;
  });

  //Firmware Update Handler
  httpUpdater.setup(&server, "/update2");
  httpUpdater.setup(&server);
  server.begin();
  
    Serial.println("Setup complete - starting main loop");
  //#endif

  //PROBABLY DONT NEED BUT KEEP TIL WORKING
  server.on("/discovery_on",[]() {
    server.send(200, "text/html", "<h1>Discovery ON...<h1><h3>Home Assistant MQTT Discovery enabled</h3>");
    delay(200);
    auto_discovery = true;
    byte retVal = haDiscovery(true);
  });
  server.on("/discovery_off",[]() {
    server.send(200, "text/html", "<h1>Discovery OFF...<h1><h3>Home Assistant MQTT Discovery disabled. Previous entities removed.</h3>");
    delay(200);
    auto_discovery = false;
    byte retVal = haDiscovery(false);
  });

  // =====================
  //  MQTT Setup
  // =====================

  if (mqttEnabled) {
    //Attempt to connect to MQTT broker - if fails, disable MQTT
    if (!setup_mqtt()) {
      mqttEnabled = false;
    }
  }

  //-----------------------------
  // Setup OTA Updates
  //-----------------------------
  ArduinoOTA.setHostname(otaHostName.c_str());
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_FS
      type = "filesystem";
    }
    // NOTE: if updating FS this would be the place to unmount FS using FS.end()
  });
  ArduinoOTA.begin();

}
 
void loop() {

  if (WiFi.status() != WL_CONNECTED) {
    wifiReconnect();
  }


  //RGB_LED();
  calculate_TDS();
  resetWiFiSettings();
  server.handleClient();

//Handle OTA updates when OTA flag set via HTML call to http://ip_address/otaupdate
  if (ota_flag) {
    updateOTA();  //Show update on LED strip
    uint32_t ota_time_start = millis();
    while (ota_time_elapsed < ota_time) {
      ArduinoOTA.handle();  
      ota_time_elapsed = millis()-ota_time_start;   
      delay(10); 
    }
    ota_flag = false;
    updateSleepMode();
  }

  uint32_t currentMillis = millis();
  int16_t distance = 0;

/*
******** UPDATE WITH SOMETHING LIKE IF STATE CHANGE FOR TDS

  //v0.44 - force MQTT update if car state changes
  if (carDetected != prevCarStatus) {
    prevCarStatus = carDetected;
    forceMQTTUpdate = true;
  }

*/
 
  //Put system to sleep if parking or exit time elapsed 
  uint32_t elapsedTime = currentMillis - startTime;
  if (((elapsedTime > (maxOperationTimePark * 1000)) && (parkSleepTimerStarted)) || ((elapsedTime > (maxOperationTimeExit * 1000)) && (exitSleepTimerStarted  ))) {
    updateSleepMode();
    isAwake = false;
    startTime = currentMillis;
    exitSleepTimerStarted = false;
    parkSleepTimerStarted = false;
  }

  //Handle MQTT calls (if enabled)
  if (mqttEnabled) {
  #if defined(MQTTMODE) && (MQTTMODE == 1 && (WIFIMODE == 1 || WIFIMODE == 2))
    if (!client.connected()) 
    {
      mqttReconnect();
    }
    client.loop();
  #endif
    
  }

  //Update MQTT Stats per tele period
  if (mqttEnabled) {
    if (((currentMillis - mqttLastUpdate) > (mqttTelePeriod * 1000)) || (forceMQTTUpdate)) {
      mqttLastUpdate = currentMillis;
      forceMQTTUpdate = false;
      if (!client.connected()) {
        mqttReconnect();
      }
    
    //Keep to use with own criteria
    /*
      // Publish MQTT values
      char outMsg[6];
      byte carStatus = 0;
      float measureDistance = 0;
      if (carDetected) carStatus = 1;

      sprintf(outMsg, "%1u",carStatus);
      client.publish(("homeassistant/stat/" + mqttTopicPub + "/cardetected").c_str(), outMsg, true);
      sprintf(outMsg, "%2.1f", measureDistance);
      client.publish(("homeassistant/stat/" + mqttTopicPub + "/parkdistance").c_str(), outMsg, true);
    
    */    
    }
  }
  //Show/Refresh LED Strip
  FastLED.show();

  delay(200);
}


Would be keen to see more info on this AJAX, ill do some research

OK so you defined calkValue_SUP as an array of 4 characters

and KValueTemp_SUP indeed as a float

what would you expect the assignment to do?

if you want the textual representation of the float value you can use snprintf() for example but 4 bytes will only save 3 characters and a trailing null char (used to denote the end of the text in the buffer).

Essentially my intentions are:

Text Input Box on WebPage,

Desired amount entered, is then used in the calculation under “calibrationTDSprobe”

Value generated from this calculation is then stored in the config file

Hope this makes sense

my view is that you should keep everything in numeric form as much as possible and when (really only when) you need to generate an ASCII representation then perform the transformation and use it and discard it. The reference should always be the numeric value (or the other way around, but don't keep both).

I would recommend snprintf() for building a buffer from a float value but if you really want to use the String class, then use it

float value = 123.4567;
String value_str = String(value, 3); // rounded at 3 digits after decimal point

regarding your JSON handling, If you get some data (from a web form), ArduinoJson knows how to decode the string into the format you want

  • if your JSON has a numeric value like "{\"value\": 3.1415926}" you could do
    float pi = doc["value"].as<float>();

  • if it's represented as a text in the JSON like "{\"value\": \"3.14\"}" then you need to do first a String extraction and then parse
    float pi = doc["value"].as<String>().toFloat();

(my recommendation to use the ESPAsyncWebServer stands ➜ handle all the web communication and leverage as much as possible static HTML in which you inject the data through AJAX. It would make building your forms much easier as you could serve those from SPIFFS and would only need to honour a request for the data only that will appear in the browser (without refreshing the page so it's nice).)

Ok, I am watching a tutorial on AJAX, which answers one of my questions around how slow the webpages can be sometimes, and especially for refreshing the TDS values so often, I will report back once I have something working.