/*
  settings.ino - user settings for Tasmota

  Copyright (C) 2020  Theo Arends

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef DOMOTICZ_UPDATE_TIMER
#define DOMOTICZ_UPDATE_TIMER       0          // [DomoticzUpdateTimer] Send relay status (0 = disable, 1 - 3600 seconds) (Optional)
#endif

#ifndef EMULATION
#define EMULATION                   EMUL_NONE  // [Emulation] Select Belkin WeMo (single relay/light) or Hue Bridge emulation (multi relay/light) (EMUL_NONE, EMUL_WEMO or EMUL_HUE)
#endif

#ifndef MTX_ADDRESS1                           // Add Display Support for up to eigth Matrices
#define MTX_ADDRESS1                0
#endif
#ifndef MTX_ADDRESS2
#define MTX_ADDRESS2                0
#endif
#ifndef MTX_ADDRESS3
#define MTX_ADDRESS3                0
#endif
#ifndef MTX_ADDRESS4
#define MTX_ADDRESS4                0
#endif
#ifndef MTX_ADDRESS5
#define MTX_ADDRESS5                0
#endif
#ifndef MTX_ADDRESS6
#define MTX_ADDRESS6                0
#endif
#ifndef MTX_ADDRESS7
#define MTX_ADDRESS7                0
#endif
#ifndef MTX_ADDRESS8
#define MTX_ADDRESS8                0
#endif

#ifndef HOME_ASSISTANT_DISCOVERY_ENABLE
#define HOME_ASSISTANT_DISCOVERY_ENABLE 0
#endif

#ifndef LATITUDE
#define LATITUDE                    48.858360  // [Latitude] Your location to be used with sunrise and sunset
#endif
#ifndef LONGITUDE
#define LONGITUDE                   2.294442   // [Longitude] Your location to be used with sunrise and sunset
#endif

#ifndef WORKING_PERIOD
#define WORKING_PERIOD              5          // Working period of the SDS Sensor, Takes a reading every X Minutes
#endif

#ifndef COLOR_TEXT
#define COLOR_TEXT                  "#000"     // Global text color - Black
#endif
#ifndef COLOR_BACKGROUND
#define COLOR_BACKGROUND            "#fff"     // Global background color - White
#endif
#ifndef COLOR_FORM
#define COLOR_FORM                  "#f2f2f2"  // Form background color - Greyish
#endif
#ifndef COLOR_INPUT_TEXT
#define COLOR_INPUT_TEXT            "#000"     // Input text color - Black
#endif
#ifndef COLOR_INPUT
#define COLOR_INPUT                 "#fff"     // Input background color - White
#endif
#ifndef COLOR_CONSOLE_TEXT
#define COLOR_CONSOLE_TEXT          "#000"     // Console text color - Black
#endif
#ifndef COLOR_CONSOLE
#define COLOR_CONSOLE               "#fff"     // Console background color - White
#endif
#ifndef COLOR_TEXT_WARNING
#define COLOR_TEXT_WARNING          "#f00"     // Warning text color - Red
#endif
#ifndef COLOR_TEXT_SUCCESS
#define COLOR_TEXT_SUCCESS          "#008000"  // Success text color - Green
#endif
#ifndef COLOR_BUTTON_TEXT
#define COLOR_BUTTON_TEXT           "#fff"     // Button text color - White
#endif
#ifndef COLOR_BUTTON
#define COLOR_BUTTON                "#1fa3ec"  // Button color - Blueish
#endif
#ifndef COLOR_BUTTON_HOVER
#define COLOR_BUTTON_HOVER          "#0e70a4"  // Button color when hovered over - Darker blueish
#endif
#ifndef COLOR_BUTTON_RESET
#define COLOR_BUTTON_RESET          "#d43535"  // Restart/Reset/Delete button color - Redish
#endif
#ifndef COLOR_BUTTON_RESET_HOVER
#define COLOR_BUTTON_RESET_HOVER    "#931f1f"  // Restart/Reset/Delete button color when hovered over - Darker redish
#endif
#ifndef COLOR_BUTTON_SAVE
#define COLOR_BUTTON_SAVE           "#47c266"  // Save button color - Greenish
#endif
#ifndef COLOR_BUTTON_SAVE_HOVER
#define COLOR_BUTTON_SAVE_HOVER     "#5aaf6f"  // Save button color when hovered over - Darker greenish
#endif
#ifndef COLOR_TIMER_TAB_TEXT
#define COLOR_TIMER_TAB_TEXT        "#fff"     // Config timer tab text color - White
#endif
#ifndef COLOR_TIMER_TAB_BACKGROUND
#define COLOR_TIMER_TAB_BACKGROUND  "#999"     // Config timer tab background color - Light grey
#endif
#ifndef COLOR_TITLE_TEXT
#define COLOR_TITLE_TEXT			      COLOR_TEXT // Title text color defaults to global text color either dark or light
#endif
#ifndef IR_RCV_MIN_UNKNOWN_SIZE
#define IR_RCV_MIN_UNKNOWN_SIZE     6          // Set the smallest sized "UNKNOWN" message packets we actually care about (default 6, max 255)
#endif
#ifndef ENERGY_OVERTEMP
#define ENERGY_OVERTEMP             90         // Overtemp in Celsius
#endif
#ifndef DEFAULT_DIMMER_MAX
#define DEFAULT_DIMMER_MAX          100
#endif
#ifndef DEFAULT_DIMMER_MIN
#define DEFAULT_DIMMER_MIN          0
#endif
#ifndef DEFAULT_LIGHT_DIMMER
#define DEFAULT_LIGHT_DIMMER        10
#endif
#ifndef DEFAULT_LIGHT_COMPONENT
#define DEFAULT_LIGHT_COMPONENT     255
#endif
#ifndef CORS_ENABLED_ALL
#define CORS_ENABLED_ALL            "*"
#endif


enum WebColors {
  COL_TEXT, COL_BACKGROUND, COL_FORM,
  COL_INPUT_TEXT, COL_INPUT, COL_CONSOLE_TEXT, COL_CONSOLE,
  COL_TEXT_WARNING, COL_TEXT_SUCCESS,
  COL_BUTTON_TEXT, COL_BUTTON, COL_BUTTON_HOVER, COL_BUTTON_RESET, COL_BUTTON_RESET_HOVER, COL_BUTTON_SAVE, COL_BUTTON_SAVE_HOVER,
  COL_TIMER_TAB_TEXT, COL_TIMER_TAB_BACKGROUND, COL_TITLE,
  COL_LAST };

const char kWebColors[] PROGMEM =
  COLOR_TEXT "|" COLOR_BACKGROUND "|" COLOR_FORM "|"
  COLOR_INPUT_TEXT "|" COLOR_INPUT "|" COLOR_CONSOLE_TEXT "|" COLOR_CONSOLE "|"
  COLOR_TEXT_WARNING "|" COLOR_TEXT_SUCCESS "|"
  COLOR_BUTTON_TEXT "|" COLOR_BUTTON "|" COLOR_BUTTON_HOVER "|" COLOR_BUTTON_RESET "|" COLOR_BUTTON_RESET_HOVER "|" COLOR_BUTTON_SAVE "|" COLOR_BUTTON_SAVE_HOVER "|"
  COLOR_TIMER_TAB_TEXT "|" COLOR_TIMER_TAB_BACKGROUND "|" COLOR_TITLE_TEXT;

enum TasmotaSerialConfig {
  TS_SERIAL_5N1, TS_SERIAL_6N1, TS_SERIAL_7N1, TS_SERIAL_8N1,
  TS_SERIAL_5N2, TS_SERIAL_6N2, TS_SERIAL_7N2, TS_SERIAL_8N2,
  TS_SERIAL_5E1, TS_SERIAL_6E1, TS_SERIAL_7E1, TS_SERIAL_8E1,
  TS_SERIAL_5E2, TS_SERIAL_6E2, TS_SERIAL_7E2, TS_SERIAL_8E2,
  TS_SERIAL_5O1, TS_SERIAL_6O1, TS_SERIAL_7O1, TS_SERIAL_8O1,
  TS_SERIAL_5O2, TS_SERIAL_6O2, TS_SERIAL_7O2, TS_SERIAL_8O2 };

const uint8_t kTasmotaSerialConfig[] PROGMEM = {
  SERIAL_5N1, SERIAL_6N1, SERIAL_7N1, SERIAL_8N1,
  SERIAL_5N2, SERIAL_6N2, SERIAL_7N2, SERIAL_8N2,
  SERIAL_5E1, SERIAL_6E1, SERIAL_7E1, SERIAL_8E1,
  SERIAL_5E2, SERIAL_6E2, SERIAL_7E2, SERIAL_8E2,
  SERIAL_5O1, SERIAL_6O1, SERIAL_7O1, SERIAL_8O1,
  SERIAL_5O2, SERIAL_6O2, SERIAL_7O2, SERIAL_8O2
};

/*********************************************************************************************\
 * RTC memory
\*********************************************************************************************/

const uint16_t RTC_MEM_VALID = 0xA55A;

uint32_t rtc_settings_crc = 0;

uint32_t GetRtcSettingsCrc(void)
{
  uint32_t crc = 0;
  uint8_t *bytes = (uint8_t*)&RtcSettings;

  for (uint32_t i = 0; i < sizeof(RTCMEM); i++) {
    crc += bytes[i]*(i+1);
  }
  return crc;
}

void RtcSettingsSave(void)
{
  if (GetRtcSettingsCrc() != rtc_settings_crc) {
    RtcSettings.valid = RTC_MEM_VALID;
    ESP.rtcUserMemoryWrite(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM));
    rtc_settings_crc = GetRtcSettingsCrc();
  }
}

void RtcSettingsLoad(void)
{
  ESP.rtcUserMemoryRead(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM));  // 0x290
  if (RtcSettings.valid != RTC_MEM_VALID) {
    memset(&RtcSettings, 0, sizeof(RTCMEM));
    RtcSettings.valid = RTC_MEM_VALID;
    RtcSettings.energy_kWhtoday = Settings.energy_kWhtoday;
    RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal;
    RtcSettings.energy_usage = Settings.energy_usage;
    for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
      RtcSettings.pulse_counter[i] = Settings.pulse_counter[i];
    }
    RtcSettings.power = Settings.power;
    RtcSettingsSave();
  }
  rtc_settings_crc = GetRtcSettingsCrc();
}

bool RtcSettingsValid(void)
{
  return (RTC_MEM_VALID == RtcSettings.valid);
}

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

uint32_t rtc_reboot_crc = 0;

uint32_t GetRtcRebootCrc(void)
{
  uint32_t crc = 0;
  uint8_t *bytes = (uint8_t*)&RtcReboot;

  for (uint32_t i = 0; i < sizeof(RTCRBT); i++) {
    crc += bytes[i]*(i+1);
  }
  return crc;
}

void RtcRebootSave(void)
{
  if (GetRtcRebootCrc() != rtc_reboot_crc) {
    RtcReboot.valid = RTC_MEM_VALID;
    ESP.rtcUserMemoryWrite(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT));
    rtc_reboot_crc = GetRtcRebootCrc();
  }
}

void RtcRebootReset(void)
{
  RtcReboot.fast_reboot_count = 0;
  RtcRebootSave();
}

void RtcRebootLoad(void)
{
  ESP.rtcUserMemoryRead(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT));  // 0x280
  if (RtcReboot.valid != RTC_MEM_VALID) {
    memset(&RtcReboot, 0, sizeof(RTCRBT));
    RtcReboot.valid = RTC_MEM_VALID;
//    RtcReboot.fast_reboot_count = 0;  // Explicit by memset
    RtcRebootSave();
  }
  rtc_reboot_crc = GetRtcRebootCrc();
}

bool RtcRebootValid(void)
{
  return (RTC_MEM_VALID == RtcReboot.valid);
}

/*********************************************************************************************\
 * Config - Flash
 *
 * Tasmota 1M flash usage
 * 0x00000000 - Unzipped binary bootloader
 * 0x00001000 - Unzipped binary code start
 *    ::::
 * 0x000xxxxx - Unzipped binary code end
 * 0x000x1000 - First page used by Core OTA
 *    ::::
 * 0x000F3000 - Tasmota Quick Power Cycle counter (SETTINGS_LOCATION - CFG_ROTATES) - First four bytes only
 * 0x000F4000 - First Tasmota rotating settings page
 *    ::::
 * 0x000FA000 - Last Tasmota rotating settings page = Last page used by Core OTA
 * 0x000FB000 - Core SPIFFS end = Core EEPROM = Tasmota settings page during OTA and when no flash rotation is active (SETTINGS_LOCATION)
 * 0x000FC000 - SDK - Uses first 128 bytes for phy init data mirrored by Core in RAM. See core_esp8266_phy.cpp phy_init_data[128] = Core user_rf_cal_sector
 * 0x000FD000 - SDK - Uses scattered bytes from 0x340 (iTead use as settings storage from 0x000FD000)
 * 0x000FE000 - SDK - Uses scattered bytes from 0x340 (iTead use as mirrored settings storage from 0x000FE000)
 * 0x000FF000 - SDK - Uses at least first 32 bytes of this page - Tasmota Zigbee persistence from 0x000FF800 to 0x000FFFFF
\*********************************************************************************************/

extern "C" {
#include "spi_flash.h"
}
#include "eboot_command.h"

#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2)

extern "C" uint32_t _SPIFFS_end;
// From libraries/EEPROM/EEPROM.cpp EEPROMClass
const uint32_t SPIFFS_END = ((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE;

#else  // Core > 2.5.2 and STAGE

#if AUTOFLASHSIZE

#include "flash_hal.h"

// From libraries/EEPROM/EEPROM.cpp EEPROMClass
const uint32_t SPIFFS_END = (FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE;

#else

extern "C" uint32_t _FS_end;
// From libraries/EEPROM/EEPROM.cpp EEPROMClass
const uint32_t SPIFFS_END = ((uint32_t)&_FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE;

#endif  // AUTOFLASHSIZE

#endif  // All cores < pre-2.6.0

// Version 4.2 config = eeprom area
const uint32_t SETTINGS_LOCATION = SPIFFS_END;  // No need for SPIFFS as it uses EEPROM area
// Version 5.2 allow for more flash space
const uint8_t CFG_ROTATES = 8;          // Number of flash sectors used (handles uploads)

uint32_t settings_location = SETTINGS_LOCATION;
uint32_t settings_crc32 = 0;
uint8_t *settings_buffer = nullptr;

/********************************************************************************************/
/*
 * Based on cores/esp8266/Updater.cpp
 */
void SetFlashModeDout(void)
{
  uint8_t *_buffer;
  uint32_t address;

  eboot_command ebcmd;
  eboot_command_read(&ebcmd);
  address = ebcmd.args[0];
  _buffer = new uint8_t[FLASH_SECTOR_SIZE];

  if (ESP.flashRead(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) {
    if (_buffer[2] != 3) {  // DOUT
      _buffer[2] = 3;
      if (ESP.flashEraseSector(address / FLASH_SECTOR_SIZE)) {
        ESP.flashWrite(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE);
      }
    }
  }
  delete[] _buffer;
}

bool VersionCompatible(void)
{
  if (Settings.flag3.compatibility_check) {
    return true;
  }

  eboot_command ebcmd;
  eboot_command_read(&ebcmd);
  uint32_t start_address = ebcmd.args[0];
  uint32_t end_address = start_address + (ebcmd.args[2] & 0xFFFFF000) + FLASH_SECTOR_SIZE;
  uint32_t* buffer = new uint32_t[FLASH_SECTOR_SIZE / 4];

  uint32_t version[3] = { 0 };
  bool found = false;
  for (uint32_t address = start_address; address < end_address; address = address + FLASH_SECTOR_SIZE) {
    ESP.flashRead(address, (uint32_t*)buffer, FLASH_SECTOR_SIZE);
    if ((address == start_address) && (0x1F == (buffer[0] & 0xFF))) {
      version[1] = 0xFFFFFFFF;  // Ota file is gzipped and can not be checked for compatibility
      found = true;
    } else {
      for (uint32_t i = 0; i < (FLASH_SECTOR_SIZE / 4); i++) {
        version[0] = version[1];
        version[1] = version[2];
        version[2] = buffer[i];
        if ((MARKER_START == version[0]) && (MARKER_END == version[2])) {
          found = true;
          break;
        }
      }
    }
    if (found) { break; }
  }
  delete[] buffer;

  if (!found) { version[1] = 0; }

  AddLog_P2(LOG_LEVEL_DEBUG, PSTR("OTA: Version 0x%08X, Compatible 0x%08X"), version[1], VERSION_COMPATIBLE);

  if (version[1] < VERSION_COMPATIBLE) {
    uint32_t eboot_magic = 0;  // Abandon OTA result
    ESP.rtcUserMemoryWrite(0, (uint32_t*)&eboot_magic, sizeof(eboot_magic));
    return false;
  }

  return true;
}

void SettingsBufferFree(void)
{
  if (settings_buffer != nullptr) {
    free(settings_buffer);
    settings_buffer = nullptr;
  }
}

bool SettingsBufferAlloc(void)
{
  SettingsBufferFree();
  if (!(settings_buffer = (uint8_t *)malloc(sizeof(Settings)))) {
    AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_UPLOAD_ERR_2));  // Not enough (memory) space
    return false;
  }
  return true;
}

uint16_t GetCfgCrc16(uint8_t *bytes, uint32_t size)
{
  uint16_t crc = 0;

  for (uint32_t i = 0; i < size; i++) {
    if ((i < 14) || (i > 15)) { crc += bytes[i]*(i+1); }  // Skip crc
  }
  return crc;
}

uint16_t GetSettingsCrc(void)
{
  // Fix miscalculation if previous Settings was 3584 and current Settings is 4096 between 0x06060007 and 0x0606000A
  uint32_t size = ((Settings.version < 0x06060007) || (Settings.version > 0x0606000A)) ? 3584 : sizeof(SYSCFG);
  return GetCfgCrc16((uint8_t*)&Settings, size);
}

uint32_t GetCfgCrc32(uint8_t *bytes, uint32_t size)
{
  // https://create.stephan-brumme.com/crc32/#bitwise
  uint32_t crc = 0;

  while (size--) {
    crc ^= *bytes++;
    for (uint32_t j = 0; j < 8; j++) {
      crc = (crc >> 1) ^ (-int(crc & 1) & 0xEDB88320);
    }
  }
  return ~crc;
}

uint32_t GetSettingsCrc32(void)
{
  return GetCfgCrc32((uint8_t*)&Settings, sizeof(SYSCFG) -4);  // Skip crc32
}

void SettingsSaveAll(void)
{
  if (Settings.flag.save_state) {
    Settings.power = power;
  } else {
    Settings.power = 0;
  }
  XsnsCall(FUNC_SAVE_BEFORE_RESTART);
  XdrvCall(FUNC_SAVE_BEFORE_RESTART);
  SettingsSave(0);
}

/*********************************************************************************************\
 * Quick power cycle monitoring
\*********************************************************************************************/

void UpdateQuickPowerCycle(bool update)
{
  if (Settings.flag3.fast_power_cycle_disable) { return; }

  uint32_t pc_register;
  uint32_t pc_location = SETTINGS_LOCATION - CFG_ROTATES;

  ESP.flashRead(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register));
  if (update && ((pc_register & 0xFFFFFFF0) == 0xFFA55AB0)) {
    uint32_t counter = ((pc_register & 0xF) << 1) & 0xF;
    if (0 == counter) {  // 4 power cycles in a row
      SettingsErase(3);  // Quickly reset all settings including QuickPowerCycle flag
      EspRestart();      // And restart
    } else {
      pc_register = 0xFFA55AB0 | counter;
      ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register));
      AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Flag %02X"), counter);
    }
  }
  else if (pc_register != 0xFFA55ABF) {
    pc_register = 0xFFA55ABF;
    // Assume flash is default all ones and setting a bit to zero does not need an erase
    if (ESP.flashEraseSector(pc_location)) {
      ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register));
    }
    AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Reset"));
  }
}

/*********************************************************************************************\
 * Config Settings.text char array support
\*********************************************************************************************/

uint32_t GetSettingsTextLen(void)
{
  char* position = Settings.text_pool;
  for (uint32_t size = 0; size < SET_MAX; size++) {
    while (*position++ != '\0') { }
  }
  return position - Settings.text_pool;
}

bool SettingsUpdateText(uint32_t index, const char* replace_me)
{
  if (index >= SET_MAX) {
    return false;  // Setting not supported - internal error
  }

  // Make a copy first in case we use source from Settings.text
  uint32_t replace_len = strlen(replace_me);
  char replace[replace_len +1];
  memcpy(replace, replace_me, sizeof(replace));

  uint32_t start_pos = 0;
  uint32_t end_pos = 0;
  char* position = Settings.text_pool;
  for (uint32_t size = 0; size < SET_MAX; size++) {
    while (*position++ != '\0') { }
    if (1 == index) {
      start_pos = position - Settings.text_pool;
    }
    else if (0 == index) {
      end_pos = position - Settings.text_pool -1;
    }
    index--;
  }
  uint32_t char_len = position - Settings.text_pool;

  uint32_t current_len = end_pos - start_pos;
  int diff = replace_len - current_len;

//  AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TST: start %d, end %d, len %d, current %d, replace %d, diff %d"),
//    start_pos, end_pos, char_len, current_len, replace_len, diff);

  int too_long = (char_len + diff) - settings_text_size;
  if (too_long > 0) {
    AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_CONFIG "Text overflow by %d char(s)"), too_long);
    return false;  // Replace text too long
  }

  if (diff != 0) {
    // Shift Settings.text up or down
    memmove_P(Settings.text_pool + start_pos + replace_len, Settings.text_pool + end_pos, char_len - end_pos);
  }
  // Replace text
  memmove_P(Settings.text_pool + start_pos, replace, replace_len);
  // Fill for future use
  memset(Settings.text_pool + char_len + diff, 0x00, settings_text_size - char_len - diff);

  AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG "CR %d/%d"), GetSettingsTextLen(), settings_text_size);

  return true;
}

char* SettingsText(uint32_t index)
{
  char* position = Settings.text_pool;

  if (index >= SET_MAX) {
    position += settings_text_size -1;  // Setting not supported - internal error - return empty string
  } else {
    for (;index > 0; index--) {
      while (*position++ != '\0') { }
    }
  }
  return position;
}

/*********************************************************************************************\
 * Config Save - Save parameters to Flash ONLY if any parameter has changed
\*********************************************************************************************/

uint32_t GetSettingsAddress(void)
{
  return settings_location * SPI_FLASH_SEC_SIZE;
}

void SettingsSave(uint8_t rotate)
{
/* Save configuration in eeprom or one of 7 slots below
 *
 * rotate 0 = Save in next flash slot
 * rotate 1 = Save only in eeprom flash slot until SetOption12 0 or restart
 * rotate 2 = Save in eeprom flash slot, erase next flash slots and continue depending on stop_flash_rotate
 * stop_flash_rotate 0 = Allow flash slot rotation (SetOption12 0)
 * stop_flash_rotate 1 = Allow only eeprom flash slot use (SetOption12 1)
 */
#ifndef FIRMWARE_MINIMAL
  if ((GetSettingsCrc32() != settings_crc32) || rotate) {
    if (1 == rotate) {   // Use eeprom flash slot only and disable flash rotate from now on (upgrade)
      stop_flash_rotate = 1;
    }
    if (2 == rotate) {   // Use eeprom flash slot and erase next flash slots if stop_flash_rotate is off (default)
      settings_location = SETTINGS_LOCATION +1;
    }
    if (stop_flash_rotate) {
      settings_location = SETTINGS_LOCATION;
    } else {
      settings_location--;
      if (settings_location <= (SETTINGS_LOCATION - CFG_ROTATES)) {
        settings_location = SETTINGS_LOCATION;
      }
    }

    Settings.save_flag++;
    if (UtcTime() > START_VALID_TIME) {
      Settings.cfg_timestamp = UtcTime();
    } else {
      Settings.cfg_timestamp++;
    }
    Settings.cfg_size = sizeof(SYSCFG);
    Settings.cfg_crc = GetSettingsCrc();  // Keep for backward compatibility in case of fall-back just after upgrade
    Settings.cfg_crc32 = GetSettingsCrc32();

    if (ESP.flashEraseSector(settings_location)) {
      ESP.flashWrite(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG));
    }

    if (!stop_flash_rotate && rotate) {
      for (uint32_t i = 1; i < CFG_ROTATES; i++) {
        ESP.flashEraseSector(settings_location -i);  // Delete previous configurations by resetting to 0xFF
        delay(1);
      }
    }

    AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG D_SAVED_TO_FLASH_AT " %X, " D_COUNT " %d, " D_BYTES " %d"), settings_location, Settings.save_flag, sizeof(SYSCFG));

    settings_crc32 = Settings.cfg_crc32;
  }
#endif  // FIRMWARE_MINIMAL
  RtcSettingsSave();
}

void SettingsLoad(void)
{
  // Load configuration from eeprom or one of 7 slots below if first valid load does not stop_flash_rotate
  struct SYSCFGH {
    uint16_t cfg_holder;                     // 000
    uint16_t cfg_size;                       // 002
    unsigned long save_flag;                 // 004
  } _SettingsH;
  unsigned long save_flag = 0;

  settings_location = 0;
  uint32_t flash_location = SETTINGS_LOCATION +1;
  uint16_t cfg_holder = 0;
  for (uint32_t i = 0; i < CFG_ROTATES; i++) {
    flash_location--;
    ESP.flashRead(flash_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG));

    bool valid = false;
    if (Settings.version > 0x06000000) {
      bool almost_valid = (Settings.cfg_crc32 == GetSettingsCrc32());
      if (Settings.version < 0x0606000B) {
        almost_valid = (Settings.cfg_crc == GetSettingsCrc());
      }
      // Sometimes CRC on pages below FB, overwritten by OTA, is fine but Settings are still invalid. So check cfg_holder too
      if (almost_valid && (0 == cfg_holder)) { cfg_holder = Settings.cfg_holder; }  // At FB always active cfg_holder
      valid = (cfg_holder == Settings.cfg_holder);
    } else {
      ESP.flashRead((flash_location -1) * SPI_FLASH_SEC_SIZE, (uint32*)&_SettingsH, sizeof(SYSCFGH));
      valid = (Settings.cfg_holder == _SettingsH.cfg_holder);
    }
    if (valid) {
      if (Settings.save_flag > save_flag) {
        save_flag = Settings.save_flag;
        settings_location = flash_location;
        if (Settings.flag.stop_flash_rotate && (0 == i)) {  // Stop only if eeprom area should be used and it is valid
          break;
        }
      }
    }

    delay(1);
  }
  if (settings_location > 0) {
    ESP.flashRead(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG));
    AddLog_P2(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_LOADED_FROM_FLASH_AT " %X, " D_COUNT " %lu"), settings_location, Settings.save_flag);
  }

#ifndef FIRMWARE_MINIMAL
  if (!settings_location || (Settings.cfg_holder != (uint16_t)CFG_HOLDER)) {  // Init defaults if cfg_holder differs from user settings in my_user_config.h
    SettingsDefault();
  }
  settings_crc32 = GetSettingsCrc32();
#endif  // FIRMWARE_MINIMAL

  RtcSettingsLoad();
}

void EspErase(uint32_t start_sector, uint32_t end_sector)
{
  bool serial_output = (LOG_LEVEL_DEBUG_MORE <= seriallog_level);
  for (uint32_t sector = start_sector; sector < end_sector; sector++) {

    bool result = ESP.flashEraseSector(sector);  // Arduino core - erases flash as seen by SDK
//    bool result = !SPIEraseSector(sector);       // SDK - erases flash as seen by SDK
//    bool result = EsptoolEraseSector(sector);    // Esptool - erases flash completely (slow)

    if (serial_output) {
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
      Serial.printf(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n", sector, (result) ? D_OK : D_ERROR);
#else
      Serial.printf_P(PSTR(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n"), sector, (result) ? D_OK : D_ERROR);
#endif
      delay(10);
    } else {
      yield();
    }
    OsWatchLoop();
  }
}

void SettingsErase(uint8_t type)
{
  /*
    For Arduino core and SDK:
    Erase only works from flash start address to SDK recognized flash end address (flashchip->chip_size = ESP.getFlashChipSize).
    Addresses above SDK recognized size (up to ESP.getFlashChipRealSize) are not accessable.
    For Esptool:
    The only way to erase whole flash is esptool which uses direct SPI writes to flash.

    The default erase function is EspTool (EsptoolErase)

    0 = Erase from program end until end of flash as seen by SDK
    1 = Erase 16k SDK parameter area near end of flash as seen by SDK (0x0xFCxxx - 0x0xFFFFF) solving possible wifi errors
    2 = Erase Tasmota parameter area (0x0xF3xxx - 0x0xFBFFF)
    3 = Erase Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
    4 = Erase SDK parameter area used for wifi calibration (0x0FCxxx - 0x0FCFFF)
  */

#ifndef FIRMWARE_MINIMAL
  uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1;
  uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE;  // Flash size as reported by hardware
  if (1 == type) {
    // source Esp.cpp and core_esp8266_phy.cpp
    _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4;     // SDK parameter area
  }
  else if (2 == type) {
    _sectorStart = SETTINGS_LOCATION - CFG_ROTATES;                       // Tasmota parameter area (0x0F3xxx - 0x0FBFFF)
    _sectorEnd = SETTINGS_LOCATION +1;
  }
  else if (3 == type) {
    _sectorStart = SETTINGS_LOCATION - CFG_ROTATES;                       // Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
    _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;             // Flash size as seen by SDK
  }
  else if (4 == type) {
//    _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4;     // SDK phy area and Core calibration sector (0x0FC000)
    _sectorStart = SETTINGS_LOCATION +1;                                  // SDK phy area and Core calibration sector (0x0FC000)
    _sectorEnd = _sectorStart +1;                                         // SDK end of phy area and Core calibration sector (0x0FCFFF)
  }
/*
  else if (5 == type) {
    _sectorStart = (ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE) -4;  // SDK phy area and Core calibration sector (0xxFC000)
    _sectorEnd = _sectorStart +1;                                         // SDK end of phy area and Core calibration sector (0xxFCFFF)
  }
*/
  else {
    return;
  }

  AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " from 0x%08X to 0x%08X"), _sectorStart * SPI_FLASH_SEC_SIZE, (_sectorEnd * SPI_FLASH_SEC_SIZE) -1);

//  EspErase(_sectorStart, _sectorEnd);                                     // Arduino core and SDK - erases flash as seen by SDK
  EsptoolErase(_sectorStart, _sectorEnd);                                 // Esptool - erases flash completely
#endif  // FIRMWARE_MINIMAL
}

void SettingsSdkErase(void)
{
  WiFi.disconnect(false);    // Delete SDK wifi config
  SettingsErase(1);
  delay(1000);
}

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

void SettingsDefault(void)
{
  AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_USE_DEFAULTS));
  SettingsDefaultSet1();
  SettingsDefaultSet2();
  SettingsSave(2);
}

void SettingsDefaultSet1(void)
{
  memset(&Settings, 0x00, sizeof(SYSCFG));

  Settings.cfg_holder = (uint16_t)CFG_HOLDER;
  Settings.cfg_size = sizeof(SYSCFG);
//  Settings.save_flag = 0;
  Settings.version = VERSION;
//  Settings.bootcount = 0;
//  Settings.cfg_crc = 0;
}

void SettingsDefaultSet2(void)
{
  memset((char*)&Settings +16, 0x00, sizeof(SYSCFG) -16);

  Settings.flag.stop_flash_rotate = APP_FLASH_CYCLE;
  Settings.flag.global_state = APP_ENABLE_LEDLINK;
  Settings.flag3.sleep_normal = APP_NORMAL_SLEEP;
  Settings.flag3.no_power_feedback = APP_NO_RELAY_SCAN;
  Settings.flag3.fast_power_cycle_disable = APP_DISABLE_POWERCYCLE;
  Settings.flag3.bootcount_update = DEEPSLEEP_BOOTCOUNT;
  Settings.flag3.compatibility_check = OTA_COMPATIBILITY;
  Settings.save_data = SAVE_DATA;
  Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY;
  Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET;  // SetOption36
  Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW;
  Settings.sleep = APP_SLEEP;
  if (Settings.sleep < 50) {
    Settings.sleep = 50;                // Default to 50 for sleep, for now
  }

  // Module
//  Settings.flag.interlock = 0;
  Settings.interlock[0] = 0xFF;         // Legacy support using all relays in one interlock group
  Settings.module = MODULE;
  ModuleDefault(WEMOS);
//  for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { Settings.my_gp.io[i] = GPIO_NONE; }
  SettingsUpdateText(SET_FRIENDLYNAME1, FRIENDLY_NAME);
  SettingsUpdateText(SET_FRIENDLYNAME2, FRIENDLY_NAME"2");
  SettingsUpdateText(SET_FRIENDLYNAME3, FRIENDLY_NAME"3");
  SettingsUpdateText(SET_FRIENDLYNAME4, FRIENDLY_NAME"4");
  SettingsUpdateText(SET_OTAURL, OTA_URL);

  // Power
  Settings.flag.save_state = SAVE_STATE;
  Settings.power = APP_POWER;
  Settings.poweronstate = APP_POWERON_STATE;
  Settings.blinktime = APP_BLINKTIME;
  Settings.blinkcount = APP_BLINKCOUNT;
  Settings.ledstate = APP_LEDSTATE;
  Settings.ledmask = APP_LEDMASK;
  Settings.pulse_timer[0] = APP_PULSETIME;
//  for (uint32_t i = 1; i < MAX_PULSETIMERS; i++) { Settings.pulse_timer[i] = 0; }

  // Serial
  Settings.serial_config = TS_SERIAL_8N1;
  Settings.baudrate = APP_BAUDRATE / 300;
  Settings.sbaudrate = SOFT_BAUDRATE / 300;
  Settings.serial_delimiter = 0xff;
  Settings.seriallog_level = SERIAL_LOG_LEVEL;

  // Wifi
  Settings.flag3.use_wifi_scan = WIFI_SCAN_AT_RESTART;
  Settings.flag3.use_wifi_rescan = WIFI_SCAN_REGULARLY;
  Settings.wifi_output_power = 170;
  ParseIp(&Settings.ip_address[0], WIFI_IP_ADDRESS);
  ParseIp(&Settings.ip_address[1], WIFI_GATEWAY);
  ParseIp(&Settings.ip_address[2], WIFI_SUBNETMASK);
  ParseIp(&Settings.ip_address[3], WIFI_DNS);
  Settings.sta_config = WIFI_CONFIG_TOOL;
//  Settings.sta_active = 0;
  SettingsUpdateText(SET_STASSID1, STA_SSID1);
  SettingsUpdateText(SET_STASSID2, STA_SSID2);
  SettingsUpdateText(SET_STAPWD1, STA_PASS1);
  SettingsUpdateText(SET_STAPWD2, STA_PASS2);
  SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);

  // Syslog
  SettingsUpdateText(SET_SYSLOG_HOST, SYS_LOG_HOST);
  Settings.syslog_port = SYS_LOG_PORT;
  Settings.syslog_level = SYS_LOG_LEVEL;

  // Webserver
  Settings.flag2.emulation = EMULATION;
  Settings.flag3.gui_hostname_ip = GUI_SHOW_HOSTNAME;
  Settings.flag3.mdns_enabled = MDNS_ENABLED;
  Settings.webserver = WEB_SERVER;
  Settings.weblog_level = WEB_LOG_LEVEL;
  SettingsUpdateText(SET_WEBPWD, WEB_PASSWORD);
  SettingsUpdateText(SET_CORS, CORS_DOMAIN);

  // Button
  Settings.flag.button_restrict = KEY_DISABLE_MULTIPRESS;
  Settings.flag.button_swap = KEY_SWAP_DOUBLE_PRESS;
  Settings.flag.button_single = KEY_ONLY_SINGLE_PRESS;
  Settings.param[P_HOLD_TIME] = KEY_HOLD_TIME;  // Default 4 seconds hold time

  // Switch
  for (uint32_t i = 0; i < MAX_SWITCHES; i++) { Settings.switchmode[i] = SWITCH_MODE; }

  // MQTT
  Settings.flag.mqtt_enabled = MQTT_USE;
  Settings.flag.mqtt_response = MQTT_RESULT_COMMAND;
  Settings.flag.mqtt_offline = MQTT_LWT_MESSAGE;
  Settings.flag.mqtt_power_retain = MQTT_POWER_RETAIN;
  Settings.flag.mqtt_button_retain = MQTT_BUTTON_RETAIN;
  Settings.flag.mqtt_switch_retain = MQTT_SWITCH_RETAIN;
  Settings.flag.mqtt_sensor_retain = MQTT_SENSOR_RETAIN;
//  Settings.flag.mqtt_serial = 0;
  Settings.flag.device_index_enable = MQTT_POWER_FORMAT;
  Settings.flag3.time_append_timezone = MQTT_APPEND_TIMEZONE;
  Settings.flag3.button_switch_force_local = MQTT_BUTTON_SWITCH_FORCE_LOCAL;
  Settings.flag3.no_hold_retain = MQTT_NO_HOLD_RETAIN;
  Settings.flag3.use_underscore = MQTT_INDEX_SEPARATOR;
  Settings.flag3.grouptopic_mode = MQTT_GROUPTOPIC_FORMAT;
  SettingsUpdateText(SET_MQTT_HOST, MQTT_HOST);
  Settings.mqtt_port = MQTT_PORT;
  SettingsUpdateText(SET_MQTT_CLIENT, MQTT_CLIENT_ID);
  SettingsUpdateText(SET_MQTT_USER, MQTT_USER);
  SettingsUpdateText(SET_MQTT_PWD, MQTT_PASS);
  SettingsUpdateText(SET_MQTT_TOPIC, MQTT_TOPIC);
  SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC);
  SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC);
  SettingsUpdateText(SET_MQTT_GRP_TOPIC, MQTT_GRPTOPIC);
  SettingsUpdateText(SET_MQTT_FULLTOPIC, MQTT_FULLTOPIC);
  Settings.mqtt_retry = MQTT_RETRY_SECS;
  SettingsUpdateText(SET_MQTTPREFIX1, SUB_PREFIX);
  SettingsUpdateText(SET_MQTTPREFIX2, PUB_PREFIX);
  SettingsUpdateText(SET_MQTTPREFIX3, PUB_PREFIX2);
  SettingsUpdateText(SET_STATE_TXT1, MQTT_STATUS_OFF);
  SettingsUpdateText(SET_STATE_TXT2, MQTT_STATUS_ON);
  SettingsUpdateText(SET_STATE_TXT3, MQTT_CMND_TOGGLE);
  SettingsUpdateText(SET_STATE_TXT4, MQTT_CMND_HOLD);
  char fingerprint[60];
  strlcpy(fingerprint, MQTT_FINGERPRINT1, sizeof(fingerprint));
  char *p = fingerprint;
  for (uint32_t i = 0; i < 20; i++) {
    Settings.mqtt_fingerprint[0][i] = strtol(p, &p, 16);
  }
  strlcpy(fingerprint, MQTT_FINGERPRINT2, sizeof(fingerprint));
  p = fingerprint;
  for (uint32_t i = 0; i < 20; i++) {
    Settings.mqtt_fingerprint[1][i] = strtol(p, &p, 16);
  }
  Settings.tele_period = TELE_PERIOD;
  Settings.mqttlog_level = MQTT_LOG_LEVEL;

  // Energy
  Settings.flag.no_power_on_check = ENERGY_VOLTAGE_ALWAYS;
  Settings.flag2.current_resolution = 3;
//  Settings.flag2.voltage_resolution = 0;
//  Settings.flag2.wattage_resolution = 0;
  Settings.flag2.energy_resolution = ENERGY_RESOLUTION;
  Settings.flag3.dds2382_model = ENERGY_DDS2382_MODE;
  Settings.flag3.hardware_energy_total = ENERGY_HARDWARE_TOTALS;
  Settings.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY;
//  Settings.energy_power_delta = 0;
  Settings.energy_power_calibration = HLW_PREF_PULSE;
  Settings.energy_voltage_calibration = HLW_UREF_PULSE;
  Settings.energy_current_calibration = HLW_IREF_PULSE;
//  Settings.energy_kWhtoday = 0;
//  Settings.energy_kWhyesterday = 0;
//  Settings.energy_kWhdoy = 0;
//  Settings.energy_min_power = 0;
//  Settings.energy_max_power = 0;
//  Settings.energy_min_voltage = 0;
//  Settings.energy_max_voltage = 0;
//  Settings.energy_min_current = 0;
//  Settings.energy_max_current = 0;
//  Settings.energy_max_power_limit = 0;                            // MaxPowerLimit
  Settings.energy_max_power_limit_hold = MAX_POWER_HOLD;
  Settings.energy_max_power_limit_window = MAX_POWER_WINDOW;
//  Settings.energy_max_power_safe_limit = 0;                       // MaxSafePowerLimit
  Settings.energy_max_power_safe_limit_hold = SAFE_POWER_HOLD;
  Settings.energy_max_power_safe_limit_window = SAFE_POWER_WINDOW;
//  Settings.energy_max_energy = 0;                                 // MaxEnergy
//  Settings.energy_max_energy_start = 0;                           // MaxEnergyStart
//  Settings.energy_kWhtotal = 0;
  RtcSettings.energy_kWhtotal = 0;
//  memset((char*)&Settings.energy_usage, 0x00, sizeof(Settings.energy_usage));
  memset((char*)&RtcSettings.energy_usage, 0x00, sizeof(RtcSettings.energy_usage));
  Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP;

  // IRRemote
  Settings.flag.ir_receive_decimal = IR_DATA_RADIX;
  Settings.flag3.receive_raw = IR_ADD_RAW_DATA;
  Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE;

  // RF Bridge
  Settings.flag.rf_receive_decimal = RF_DATA_RADIX;
//  for (uint32_t i = 0; i < 17; i++) { Settings.rf_code[i][0] = 0; }
  memcpy_P(Settings.rf_code[0], kDefaultRfCode, 9);

  // Domoticz
  Settings.domoticz_update_timer = DOMOTICZ_UPDATE_TIMER;
//  for (uint32_t i = 0; i < MAX_DOMOTICZ_IDX; i++) {
//    Settings.domoticz_relay_idx[i] = 0;
//    Settings.domoticz_key_idx[i] = 0;
//    Settings.domoticz_switch_idx[i] = 0;
//  }
//  for (uint32_t i = 0; i < MAX_DOMOTICZ_SNS_IDX; i++) {
//    Settings.domoticz_sensor_idx[i] = 0;
//  }

  // Sensor
  Settings.flag.temperature_conversion = TEMP_CONVERSION;
  Settings.flag.pressure_conversion = PRESSURE_CONVERSION;
  Settings.flag2.pressure_resolution = PRESSURE_RESOLUTION;
  Settings.flag2.humidity_resolution = HUMIDITY_RESOLUTION;
  Settings.flag2.temperature_resolution = TEMP_RESOLUTION;
  Settings.flag3.ds18x20_internal_pullup = DS18X20_PULL_UP;
  Settings.flag3.counter_reset_on_tele = COUNTER_RESET;
//  Settings.altitude = 0;

  // Rules
//  Settings.rule_enabled = 0;
//  Settings.rule_once = 0;
//  for (uint32_t i = 1; i < MAX_RULE_SETS; i++) { Settings.rules[i][0] = '\0'; }
  Settings.flag2.calc_resolution = CALC_RESOLUTION;

  // Timer
  Settings.flag3.timers_enable = TIMERS_ENABLED;

  // Home Assistant
  Settings.flag.hass_light = HASS_AS_LIGHT;
  Settings.flag.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE;
  Settings.flag3.hass_tele_on_power = TELE_ON_POWER;

  // Knx
  Settings.flag.knx_enabled = KNX_ENABLED;
  Settings.flag.knx_enable_enhancement = KNX_ENHANCED;

  // Light
  Settings.flag.pwm_control = LIGHT_MODE;
  Settings.flag.ws_clock_reverse = LIGHT_CLOCK_DIRECTION;
  Settings.flag.light_signal = LIGHT_PAIRS_CO2;
  Settings.flag.not_power_linked = LIGHT_POWER_CONTROL;
  Settings.flag.decimal_text = LIGHT_COLOR_RADIX;
  Settings.flag3.pwm_multi_channels = LIGHT_CHANNEL_MODE;
  Settings.flag3.slider_dimmer_stay_on = LIGHT_SLIDER_POWER;
  Settings.flag4.alexa_ct_range = LIGHT_ALEXA_CT_RANGE;

  Settings.pwm_frequency = PWM_FREQ;
  Settings.pwm_range = PWM_RANGE;
  for (uint32_t i = 0; i < MAX_PWMS; i++) {
    Settings.light_color[i] = DEFAULT_LIGHT_COMPONENT;
//    Settings.pwm_value[i] = 0;
  }
  Settings.light_correction = 1;
  Settings.light_dimmer = DEFAULT_LIGHT_DIMMER;
//  Settings.light_fade = 0;
  Settings.light_speed = 1;
//  Settings.light_scheme = 0;
  Settings.light_width = 1;
//  Settings.light_wakeup = 0;
  Settings.light_pixels = WS2812_LEDS;
//  Settings.light_rotation = 0;
  Settings.ws_width[WS_SECOND] = 1;
  Settings.ws_color[WS_SECOND][WS_RED] = 255;
//  Settings.ws_color[WS_SECOND][WS_GREEN] = 0;
  Settings.ws_color[WS_SECOND][WS_BLUE] = 255;
  Settings.ws_width[WS_MINUTE] = 3;
//  Settings.ws_color[WS_MINUTE][WS_RED] = 0;
  Settings.ws_color[WS_MINUTE][WS_GREEN] = 255;
//  Settings.ws_color[WS_MINUTE][WS_BLUE] = 0;
  Settings.ws_width[WS_HOUR] = 5;
  Settings.ws_color[WS_HOUR][WS_RED] = 255;
//  Settings.ws_color[WS_HOUR][WS_GREEN] = 0;
//  Settings.ws_color[WS_HOUR][WS_BLUE] = 0;

  Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX;
  Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN;

  // Display
//  Settings.display_model = 0;
  Settings.display_mode = 1;
  Settings.display_refresh = 2;
  Settings.display_rows = 2;
  Settings.display_cols[0] = 16;
  Settings.display_cols[1] = 8;
  Settings.display_dimmer = 1;
  Settings.display_size = 1;
  Settings.display_font = 1;
//  Settings.display_rotate = 0;
  Settings.display_address[0] = MTX_ADDRESS1;
  Settings.display_address[1] = MTX_ADDRESS2;
  Settings.display_address[2] = MTX_ADDRESS3;
  Settings.display_address[3] = MTX_ADDRESS4;
  Settings.display_address[4] = MTX_ADDRESS5;
  Settings.display_address[5] = MTX_ADDRESS6;
  Settings.display_address[6] = MTX_ADDRESS7;
  Settings.display_address[7] = MTX_ADDRESS8;

  // Time
  if (((APP_TIMEZONE > -14) && (APP_TIMEZONE < 15)) || (99 == APP_TIMEZONE)) {
    Settings.timezone = APP_TIMEZONE;
    Settings.timezone_minutes = 0;
  } else {
    Settings.timezone = APP_TIMEZONE / 60;
    Settings.timezone_minutes = abs(APP_TIMEZONE % 60);
  }
  SettingsUpdateText(SET_NTPSERVER1, NTP_SERVER1);
  SettingsUpdateText(SET_NTPSERVER2, NTP_SERVER2);
  SettingsUpdateText(SET_NTPSERVER3, NTP_SERVER3);
  for (uint32_t i = 0; i < MAX_NTP_SERVERS; i++) {
    SettingsUpdateText(SET_NTPSERVER1 +i, ReplaceCommaWithDot(SettingsText(SET_NTPSERVER1 +i)));
  }
  Settings.latitude = (int)((double)LATITUDE * 1000000);
  Settings.longitude = (int)((double)LONGITUDE * 1000000);
  SettingsResetStd();
  SettingsResetDst();

  Settings.button_debounce = KEY_DEBOUNCE_TIME;
  Settings.switch_debounce = SWITCH_DEBOUNCE_TIME;

  for (uint32_t j = 0; j < 5; j++) {
    Settings.rgbwwTable[j] = 255;
  }

  Settings.novasds_startingoffset = STARTING_OFFSET;

  SettingsDefaultWebColor();

  memset(&Settings.monitors, 0xFF, 20);  // Enable all possible monitors, displays and sensors
  SettingsEnableAllI2cDrivers();

  // Tuya
  Settings.flag3.tuya_apply_o20 = TUYA_SETOPTION_20;
  Settings.flag3.tuya_serial_mqtt_publish = MQTT_TUYA_RECEIVED;

  Settings.flag3.buzzer_enable = BUZZER_ENABLE;
  Settings.flag3.shutter_mode = SHUTTER_SUPPORT;
  Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS;
  Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES;
}

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

void SettingsResetStd(void)
{
  Settings.tflag[0].hemis = TIME_STD_HEMISPHERE;
  Settings.tflag[0].week = TIME_STD_WEEK;
  Settings.tflag[0].dow = TIME_STD_DAY;
  Settings.tflag[0].month = TIME_STD_MONTH;
  Settings.tflag[0].hour = TIME_STD_HOUR;
  Settings.toffset[0] = TIME_STD_OFFSET;
}

void SettingsResetDst(void)
{
  Settings.tflag[1].hemis = TIME_DST_HEMISPHERE;
  Settings.tflag[1].week = TIME_DST_WEEK;
  Settings.tflag[1].dow = TIME_DST_DAY;
  Settings.tflag[1].month = TIME_DST_MONTH;
  Settings.tflag[1].hour = TIME_DST_HOUR;
  Settings.toffset[1] = TIME_DST_OFFSET;
}

void SettingsDefaultWebColor(void)
{
  char scolor[10];
  for (uint32_t i = 0; i < COL_LAST; i++) {
    WebHexCode(i, GetTextIndexed(scolor, sizeof(scolor), i, kWebColors));
  }
}

void SettingsEnableAllI2cDrivers(void)
{
  Settings.i2c_drivers[0] = 0xFFFFFFFF;
  Settings.i2c_drivers[1] = 0xFFFFFFFF;
  Settings.i2c_drivers[2] = 0xFFFFFFFF;
}

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

void SettingsDelta(void)
{
  if (Settings.version != VERSION) {      // Fix version dependent changes
    if (Settings.version < 0x06000000) {
      Settings.cfg_size = sizeof(SYSCFG);
      Settings.cfg_crc = GetSettingsCrc();
    }
    if (Settings.version < 0x06000002) {
      for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
        if (i < 4) {
          Settings.switchmode[i] = Settings.interlock[i];
        } else {
          Settings.switchmode[i] = SWITCH_MODE;
        }
      }
      for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
        if (Settings.my_gp.io[i] >= GPIO_SWT5) {  // Move up from GPIO_SWT5 to GPIO_KEY1
          Settings.my_gp.io[i] += 4;
        }
      }
    }
    if (Settings.version < 0x06000003) {
      Settings.flag.mqtt_serial_raw = 0;      // Was rules_enabled until 5.14.0b
      Settings.flag.pressure_conversion = 0;  // Was rules_once until 5.14.0b
      Settings.flag3.data = 0;
    }
    if (Settings.version < 0x06010103) {
      Settings.flag3.timers_enable = 1;
    }
    if (Settings.version < 0x0601010C) {
      Settings.button_debounce = KEY_DEBOUNCE_TIME;
      Settings.switch_debounce = SWITCH_DEBOUNCE_TIME;
    }
    if (Settings.version < 0x0602010A) {
      for (uint32_t j = 0; j < 5; j++) {
        Settings.rgbwwTable[j] = 255;
      }
    }
    if (Settings.version < 0x06030002) {
      Settings.timezone_minutes = 0;
    }
    if (Settings.version < 0x06030004) {
      memset(&Settings.monitors, 0xFF, 20);  // Enable all possible monitors, displays and sensors
    }
    if (Settings.version < 0x0603000E) {
      Settings.flag2.calc_resolution = CALC_RESOLUTION;
    }
    if (Settings.version < 0x0603000F) {
      if (Settings.sleep < 50) {
        Settings.sleep = 50;                // Default to 50 for sleep, for now
      }
    }
    if (Settings.version < 0x06040105) {
      Settings.flag3.mdns_enabled = MDNS_ENABLED;
      Settings.param[P_MDNS_DELAYED_START] = 0;
    }
    if (Settings.version < 0x0604010B) {
      Settings.interlock[0] = 0xFF;         // Legacy support using all relays in one interlock group
      for (uint32_t i = 1; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; }
    }
    if (Settings.version < 0x0604010D) {
      Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET;  // SetOption36
    }
    if (Settings.version < 0x06040110) {
      ModuleDefault(WEMOS);
    }
    if (Settings.version < 0x06040113) {
      Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW;
    }
    if (Settings.version < 0x06050003) {
      Settings.novasds_startingoffset = STARTING_OFFSET;
    }
    if (Settings.version < 0x06050006) {
      SettingsDefaultWebColor();
    }
    if (Settings.version < 0x06050007) {
      Settings.ledmask = APP_LEDMASK;
    }
    if (Settings.version < 0x0605000A) {
      Settings.my_adc0 = ADC0_NONE;
    }
    if (Settings.version < 0x0605000D) {
      Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE;
    }
    if (Settings.version < 0x06060001) {
      Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP;
    }
    if (Settings.version < 0x06060007) {
      memset((char*)&Settings +0xE00, 0x00, sizeof(SYSCFG) -0xE00);
    }
    if (Settings.version < 0x06060008) {
      // Move current tuya dimmer range to the new param.
      if (Settings.flag3.tuya_serial_mqtt_publish) { // ex Settings.flag3.ex_tuya_dimmer_range_255 SetOption
        Settings.param[P_ex_DIMMER_MAX] = 100;
      } else {
        Settings.param[P_ex_DIMMER_MAX] = 255;
      }
    }
    if (Settings.version < 0x06060009) {
      Settings.baudrate = APP_BAUDRATE / 300;
      Settings.sbaudrate = SOFT_BAUDRATE / 300;
    }
    if (Settings.version < 0x0606000A) {
      uint8_t tuyaindex = 0;
      if (Settings.param[P_BACKLOG_DELAY] > 0) {             // ex SetOption34
        Settings.tuya_fnid_map[tuyaindex].fnid = 21;         // TUYA_MCU_FUNC_DIMMER - Move Tuya Dimmer Id to Map
        Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_BACKLOG_DELAY];
        tuyaindex++;
      } else if (Settings.flag3.fast_power_cycle_disable == 1) {  // ex SetOption65
        Settings.tuya_fnid_map[tuyaindex].fnid = 11;         // TUYA_MCU_FUNC_REL1 - Create FnID for Switches
        Settings.tuya_fnid_map[tuyaindex].dpid = 1;
        tuyaindex++;
      }
      if (Settings.param[P_ex_TUYA_RELAYS] > 0) {
        for (uint8_t i = 0 ; i < Settings.param[P_ex_TUYA_RELAYS]; i++) {  // ex SetOption41
          Settings.tuya_fnid_map[tuyaindex].fnid = 12 + i;   // TUYA_MCU_FUNC_REL2 -  Create FnID for Switches
          Settings.tuya_fnid_map[tuyaindex].dpid = i + 2;
          tuyaindex++;
        }
      }
      if (Settings.param[P_ex_TUYA_POWER_ID] > 0) {          // ex SetOption46
        Settings.tuya_fnid_map[tuyaindex].fnid = 31;         // TUYA_MCU_FUNC_POWER -  Move Tuya Power Id to Map
        Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_POWER_ID];
        tuyaindex++;
      }
      if (Settings.param[P_ex_TUYA_VOLTAGE_ID] > 0) {        // ex SetOption44
        Settings.tuya_fnid_map[tuyaindex].fnid = 33;         // TUYA_MCU_FUNC_VOLTAGE - Move Tuya Voltage Id to Map
        Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_VOLTAGE_ID];
        tuyaindex++;
      }
      if (Settings.param[P_ex_TUYA_CURRENT_ID] > 0) {        // ex SetOption45
        Settings.tuya_fnid_map[tuyaindex].fnid = 32;         // TUYA_MCU_FUNC_CURRENT - Move Tuya Current Id to Map
        Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_CURRENT_ID];
      }
    }
    if (Settings.version < 0x0606000C) {
      memset((char*)&Settings +0x1D6, 0x00, 16);
    }
    if (Settings.version < 0x0606000F) {
      Settings.ex_shutter_accuracy = 0;
      Settings.ex_mqttlog_level = MQTT_LOG_LEVEL;
    }
    if (Settings.version < 0x06060011) {
      Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY;
    }
    if (Settings.version < 0x06060012) {
      Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN;
      Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX;
      if (TUYA_DIMMER == Settings.module) {
        if (Settings.flag3.ex_tuya_dimmer_min_limit) {
          Settings.dimmer_hw_min = 25;
        } else {
          Settings.dimmer_hw_min = 1;
        }
        Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX];
      }
      else if (PS_16_DZ == Settings.module) {
        Settings.dimmer_hw_min = 10;
        Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX];
      }
    }
    if (Settings.version < 0x06060014) {
      // Clear unused parameters for future use
/*
      Settings.flag3.tuya_serial_mqtt_publish = 0;  // ex Settings.flag3.ex_tuya_dimmer_range_255
      Settings.flag3.ex_tuya_dimmer_min_limit = 0;
      Settings.param[P_ex_TUYA_RELAYS] = 0;
      Settings.param[P_ex_DIMMER_MAX] = 0;
      Settings.param[P_ex_TUYA_VOLTAGE_ID] = 0;
      Settings.param[P_ex_TUYA_CURRENT_ID] = 0;
      Settings.param[P_ex_TUYA_POWER_ID] = 0;
      Settings.ex_baudrate = 0;
      Settings.ex_sbaudrate = 0;
*/
      Settings.flag3.fast_power_cycle_disable = 0;
      Settings.energy_power_delta = Settings.ex_energy_power_delta;
      Settings.ex_energy_power_delta = 0;
    }
    if (Settings.version < 0x06060015) {
      if ((EX_WIFI_SMARTCONFIG == Settings.ex_sta_config) || (EX_WIFI_WPSCONFIG == Settings.ex_sta_config)) {
        Settings.ex_sta_config = WIFI_MANAGER;
      }
    }

    if (Settings.version < 0x07000002) {
      Settings.web_color2[0][0] = Settings.web_color[0][0];
      Settings.web_color2[0][1] = Settings.web_color[0][1];
      Settings.web_color2[0][2] = Settings.web_color[0][2];
    }
    if (Settings.version < 0x07000003) {
      SettingsEnableAllI2cDrivers();
    }
    if (Settings.version < 0x07000004) {
      Settings.ex_wifi_output_power = 170;
    }
    if (Settings.version < 0x07010202) {
      Settings.ex_serial_config = TS_SERIAL_8N1;
    }
    if (Settings.version < 0x07010204) {
      if (Settings.flag3.ex_cors_enabled == 1) {
        strlcpy(Settings.ex_cors_domain, CORS_ENABLED_ALL, sizeof(Settings.ex_cors_domain));
      } else {
        Settings.ex_cors_domain[0] = 0;
      }
    }
    if (Settings.version < 0x07010205) {
      Settings.seriallog_level = Settings.ex_seriallog_level;  // 09E -> 452
      Settings.sta_config = Settings.ex_sta_config;            // 09F -> EC7
      Settings.sta_active = Settings.ex_sta_active;            // 0A0 -> EC8
      memcpy((char*)&Settings.rule_stop, (char*)&Settings.ex_rule_stop, 47);  // 1A7 -> EC9
    }
    if (Settings.version < 0x07010206) {
      Settings.flag4 = Settings.ex_flag4;                      // 1E0 -> EF8
      Settings.mqtt_port = Settings.ex_mqtt_port;              // 20A -> EFC
      memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5);  // 1E4 -> EFE
    }

    if (Settings.version < 0x08000000) {
      char temp[strlen(Settings.text_pool) +1];           strncpy(temp, Settings.text_pool, sizeof(temp));  // Was ota_url
      char temp21[strlen(Settings.ex_mqtt_prefix[0]) +1]; strncpy(temp21, Settings.ex_mqtt_prefix[0], sizeof(temp21));
      char temp22[strlen(Settings.ex_mqtt_prefix[1]) +1]; strncpy(temp22, Settings.ex_mqtt_prefix[1], sizeof(temp22));
      char temp23[strlen(Settings.ex_mqtt_prefix[2]) +1]; strncpy(temp23, Settings.ex_mqtt_prefix[2], sizeof(temp23));
      char temp31[strlen(Settings.ex_sta_ssid[0]) +1];    strncpy(temp31, Settings.ex_sta_ssid[0], sizeof(temp31));
      char temp32[strlen(Settings.ex_sta_ssid[1]) +1];    strncpy(temp32, Settings.ex_sta_ssid[1], sizeof(temp32));
      char temp41[strlen(Settings.ex_sta_pwd[0]) +1];     strncpy(temp41, Settings.ex_sta_pwd[0], sizeof(temp41));
      char temp42[strlen(Settings.ex_sta_pwd[1]) +1];     strncpy(temp42, Settings.ex_sta_pwd[1], sizeof(temp42));
      char temp5[strlen(Settings.ex_hostname) +1];        strncpy(temp5, Settings.ex_hostname, sizeof(temp5));
      char temp6[strlen(Settings.ex_syslog_host) +1];     strncpy(temp6, Settings.ex_syslog_host, sizeof(temp6));
      char temp7[strlen(Settings.ex_mqtt_host) +1];       strncpy(temp7, Settings.ex_mqtt_host, sizeof(temp7));
      char temp8[strlen(Settings.ex_mqtt_client) +1];     strncpy(temp8, Settings.ex_mqtt_client, sizeof(temp8));
      char temp9[strlen(Settings.ex_mqtt_user) +1];       strncpy(temp9, Settings.ex_mqtt_user, sizeof(temp9));
      char temp10[strlen(Settings.ex_mqtt_pwd) +1];       strncpy(temp10, Settings.ex_mqtt_pwd, sizeof(temp10));
      char temp11[strlen(Settings.ex_mqtt_topic) +1];     strncpy(temp11, Settings.ex_mqtt_topic, sizeof(temp11));
      char temp12[strlen(Settings.ex_button_topic) +1];   strncpy(temp12, Settings.ex_button_topic, sizeof(temp12));
      char temp13[strlen(Settings.ex_mqtt_grptopic) +1];  strncpy(temp13, Settings.ex_mqtt_grptopic, sizeof(temp13));

      memset(Settings.text_pool, 0x00, settings_text_size);
      SettingsUpdateText(SET_OTAURL, temp);
      SettingsUpdateText(SET_MQTTPREFIX1, temp21);
      SettingsUpdateText(SET_MQTTPREFIX2, temp22);
      SettingsUpdateText(SET_MQTTPREFIX3, temp23);
      SettingsUpdateText(SET_STASSID1, temp31);
      SettingsUpdateText(SET_STASSID2, temp32);
      SettingsUpdateText(SET_STAPWD1, temp41);
      SettingsUpdateText(SET_STAPWD2, temp42);
      SettingsUpdateText(SET_HOSTNAME, temp5);
      SettingsUpdateText(SET_SYSLOG_HOST, temp6);
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
      if (!strlen(Settings.ex_mqtt_user)) {
        SettingsUpdateText(SET_MQTT_HOST, temp7);
        SettingsUpdateText(SET_MQTT_USER, temp9);
      } else {
        char aws_mqtt_host[66];
        snprintf_P(aws_mqtt_host, sizeof(aws_mqtt_host), PSTR("%s%s"), temp9, temp7);
        SettingsUpdateText(SET_MQTT_HOST, aws_mqtt_host);
        SettingsUpdateText(SET_MQTT_USER, "");
      }
#else
      SettingsUpdateText(SET_MQTT_HOST, temp7);
      SettingsUpdateText(SET_MQTT_USER, temp9);
#endif
      SettingsUpdateText(SET_MQTT_CLIENT, temp8);
      SettingsUpdateText(SET_MQTT_PWD, temp10);
      SettingsUpdateText(SET_MQTT_TOPIC, temp11);
      SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, temp12);
      SettingsUpdateText(SET_MQTT_GRP_TOPIC, temp13);

      SettingsUpdateText(SET_WEBPWD, Settings.ex_web_password);
      SettingsUpdateText(SET_CORS, Settings.ex_cors_domain);
      SettingsUpdateText(SET_MQTT_FULLTOPIC, Settings.ex_mqtt_fulltopic);
      SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, Settings.ex_switch_topic);
      SettingsUpdateText(SET_STATE_TXT1, Settings.ex_state_text[0]);
      SettingsUpdateText(SET_STATE_TXT2, Settings.ex_state_text[1]);
      SettingsUpdateText(SET_STATE_TXT3, Settings.ex_state_text[2]);
      SettingsUpdateText(SET_STATE_TXT4, Settings.ex_state_text[3]);
      SettingsUpdateText(SET_NTPSERVER1, Settings.ex_ntp_server[0]);
      SettingsUpdateText(SET_NTPSERVER2, Settings.ex_ntp_server[1]);
      SettingsUpdateText(SET_NTPSERVER3, Settings.ex_ntp_server[2]);
      SettingsUpdateText(SET_MEM1, Settings.script_pram[0]);
      SettingsUpdateText(SET_MEM2, Settings.script_pram[1]);
      SettingsUpdateText(SET_MEM3, Settings.script_pram[2]);
      SettingsUpdateText(SET_MEM4, Settings.script_pram[3]);
      SettingsUpdateText(SET_MEM5, Settings.script_pram[4]);
      SettingsUpdateText(SET_FRIENDLYNAME1, Settings.ex_friendlyname[0]);
      SettingsUpdateText(SET_FRIENDLYNAME2, Settings.ex_friendlyname[1]);
      SettingsUpdateText(SET_FRIENDLYNAME3, Settings.ex_friendlyname[2]);
      SettingsUpdateText(SET_FRIENDLYNAME4, Settings.ex_friendlyname[3]);
    }

    Settings.version = VERSION;
    SettingsSave(1);
  }
}
