// The below sketch and implementation is based on:


/*********************************************************************************
 *  MIT License
 *
 *  Copyright (c) 2020-2022 Gregg E. Berman
 *
 *  https://github.com/HomeSpan/HomeSpan
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 *
 ********************************************************************************/

/*
  TODO:
  - change: void change_mac() to bool change_mac() - true on success (or not)
  - all configuration in Preferences (or JSON?)
  - revert OTA if device is failing 
*/


// #define DEBUG

#include <Arduino.h>
#include "devices_config.h"
#include "passwords.h"              // firmware host



// ****************  EVERYTHONG BELOW ALL IS COMMON FOR ANY ESP32 *********************
// if no specific in devices_config.h
#ifndef CHECK_FW_BOOTCOUNTS
  #define CHECK_FW_BOOTCOUNTS         2000 // 2016 = 7 days,   288   // (every 5min = 12/h = 288/day => 288)
#endif 

#ifndef CHECK_FW_ON_BOOT
  #define CHECK_FW_ON_BOOT            0   // 1 - check FW every restart, 0 - check only every CHECK_FW_BOOTCOUNTS
#endif
// if no specific in devices_config.h END

#define ENCRYPT_ESPNOW              0   // if 1 encryption is ON and limited number of clients is 6, with 0/off it is 20
#define WAIT_FOR_WIFI               10  // in seconds, for upgrade firmware
#define uS_TO_S_FACTOR              1000000ULL
#define COMPILATION_TIME            (__DATE__ ", " __TIME__)
#define BRIDGE                      "1A:FF:01:01:01:01" // bridge fake MAC

#define CP_TIMEOUT_S                180 //180 // 300  // CP and AP will terminate after this time
#define SLEEP_TIME_H_BATTERY_EMPTY  24  // sleep hours when battery empty
#define MAX17048_DELAY_ON_RESET_MS  200 // as per datasheet: needed before next reading from MAX17048 after reset, only in use when reset/battery change
#define DEBOUNCE_MS                 200 // wait time after pressing FW GPIO
#define WAIT_FOR_HTTP_S             5  // wait time in seconds for http response 200 or timeout

// MAX17048 - battery fuel gauge, I2C
#if (USE_MAX17048 == 1)
  #include <SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library.h>
  SFE_MAX1704X lipo(MAX1704X_MAX17048);
  bool max17048ready = false;
  void get_battery(float *volts, uint8_t *prct);
  bool prepare_max17048();
  #include "measure-battery.h"
#endif

// sht31 - temperature and humidity, I2C
#if (USE_SHT31 == 1)
  #include "Adafruit_SHT31.h"
  void get_temp_hum(float *temperature, float *humidity);
  #include "measure-temp-hum.h"
#endif

// lux from TSL2561 - light sensor, I2C
#if (USE_TSL2561 == 1)
  #include <Wire.h>
  #include <Adafruit_Sensor.h>
  #include <Adafruit_TSL2561_U.h>
  void get_lux(float *lux);
  #include "measure-lux.h"
#endif

// check charging
#if defined(POWER_GPIO) and defined(CHARGING_GPIO)
  u_int8_t get_charging();
  #include "check-charging.h"
#endif

/*  DONE TILL HERE  */

// other libraries and variables
#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>
#include <Wire.h>

#include "HomeSpan.h"
SpanPoint *mainDevice;

typedef struct struct_message          // old: 44 bytes, new: 152 bytes - POSITION MATTER - NOT NAME OF VARIABLE!
{
  float md_temp;
  float md_hum;
  float md_light;
  uint8_t md_bat;
  char md_version[20];
  uint8_t md_mcu_model;
  uint8_t md_charging;
  uint8_t md_boot_reason;
  uint8_t md_wakeup_reason;
  unsigned long md_bootcount;

  // added for future use:
  // uint8_t var1;     // use for wifi_ok
  uint8_t wifi_ok;
  uint8_t var2;
  uint8_t var3;
  uint8_t var4;
  uint8_t var5;
  char var6[10];
  char var7[22];
  char var8[32];
  float md_bat_volts;
  float var10;
  float var11;
  float var12;
  float var13;
  uint32_t var14;
  uint32_t var15;
  uint32_t var16;
  uint32_t var17;

} struct_message;

struct_message myData;

// for MAC manipulation and FW update:
char mac_org_char[18];
uint8_t mac_org[6];
char mac_new_char[18];
uint8_t mac_new[6];

char fw_version[20];
char serial_number[20];

char fw_ver_on_server[50];

// -----------------------------------------------FIRMWARE UPDATE
#include <HTTPClient.h>
#include <Update.h>
HTTPClient firmware_update_client;
int fw_totalLength = 0;
int fw_currentLength = 0;
bool perform_update_firmware=false;
int update_progress=0;
int old_update_progress=0;
bool blink_led_status=false;
bool fw_update = false;
// -----------------------------------------------FIRMWARE UPDATE END

// auxuliary variables:
// sleep:
unsigned long start_time, end_time, work_time;
// tasks
TaskHandle_t led_blink_handle = NULL;
BaseType_t xReturned_led_blink;
// timers
#include "freertos/timers.h"
TimerHandle_t cp_timer_handle = NULL;
int id = 1;

// check reasons to wake up and start
esp_sleep_wakeup_cause_t wakeup_reason;
esp_reset_reason_t boot_reason; // esp_reset_reason_t ???
uint64_t wakeup_gpio_mask;
byte wakeup_gpio;

// charging
uint8_t charging_int = 0;          // 0-NC,  1-ON, 2-FULL,   // 3-off ??
const char charging_states[3][5] = {"NC", "ON", "FULL"};  // "OFF"};


unsigned long bootCount;

// declarations
void sos(int led);
void do_update();
void updateFirmware(uint8_t *data, size_t len);
int update_firmware_prepare();
void hibernate(bool force, int final_sleeping_time_s);
void do_esp_restart();
void led_blink(void *pvParams);
void cp_timer( TimerHandle_t xTimer );

void do_esp_sleep();
void save_boot_count();

void change_mac();
void make_serial_number(char *org_mac, char*new_mac, char *buff);
void make_fw_version(const char *fw_numeric, char *fw_with_date_time);

bool check_new_fw_on_server(char *firmware_path, char *new_firmware_version); 
bool compare_firmwares(char *current_firmware, char *new_firmware);
bool periodic_firmware_check();


// ----------------------------- CAPTIVE PORTAL and WIFI
#include <nvs_flash.h>
#include <DNSServer.h>
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include <Preferences.h>

void connect_wifi();
void setupServer();
void WiFiSoftAPSetup();
void WiFiStationSetup(String rec_ssid, String rec_password, String rec_channel, String rec_sleep_s);
void StartCaptivePortal();
void reset_wifi_credentials();

// new
void read_wifi_credentials();
void store_old_wifi_credentials();
bool print_on = false; 

Preferences preferences;
DNSServer dnsServer;
AsyncWebServer server(80);

String ssid, old_ssid;
String password,old_password;
String channel,old_channel;
String sleeptime_s_str,old_sleeptime_s_str;
bool is_setup_done = false;
bool valid_ssid_received = false;
bool valid_channel_received = false;
bool valid_password_received = false;
bool valid_sleeptime_s_str_received = false;
bool wifi_timeout = false;
int sleeptime_s = SLEEP_TIME_S;

String cp_html_page;

class CaptiveRequestHandler : public AsyncWebHandler {
  public:
    CaptiveRequestHandler() {}
    virtual ~CaptiveRequestHandler() {}

    bool canHandle(AsyncWebServerRequest *request) {
      //request->addInterestingHeader("ANY");
      return true;
    }

    void handleRequest(AsyncWebServerRequest *request) {
cp_html_page  =
"<!DOCTYPE HTML><html><head>\
<title>DEVICE PROVISIONING PORTAL</title>\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
</head>\
<style>\
#timeout {\
background-color: lightblue;\
color: red;\
padding: 4px;\
text-align: center;\
white-space: nowrap;\
}\
</style>\
<body>\
<h3>DEVICE PROVISIONING PORTAL</h3><h4><p id=\"timeout\"></h4>\
<h4>Device data:</h4>\
Name:&nbsp<i><b>";
cp_html_page += HOSTNAME;
cp_html_page += "</b></i><br>Type:&nbsp<i><b>";
cp_html_page += MODEL;
cp_html_page += "</b></i><br>Firmware version:&nbsp<i><b>";
cp_html_page += fw_version;
cp_html_page += "</b></i><br>Serial number:&nbsp<i><b>";
cp_html_page += serial_number;
cp_html_page += "</b></i><br>Orginal MAC address:&nbsp<i><b>";
cp_html_page += mac_org_char;
cp_html_page += "</b></i><br>Provisioned MAC address:&nbsp<i><b>";
cp_html_page += mac_new_char;
cp_html_page += "</b></i><br>Bridge MAC address:&nbsp<i><b>";
cp_html_page += BRIDGE;
cp_html_page += "</b></i>\
<br><h4>Configuration data:</h4>\
<form action=\"/get\">\
<b>SSID</b> (<=32 characters): <input type=\"text\" name=\"ssid\" maxlength=\"32\" size=\"32\" value=\"";
cp_html_page += old_ssid;
cp_html_page += "\"\"><br>\
<b>Password</b> (8-63 characters): <input type=\"password\" name=\"password\" id=\"password\" maxlength=\"63\" size=\"63\" value=\"";
cp_html_page += old_password;
cp_html_page += "\"\">\
<input type=\"checkbox\" onclick=\"myFunction()\">Show Password\
<script>\
function myFunction() {\
var x = document.getElementById(\"password\");\
if (x.type === \"password\") {\
x.type = \"text\";\
} else {\
x.type = \"password\";\
}\
}\
</script>\
<br>\
<b>Channel</b> [1-13] - leave empty if Access Point/Router is set to Auto-Channel (or if you are not sure): <input type=\"text\" name=\"channel\" maxlength=\"2\" size=\"2\" value=\"";
cp_html_page += old_channel;
cp_html_page += "\"\">  <br>\
<b>Sleep time</b> in seconds [1-3600] (if wrong value entered, default is 300s=5min) <input type=\"text\" name=\"sleeptime_s_str\" maxlength=\"4\" size=\"4\" value=\"";
cp_html_page += old_sleeptime_s_str;
cp_html_page += "\"\"><br>\
<input type=\"submit\" value=\"Submit\" style=\"border: none; height:30px; width:100px font-size:50px\">\
</form>\
<br>If you don't press \"Submit\" button before timeout, all configuration data will be reset to default values and <b>WiFi credentials will be erased</b></br>\
<script>\
var countDownDate = new Date().getTime() + (";
cp_html_page += CP_TIMEOUT_S;
cp_html_page += "*1000);\
var x = setInterval(function() {\
var now = new Date().getTime();\
var distance = countDownDate - now;\
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));\
var seconds = Math.floor((distance % (1000 * 60)) / 1000);\
document.getElementById(\"timeout\").innerHTML = \"Timeout: \" + minutes + \"m \" + seconds + \"s \";\
if (distance < 0) {\
clearInterval(x);\
document.getElementById(\"demo\").innerHTML = \"EXPIRED\";\
}\
}, 500);\
</script>\
</body></html>";

      // Serial.println(cp_html_page);
      const char *index_html =cp_html_page.c_str();
      request->send_P(200, "text/html", index_html);
    }
};

void reset_wifi_credentials()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif

  preferences.begin("wifi-secrets", false);     // read/write
  preferences.clear();
  preferences.end();
  delay(10);
}

void setupServer()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request)
  {
    const char *index_html = cp_html_page.c_str();
    request->send_P(200, "text/html", index_html);
    if (print_on) Serial.println("Client Connected");
  });
// Serial.printf("[%s]: Client Connected\n",__func__);
  server.on("/get", HTTP_GET, [] (AsyncWebServerRequest * request)
  {
    String inputMessage;
    String inputParam;

    if (request->hasParam("ssid"))
    {
      inputMessage = request->getParam("ssid")->value();
      inputParam = "ssid";
      ssid = inputMessage;
      if (print_on) Serial.printf("[%s]: SSID received: %s\n",__func__,ssid);
      if (strlen(ssid.c_str()) == 0)
      {
        ssid = old_ssid;
        if (print_on) Serial.printf("[%s]: SSID is blank, reusing the old value: %s\n",__func__,ssid);
      } else
      {
        ssid = inputMessage;
      }
      valid_ssid_received = true;
    }

    if (request->hasParam("password"))
    {
      inputMessage = request->getParam("password")->value();
      inputParam = "password";
      password = inputMessage;
      if (print_on) Serial.printf("[%s]: password received: ",__func__); 
      if (print_on) Serial.println(password);
      if (strlen(password.c_str()) == 0)
      {
        password = old_password;
        if (print_on) Serial.printf("[%s]: password is blank, reusing the old value: ",__func__); 
        if (print_on) Serial.println(password);
      } else
      {
        password = inputMessage;
      }
      valid_password_received = true;
    }

    if (request->hasParam("channel"))
    {
      inputMessage = request->getParam("channel")->value();
      inputParam = "channel";
      channel = inputMessage;
      if (print_on) Serial.printf("[%s]: channel received: %s\n",__func__,channel);
      if (strlen(channel.c_str()) == 0)
      {
        channel = old_channel;
        if (print_on) Serial.printf("[%s]: channel is blank, reusing the old value: %s\n",__func__,channel);
      } else
      {
        channel = inputMessage;
      }
      valid_channel_received = true;
    }

    if (request->hasParam("sleeptime_s_str"))
    {
      inputMessage = request->getParam("sleeptime_s_str")->value();
      inputParam = "sleeptime_s_str";
      sleeptime_s_str = inputMessage;
      if (print_on) Serial.printf("[%s]: sleeptime_s_str received: %s\n",__func__,sleeptime_s_str);
      if (strlen(sleeptime_s_str.c_str()) == 0)
      {
        sleeptime_s_str = old_sleeptime_s_str;
        if (print_on) Serial.printf("[%s]: sleeptime_s_str is blank, reusing the old value: %s\n",__func__,sleeptime_s_str);
      } else
      {
        sleeptime_s_str = inputMessage;
      }
      valid_sleeptime_s_str_received = true;
    }

    request->send(200, "text/html", "The values entered by you have been successfully sent to the device. It will now attempt WiFi connection");
  });
}

void WiFiSoftAPSetup()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  char cp_ssid[32];
  snprintf(cp_ssid,sizeof(cp_ssid),"%s",HOSTNAME);
  WiFi.mode(WIFI_AP);
  WiFi.softAP(cp_ssid);
  if (print_on) Serial.printf("[%s]: AP IP address: ",__func__);
  if (print_on) Serial.println(WiFi.softAPIP());
  if (print_on) Serial.printf("[%s]: SSID: %s\n",__func__,cp_ssid);
  if (print_on) Serial.printf("[%s]: NO PASSWORD\n",__func__);
}

void WiFiStationSetup(String rec_ssid, String rec_password, String rec_channel, String rec_sleep_s)
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  wifi_timeout = false;
  // WiFi.mode(WIFI_STA);

  wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
  esp_wifi_init(&config);
  esp_wifi_set_ps(WIFI_PS_NONE);
  esp_wifi_set_mode(WIFI_MODE_STA);
  esp_wifi_set_promiscuous(0);
  WiFi.mode(WIFI_STA);

  char ssid_arr[33];
  char password_arr[65];
  int channel;
  int sleeptime_s;
  rec_ssid.toCharArray(ssid_arr, rec_ssid.length() + 1);
  rec_password.toCharArray(password_arr, rec_password.length() + 1);

  channel = rec_channel.toInt();
  sleeptime_s = rec_sleep_s.toInt();

  #ifdef DEBUG
    if (print_on) Serial.printf("[%s]: Received SSID: %s, password: %s, channel: %d, sleeping time: %d \n",__func__,ssid_arr,password_arr, channel, sleeptime_s);
  #endif
  if ((channel > 0) and (channel < 14))
  {
     if (print_on) Serial.printf("[%s]: Valid fixed channel: %d\n", __func__,channel);
  } else
  {
    if (print_on) Serial.printf("[%s]: NOT valid fixed channel: %d\n", __func__,channel);
  }
  if ((sleeptime_s > 0) and (sleeptime_s <= 3600))
  {
     if (print_on) Serial.printf("[%s]: Valid sleeptime: %d seconds\n", __func__,sleeptime_s);
  } else
  {
    if (print_on) Serial.printf("[%s]: NOT valid sleeptime: %d, using default: %d seconds\n", __func__,sleeptime_s, (SLEEP_TIME_S));
  }
  WiFi.begin(ssid_arr, password_arr);
  if (print_on) Serial.printf("[%s]: Connecting...\n",__func__);
  uint32_t t1 = millis();
  while (WiFi.status() != WL_CONNECTED) 
  {
    delay(100);
    if (print_on) Serial.print(".");
    if (millis() - t1 > WAIT_FOR_WIFI * 1000)
    {
      if (print_on) Serial.println();
      if (print_on) Serial.printf("\n[%s]: Timeout connecting to WiFi. The SSID or Password seem incorrect\n",__func__);
      valid_ssid_received = false;
      valid_password_received = false;
      valid_channel_received = false;
      valid_sleeptime_s_str_received = false;
      is_setup_done = false;
      StartCaptivePortal();
      wifi_timeout = true;
      break;
    }
  }
  if (!wifi_timeout)
  {
    if (print_on) Serial.printf("\n[%s]: WiFi connected to: %s\n",__func__,rec_ssid);
    valid_ssid_received = false;
    if (print_on) Serial.printf("[%s]: STA IP address: ",__func__);
    if (print_on) Serial.println(WiFi.localIP());

    is_setup_done = true;
    old_ssid = rec_ssid;
    old_channel = rec_channel;
    old_password = rec_password;
    old_sleeptime_s_str = rec_sleep_s;

    if( led_blink_handle != NULL )
    {
      if (print_on) Serial.printf("[%s]: Disabling blinking LEDs\n",__func__);
      vTaskDelete( led_blink_handle );
      delay(5);
    } else
    {
      if (print_on) Serial.printf("[%s]: LEDs still bllinking or were never blinking\n",__func__);
    }
    if (print_on) Serial.printf("[%s]: Done\n",__func__);
  }
}

void StartCaptivePortal() 
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  // blink LEDs in task, if not yet blinking
  #if ((defined (ACT_BLUE_LED_GPIO)) or (defined (ERROR_RED_LED_GPIO)))
    if (led_blink_handle == NULL)
    {
      xReturned_led_blink = xTaskCreatePinnedToCore(led_blink, "led_blink", 2000, NULL, 1, &led_blink_handle, CONFIG_ARDUINO_RUNNING_CORE);
      if( xReturned_led_blink != pdPASS )
      {
        if (print_on) Serial.printf("[%s]: CANNOT create led_blink task\n",__func__);
      } else
      {
        #ifdef DEBUG
          if (print_on) Serial.printf("[%s]: Task led_blink created\n",__func__);
        #endif
      }
    } else
    // blink LEDs already blinking
    {
      if (print_on) Serial.printf("[%s]: Task led_blink ALREADY created\n",__func__);
    }
  #endif

  // create CP timer if not yet created
  if (cp_timer_handle  == NULL)
  {
    cp_timer_handle = xTimerCreate("MyTimer", pdMS_TO_TICKS(CP_TIMEOUT_S * 1000), pdTRUE, ( void * )id, &cp_timer);
    if( xTimerStart(cp_timer_handle, 10 ) != pdPASS )
    {
      if (print_on) Serial.printf("[%s]: CP timer start error\n",__func__);
    } else
    {
      #ifdef DEBUG
        if (print_on) Serial.printf("[%s]: CP timer STARTED\n",__func__);
      #endif
    }
  } else
  // CP timer created so restart it
  {
    if( xTimerReset( cp_timer_handle, 0 ) != pdPASS )
    {
      if (print_on) Serial.printf("[%s]: CP timer was not reset\n",__func__);
    } else
    {
      if (print_on) Serial.printf("[%s]: CP timer RE-STARTED\n",__func__);
    }
  }

  if (print_on) Serial.printf("[%s]: Setting up AP Mode\n",__func__);
  WiFiSoftAPSetup();
  if (print_on) Serial.printf("[%s]: Setting up Async WebServer\n",__func__);
  setupServer();
  if (print_on) Serial.printf("[%s]: Starting DNS Server\n",__func__);
  dnsServer.start(53, "*", WiFi.softAPIP());
  server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
  server.begin();
  dnsServer.processNextRequest();
  Serial.printf("[%s]: Captive Portal and AP created, timeout set to %ds\n",__func__,CP_TIMEOUT_S);
}

// CONNECT WIFI USING CAPTIVE PORTAL
void connect_wifi()
{
  Serial.printf("[%s]: Starting WiFi connection...\n",__func__);
  preferences.begin("wifi-secrets", false);       // write
  is_setup_done = preferences.getBool("is_setup_done", false);
  ssid = preferences.getString("rec_ssid", "Sample_SSID");
  channel = preferences.getString("rec_channel", "1");
  password = preferences.getString("rec_password", "abcdefgh");
  sleeptime_s_str = preferences.getString("rec_sleep_s", "5");

  // TESTING
  // if (!is_setup_done)
  // {
  //   StartCaptivePortal();
  // }
  // else
  // {
    if (!is_setup_done) Serial.printf("[%s]: previous connection not done but trying anyway...\n",__func__);
    if (print_on) Serial.printf("[%s]: Using saved SSID and PASS to attempt WiFi Connection...\n",__func__);
    if (print_on) Serial.printf("[%s]: SSID: ",__func__); if (print_on) Serial.println(ssid);
    if (print_on) Serial.printf("[%s]: PASS: ",__func__); if (print_on) Serial.print(password[0]);if (print_on) Serial.println("***");
    WiFiStationSetup(ssid, password, channel, sleeptime_s_str);
  // }
  // TESTING END

  while (!is_setup_done)
  {
    dnsServer.processNextRequest();
    delay(10);
    if (valid_ssid_received && valid_password_received)
    {
      if (print_on) Serial.printf("[%s]: Attempting WiFi Connection with: ssid=%s, channel=%s, sleeptime=%s, passwd=",__func__,ssid, channel, sleeptime_s_str);Serial.println(password);
      WiFiStationSetup(ssid, password, channel, sleeptime_s_str);
    }
  }
  if (print_on) Serial.printf("[%s]: Done\n",__func__);
}
// CAPTIVE PORTAL END

// new:
void read_wifi_credentials()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif

  preferences.begin("wifi-secrets", true);    // read only

  is_setup_done = preferences.getBool("is_setup_done", false);
  old_ssid = preferences.getString("rec_ssid", "Sample_SSID");
  old_channel = preferences.getString("rec_channel", "1");
  old_password = preferences.getString("rec_password", "abcdefgh");
  old_sleeptime_s_str = preferences.getString("rec_sleep_s", "5");

  #ifdef DEBUG
    Serial.printf("[%s]: ssid:            %s\n",__func__,old_ssid);
    Serial.printf("[%s]: channel:         %s\n",__func__,old_channel);
    Serial.printf("[%s]: password:        ",__func__); Serial.print(old_password[0]); Serial.println("****");//Serial.println(old_password);
    Serial.printf("[%s]: sleeptime_s_str: %s\n",__func__,old_sleeptime_s_str);
    Serial.printf("[%s]: is_setup_done:   %d\n",__func__,is_setup_done);
  #endif

  preferences.end();
}

void store_old_wifi_credentials()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  


// compare current values (changed during program) with the orginals from preferences
  preferences.begin("wifi-secrets", true);    // read only

  bool infile_is_setup_done = preferences.getBool("is_setup_done", false);
  String infile_old_ssid = preferences.getString("rec_ssid", "Sample_SSID");
  String infile_old_channel = preferences.getString("rec_channel", "1");
  String infile_old_password = preferences.getString("rec_password", "abcdefgh");
  String infile_old_sleeptime_s_str = preferences.getString("rec_sleep_s", "5");

  #ifdef DEBUG
    Serial.printf("\n[%s]: Parameters from CURRENT run of the PROGRAM:\n",__func__);
    Serial.printf("[%s]: ssid:            %s\n",__func__,old_ssid);
    Serial.printf("[%s]: channel:         %s\n",__func__,old_channel);
    Serial.printf("[%s]: password:        ",__func__); Serial.print(old_password[0]); Serial.println("****");//Serial.println(old_password);
    Serial.printf("[%s]: sleeptime_s_str: %s\n",__func__,old_sleeptime_s_str);
    Serial.printf("[%s]: is_setup_done:   %d\n",__func__,is_setup_done);

    Serial.printf("\n[%s]: Parameters from FILE:\n",__func__);
    Serial.printf("[%s]: infile_old_ssid:            %s\n",__func__,infile_old_ssid);
    Serial.printf("[%s]: infile_old_channel:         %s\n",__func__,infile_old_channel);
    Serial.printf("[%s]: infile_old_password:        ",__func__); Serial.print(infile_old_password[0]); Serial.println("****");//Serial.println(old_password);
    Serial.printf("[%s]: infile_old_sleeptime_s_str: %s\n",__func__,infile_old_sleeptime_s_str);
    Serial.printf("[%s]: infile_is_setup_done:   %d\n\n",__func__,infile_is_setup_done);
  #endif


  preferences.end();
// compare current values (changed during program) with the orginals from preferences END


  if ((infile_is_setup_done == is_setup_done) and (infile_old_ssid == old_ssid) and (infile_old_password == old_password) and (infile_old_channel == old_channel) and (infile_old_sleeptime_s_str == old_sleeptime_s_str))
  {
    #ifdef DEBUG
      Serial.printf("[%s]: OLD parameters are EQUAL with new - exit without saving \n",__func__);
    #endif
    return;
  } else 
  {
    Serial.printf("[%s]: OLD parameters are NOT equal with new -  SAVING new ones \n",__func__);
  }

  preferences.begin("wifi-secrets", false);     // write
  preferences.putBool("is_setup_done", is_setup_done); 
  preferences.putString("rec_ssid", old_ssid);
  preferences.putString("rec_channel", old_channel);
  preferences.putString("rec_password", old_password);
  preferences.putString("rec_sleep_s", old_sleeptime_s_str);

  preferences.end();
  delay(10);  

}

// ----------------------------- CAPTIVE PORTAL and WIFI END


// blinking in rtos
void led_blink(void *pvParams)
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  int delay_ms = 60;
  #if defined(ERROR_RED_LED_GPIO) and defined(ACT_BLUE_LED_GPIO)
    while(1)
    {
      // both low
      digitalWrite(ERROR_RED_LED_GPIO,LOW);
      digitalWrite(ACT_BLUE_LED_GPIO,LOW);

      // delay
      vTaskDelay(delay_ms/portTICK_RATE_MS);

      // red high
      digitalWrite(ERROR_RED_LED_GPIO,HIGH);

      // delay
      vTaskDelay(delay_ms/portTICK_RATE_MS);

      // both low
      digitalWrite(ERROR_RED_LED_GPIO,LOW);
      digitalWrite(ACT_BLUE_LED_GPIO,LOW);

      // delay
      vTaskDelay(delay_ms/portTICK_RATE_MS);

      // blue high
      digitalWrite(ACT_BLUE_LED_GPIO,HIGH);

      // delay
      vTaskDelay(delay_ms/portTICK_RATE_MS);
    }
  #else
    #if defined (ERROR_RED_LED_GPIO) // and defined (ERROR_RED_LED_GPIO)
      while (1)
      {
        digitalWrite(ERROR_RED_LED_GPIO,LOW);
        vTaskDelay(delay_ms/portTICK_RATE_MS);
        digitalWrite(ERROR_RED_LED_GPIO,HIGH);
        vTaskDelay(delay_ms/portTICK_RATE_MS);
      }
    #else 
      #if defined (ACT_BLUE_LED_GPIO) // and defined (ERROR_RED_LED_GPIO)
      while (1)
      {
        digitalWrite(ERROR_RED_LED_GPIO,LOW);
        vTaskDelay(delay_ms/portTICK_RATE_MS);
        digitalWrite(ERROR_RED_LED_GPIO,HIGH);
        vTaskDelay(delay_ms/portTICK_RATE_MS);
      }
    #endif
  #endif  
  #endif
}
// blinking in rtos END

// blink nicely - SOS on upgrade failure
void sos(int led)
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  Serial.printf("[%s]: SOS...\n",__func__);
  #define DIT_MS 50;
  int dit = DIT_MS;
  int dah = 3 * dit;
  int inter_dit = dit;
  int inter_letter = 3 * dit;
  int inter_word = 7 * dit;

  digitalWrite(led,LOW);
  // delay(inter_word);

  // s
  for (byte i=0; i<3; i++)
  {
    digitalWrite(led,HIGH);
    delay(dit);
    digitalWrite(led,LOW);
    delay(dit);
  }
  delay(inter_letter);
  // 0
  for (byte i=0; i<3; i++)
  {
    digitalWrite(led,HIGH);
    delay(dah);
    digitalWrite(led,LOW);
    delay(dit);
  }
  delay(inter_letter);
  // s
  for (byte i=0; i<3; i++)
  {
    digitalWrite(led,HIGH);
    delay(dit);
    digitalWrite(led,LOW);
    delay(dit);
  }
  delay(inter_letter);
  Serial.printf("[%s]: SOS DONE\n",__func__);
}

// FW upgrade wrapper
void do_update()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  Serial.printf("[%s]: FW UPGRADE starting...\n",__func__);
  int update_firmware_status = -1;
  update_firmware_status=update_firmware_prepare();
  if (update_firmware_status == 0)
  {
    Serial.printf("[%s]: RESTARTING - FW update SUCCESSFULL\n\n",__func__);
    // blink slowly when FW upgrade successfull
    for (int i=0;i<3;i++)
    {
      #ifdef ACT_BLUE_LED_GPIO
        digitalWrite(ACT_BLUE_LED_GPIO,LOW);
        delay(100);
        digitalWrite(ACT_BLUE_LED_GPIO,HIGH);
        delay(30);
      #elif defined(ACT_BLUE_LED_GPIO)
        digitalWrite(ERROR_RED_LED_GPIO,LOW);
        delay(100);
        digitalWrite(ERROR_RED_LED_GPIO,HIGH);
        delay(30);
      #endif
    }

  } else
  {
    Serial.printf("[%s]: FW update failed - reason: %d\n",__func__,update_firmware_status);
    #ifdef ERROR_RED_LED_GPIO
      sos(ERROR_RED_LED_GPIO);
    #elif defined(ACT_BLUE_LED_GPIO)
      sos(ACT_BLUE_LED_GPIO);
    #endif
  }
  do_esp_restart();
}

// real upgrade
void updateFirmware(uint8_t *data, size_t len)
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  // blink ERROR_RED_LED_GPIO or...
  #ifdef ERROR_RED_LED_GPIO
    if (blink_led_status) {
      blink_led_status=LOW;
      digitalWrite(ERROR_RED_LED_GPIO,blink_led_status);
    } else {
      blink_led_status=HIGH;
      digitalWrite(ERROR_RED_LED_GPIO,blink_led_status);
    }
  #else
    // ...blink ACT_BLUE_LED_GPIO
    #ifdef ACT_BLUE_LED_GPIO
      if (blink_led_status) {
        blink_led_status=LOW;
        digitalWrite(ACT_BLUE_LED_GPIO,blink_led_status);
      } else {
        blink_led_status=HIGH;
        digitalWrite(ACT_BLUE_LED_GPIO,blink_led_status);
      }
    #endif
  #endif

  Update.write(data, len);
  fw_currentLength += len;
  old_update_progress=update_progress;
  update_progress=(fw_currentLength * 100) / fw_totalLength;
  if (update_progress>old_update_progress){
    if (update_progress % 5 == 0){ //it prints every 5%
      Serial.printf("[%s]: FW update: %d%%\n",__func__,update_progress);
    }
  }
  // if current length of written firmware is not equal to total firmware size, repeat
  if(fw_currentLength != fw_totalLength) return;
  Update.end(true);
  Serial.printf("\n[%s]: Update Success, Total Size: %d bytes\n",__func__,fw_currentLength);
}

// download from webserver
int update_firmware_prepare()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  char firmware_file[255];
  snprintf(firmware_file,sizeof(firmware_file),"%s/box%d.bin",UPDATE_FIRMWARE_HOST,DEVICE_ID);

  fw_totalLength=0;
  fw_currentLength=0;
  Serial.printf("[%s]: uploading file: %s\n",__func__,firmware_file);
  firmware_update_client.begin(firmware_file);
  int resp = firmware_update_client.GET();

  // testing http responses
  // Serial.printf("[%s]: initial Response: %d\n",__func__,resp);
  uint32_t started_millis = millis();
  while ((millis() <  (started_millis + (WAIT_FOR_HTTP_S * 1000))) and (resp != 200))
  {
    delay(500);
    resp = firmware_update_client.GET();
    #ifdef DEBUG
      Serial.printf("[%s]: next Response: %d\n",__func__,resp);
    #endif
  }
  // testing http responses END

  // If file is reachable, start downloading
  if(resp == 200)
  {
    // get length of the file (is -1 when Server sends no Content-Length header)
    fw_totalLength = firmware_update_client.getSize();
    if (fw_totalLength <= 0)
    {
      Serial.printf("[%s]: fw_totalLength < 0, FW UPDATE FAILED, exitting\n",__func__);
      return 1; // 0 is OK, any other is NOT OK
    }    
    // transfer to local variable
    int len = fw_totalLength;
    // this is required to start firmware update process
    Update.begin(UPDATE_SIZE_UNKNOWN);
    Serial.printf("[%s]: FW Size: %lu bytes\n",__func__,fw_totalLength);

    // create buffer for read
    uint8_t buff[128] = { 0 };
    // get tcp stream
    WiFiClient * stream = firmware_update_client.getStreamPtr();
    // read all data from server
    Serial.printf("[%s]: Updating firmware progress:\n",__func__);
    while(firmware_update_client.connected() && (len > 0 || len == -1))
    {
      // get available data size
      size_t size = stream->available();
      if(size) {
        // read up to 128 byte
        int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
        // pass to function
        updateFirmware(buff, c);
        if(len > 0) {
           len -= c;
        }
      }
      delay(1);
      }
  }else
  {
    Serial.printf("[%s]: Cannot download firmware file. Only HTTP response 200 is supported but received: %d\n",__func__,resp);
    Serial.printf("[%s]: firmware update prepare UNSUCESSFUL\n",__func__);
    // how about hibernate here?
    hibernate(false, sleeptime_s);
    // never here 
    return resp;
  }
  firmware_update_client.end();
  Serial.printf("[%s]: firmware update prepare SUCESSFULL\n",__func__);
  return 0;
}
// update firmware END

void hibernate(bool force, int final_sleeping_time_s) // force = true -> wake up only on timer, false->also on GPIO if defined
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  // wake up on timer...
  esp_sleep_enable_timer_wakeup(final_sleeping_time_s * uS_TO_S_FACTOR);

  esp_deep_sleep_disable_rom_logging();       // it does not display welcome message - shorter time to wake up
  esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL,         ESP_PD_OPTION_OFF);
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC8M,         ESP_PD_OPTION_OFF);
  esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO,       ESP_PD_OPTION_OFF);
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);

  //the next 2 lines are not working with ext0 but ok with ext1
  #if (BOARD_TYPE != 2) //GPIO error on S2
    esp_sleep_config_gpio_isolate();
  #endif
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH,   ESP_PD_OPTION_OFF);

  if (force) // used on error // used when battery too low - sleep for 1h to avoid battery depletion
  {
    Serial.printf("[%s]: FORCED HIBERNATION WITHOUT WAKE UP ON PIN PRESSED: going to sleep for %d seconds (heartbeat)\n",__func__, final_sleeping_time_s);
    // Serial.printf("[%s]: Bye...\n========= E N D =========\n",__func__);
    // esp_deep_sleep_start();
    do_esp_sleep();
    #ifdef ERROR_RED_LED_GPIO
      sos(ERROR_RED_LED_GPIO);
    #elif defined(ACT_BLUE_LED_GPIO)
      sos(ACT_BLUE_LED_GPIO);
    #endif
  }

  #ifdef DEBUG
    #ifdef FW_UPGRADE_GPIO
      Serial.printf("[%s]: FW_UPGRADE_GPIO (to wake up device)=%d\n",__func__,FW_UPGRADE_GPIO);
    #endif
  #endif
  uint64_t bitmask_dec;

// https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/ for ext0 and ext1 examples

  //... or on GPIO ext1 FW_UPGRADE_GPIO
  #ifdef FW_UPGRADE_GPIO //if FW_UPGRADE_GPIO  defined, wake up on it or on PIR - actually FW_UPGRADE_GPIO is obligatory
    bitmask_dec = pow(2,FW_UPGRADE_GPIO);
    #ifdef DEBUG
      Serial.printf("[%s]: bitmask_dec (FW_UPGRADE_GPIO) in dec=%ju\n",__func__,bitmask_dec);
    #endif
    // ESP32-C3
    #if (BOARD_TYPE == 4)
      {
        esp_err_t ret;
        #ifdef DEBUG
          Serial.printf("[%s]: BOARD_TYPE = 4 - applying ESP_GPIO_WAKEUP_GPIO_HIGH\n",__func__,bitmask_dec);
        #endif
        // active HIGH by default
        pinMode(FW_UPGRADE_GPIO, INPUT_PULLDOWN);
        ret = esp_deep_sleep_enable_gpio_wakeup(bitmask_dec, ESP_GPIO_WAKEUP_GPIO_HIGH);
        #ifdef DEBUG
          if (ret == ESP_OK)
            Serial.printf("[%s]: Wakup GPIO=%d activated (active HIGH)\n",__func__,FW_UPGRADE_GPIO);
          else 
            Serial.printf("[%s]: Wakup GPIO=%d NOT activated !!!\n",__func__,FW_UPGRADE_GPIO);
        #endif
      }
      /* new, needed, added 2023-12-05
      pinMode(FW_UPGRADE_GPIO, INPUT_PULLDOWN); or high
      esp_deep_sleep_enable_gpio_wakeup(1 << FW_UPGRADE_GPIO, ESP_GPIO_WAKEUP_GPIO_HIGH); - same like before but written in different way
      */
    #else
      {
        #ifdef DEBUG
          Serial.printf("[%s]: BOARD_TYPE != 4 - applying ESP_EXT1_WAKEUP_ANY_HIGH\n",__func__,bitmask_dec);
        #endif
        esp_sleep_enable_ext1_wakeup(bitmask_dec, ESP_EXT1_WAKEUP_ANY_HIGH);
      }
    #endif
  #endif
  Serial.printf("[%s]: going to sleep for %d seconds (heartbeat)\n",__func__, final_sleeping_time_s);



    // testing with PPK2 - go to sleep
  #ifdef PPK2_GPIO
    digitalWrite(PPK2_GPIO,HIGH);
  #endif

  #ifdef ACT_BLUE_LED_GPIO
    digitalWrite(ACT_BLUE_LED_GPIO,LOW);
  #endif
  do_esp_sleep();
}

void do_esp_restart()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  store_old_wifi_credentials();
  Serial.printf("[%s]: RESTARTING...\n",__func__);
  #ifdef ERROR_RED_LED_GPIO
    digitalWrite(ERROR_RED_LED_GPIO,LOW);
    delay(10);
    digitalWrite(ERROR_RED_LED_GPIO,HIGH);
    delay(200);
  #elif defined(ACT_BLUE_LED_GPIO)
    digitalWrite(ACT_BLUE_LED_GPIO,LOW);
    delay(10);
    digitalWrite(ACT_BLUE_LED_GPIO,HIGH);
    delay(200);
  #endif
  ESP.restart();
}

void cp_timer( TimerHandle_t cp_timer_handle )
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  Serial.printf("[%s]: Captive Portal timer expired\n",__func__);
  is_setup_done = false;
  // how about hibernate here?
  hibernate(false, sleeptime_s);
  // never here 
  do_esp_restart();
}

void do_esp_sleep()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  store_old_wifi_credentials();

  if (boot_reason != 8) // reason was NOT timer: reset or power on
  {
    end_time = millis();
    work_time = end_time - start_time + (ESP32_BOOT_TIME) + (ESP32_TAIL_TIME) + ESP32_BOOT_TIME_EXTRA;
  } else                // reason was TIMER (deepsleep) or FW GPIO
  {
    end_time = millis();
    work_time = end_time - start_time + (ESP32_BOOT_TIME) + (ESP32_TAIL_TIME);
  }
  #ifdef DEBUG
    Serial.printf("[%s]: start_time=%ums, end_time=%ums, work_time=%dms\n",__func__,start_time,end_time,work_time);
  #else
    Serial.printf("[%s]: Program finished in %dms\n",__func__,work_time);
  #endif

  save_boot_count();

  Serial.printf("========= E N D =========\n");
  esp_deep_sleep_start();
}

void save_boot_count()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  preferences.begin("wifi-secrets", false);     // write
  preferences.putString("bootcount",String(bootCount));
  preferences.end();
  delay(10);
  #ifdef DEBUG
    Serial.printf("[%s]: SAVED Bootcount=%d\n",__func__,bootCount);
  #endif
}

// NEW:
void change_mac()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  WiFi.mode(WIFI_STA);
  WiFi.macAddress(mac_org);

  snprintf(mac_org_char, sizeof(mac_org_char), "%02x:%02x:%02x:%02x:%02x:%02x",mac_org[0], mac_org[1], mac_org[2], mac_org[3], mac_org[4], mac_org[5]);
  #ifdef DEBUG
    Serial.printf("[%s]: OLD MAC: %s\n",__func__,mac_org_char);
  #endif

  
  esp_err_t ret;
  ret = esp_wifi_set_mac(WIFI_IF_STA, &FixedMACAddress[0]);
  if (ret == ESP_OK) 
  {
    #ifdef DEBUG
      Serial.printf("[%s]: changing MAC SUCCESSFULL\n",__func__); 
    #endif
  }
  else 
  {
    Serial.printf("[%s]: changing MAC FAILED !!!\n",__func__); 
  }

  WiFi.macAddress(mac_new);
  snprintf(mac_new_char, sizeof(mac_new_char), "%02x:%02x:%02x:%02x:%02x:%02x",mac_new[0], mac_new[1], mac_new[2], mac_new[3], mac_new[4], mac_new[5]);
  
  #ifdef DEBUG
    Serial.printf("[%s]: NEW MAC: %s\n",__func__,mac_new_char);
  #endif
}

void make_serial_number(char *org_mac, char*new_mac, char *buff)
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  String mac_org_str = String(org_mac);
  String mac_new_str = String(new_mac);

  mac_org_str.replace(":","");
  mac_new_str.replace(":","");
  mac_new_str.remove(0,6);

  // find the size of the new string formatted
  size_t nbytes = snprintf(NULL,0,"%s_%s",mac_org_str,mac_new_str) +1;

  // final serial_number in format: "oldmac_6charofnewmac"
  snprintf(buff,nbytes,"%s_%s",mac_org_str,mac_new_str);

  #ifdef DEBUG
    Serial.printf("[%s]: serial_number: %s\n",__func__,buff);
  #endif
}

void make_fw_version(const char *fw_numeric, char *fw_with_date_time)
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  int month, day, year, zh_hour,zh_minute, short_y;
  static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  // day, year done
  sscanf(__DATE__, "%s %d %d", fw_with_date_time, &day, &year);
  // short year
  short_y=(((year / 10U) % 10)* 10)  + ((year / 1U) % 10);
  //month done
  month = (strstr(month_names, fw_with_date_time)-month_names)/3+1;
  // time
  String time_str = String(__TIME__);
  time_str.replace(":"," ");
  char time_chr[30];
  snprintf(time_chr,sizeof(time_chr),"%s",time_str);
  sscanf(time_chr, "%d %d", &zh_hour, &zh_minute);
  // tmp char
  char fw_char[20];
  // numeric to string
  String fw_str = String(fw_numeric);
  // dots to space
  fw_str.replace("."," ");
  // string to char
  snprintf(fw_char,sizeof(fw_char),"%s",fw_str);
  int major,minor,patch;
  // digits to int
  sscanf(fw_char, "%d %d %d", &major, &minor, &patch);
  snprintf(fw_char,sizeof(fw_char),"%d",major*100+minor*10+patch);
  // final fw_version in format: FW.DATE.TIME (of compilation) where FW= major(hundreds)+minor(tens)+patch(units)
  size_t nbytes =   snprintf(NULL,0,"%s.%02d%02d%02d.%02d%02d",fw_char, short_y, month, day,zh_hour,zh_minute)+1;
  snprintf(fw_with_date_time,nbytes,"%s.%02d%02d%02d.%02d%02d",fw_char, short_y, month, day,zh_hour,zh_minute);
  #ifdef DEBUG
    Serial.printf("[%s]: fw_version: %s\n",__func__,fw_with_date_time);
  #endif
}

bool periodic_firmware_check()
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  u_int32_t start = millis();
  char current_firmware_file[255];
  snprintf(current_firmware_file,sizeof(current_firmware_file),"%s/box%d.bin.version",UPDATE_FIRMWARE_HOST,DEVICE_ID);

  char cur_fw_ver[50];
  snprintf(cur_fw_ver,sizeof(cur_fw_ver),"%s",FW_VERSION);

  if (check_new_fw_on_server(current_firmware_file,fw_ver_on_server))
  {
    if (compare_firmwares(cur_fw_ver, fw_ver_on_server)) 
    {
      Serial.printf("[%s]: Firmware on server (%s) is NEWER than current (%s), it took=%dms\n",__func__,fw_ver_on_server,cur_fw_ver,millis()-start);
      Serial.printf("[%s]: Going to update firmware now!\n",__func__);
      do_update();
    }
    else 
    {
      LOG0  ("[%s]: FW on server=%s, my FW=%s, it took=%dms\n",__func__,fw_ver_on_server,cur_fw_ver,millis()-start);
    }
    return true;
  } else 
  {
    LOG0  ("[%s]: ERROR: Cannot check firmware on server!, it took=%dms\n",__func__,millis()-start);
    return false;
  }  
}

bool compare_firmwares(char *current_firmware, char *new_firmware)         //true if new firmware is newer than current, false if equal or new is older or not comparable
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  int cur_fw_major_ver, cur_fw_minor_ver, cur_fw_patch_ver;
  int new_fw_major_ver, new_fw_minor_ver, new_fw_patch_ver;

  #ifdef DEBUG
    Serial.printf("[%s]: Current: %s\n",__func__,current_firmware);
    Serial.printf("[%s]: New    : %s\n",__func__,new_firmware);
  #endif

  uint8_t cfw_dots, nfw_dots;
  for (int i=0; i<strlen(current_firmware); i++)
  {
    if (current_firmware[i] == '.') 
    {
      ++cfw_dots;
    }
  }
  for (int i=0; i<strlen(new_firmware); i++)
  {
    if (new_firmware[i] == '.') 
    {
      ++nfw_dots;
    }
  }

  if (cfw_dots != nfw_dots)
  {
    #ifdef DEBUG
      Serial.printf("[%s]: FW versions are in different formats - cannot compare!\n\tCurrent FW=%s\n\t    New FW=%s\n",__func__,current_firmware,new_firmware);
      Serial.printf("[%s]: cfw_dots=%d, nfw_dots=%d\n",__func__,cfw_dots,nfw_dots);
    #endif
    return false;
  }

  int cfw = sscanf(current_firmware, "%d.%d.%d", &cur_fw_major_ver, &cur_fw_minor_ver, &cur_fw_patch_ver);
  int nfw = sscanf(new_firmware, "%d.%d.%d", &new_fw_major_ver, &new_fw_minor_ver, &new_fw_patch_ver);

  #ifdef DEBUG
    Serial.printf("[%s]: cfw: %d\n",__func__,cfw);
    Serial.printf("[%s]: nfw: %d\n",__func__,nfw);
  #endif  

  if (cfw != nfw)
  {
    #ifdef DEBUG
      Serial.printf("[%s]: One of the versions are not numerical: current SW=%s, new SW=%s - CANNOT COMPARE!\n",__func__,current_firmware,new_firmware);
    #endif
    return false;
  }

  #ifdef DEBUG
    if (new_fw_major_ver > cur_fw_major_ver)            // new major >
    {
      Serial.println("new major bigger");
      return true;
    } else                                              // new major <=
    {
      if (new_fw_major_ver < cur_fw_major_ver)          // new major <
      {
        Serial.println("new major smaller");
        return false;
      } else                                            // new major =
      {
        if (new_fw_minor_ver > cur_fw_minor_ver)        // new minor >
        {
          Serial.println("new minor bigger");
          return true;
        } else                                          // new minor <=
        {
          if (new_fw_minor_ver < cur_fw_minor_ver)      // new minor <
          {
            Serial.println("new minor smaller");
            return false;
          } else                                        // new minor =
          {
            if (new_fw_patch_ver > cur_fw_patch_ver)    // new patch >
            {
              Serial.println("new patch bigger");
              return true;
            } else                                      // new patch <=
            {
              if (new_fw_patch_ver < cur_fw_patch_ver)  // new patch <
              {
                Serial.println("new patch smaller");
                return false;
              } else                                    // new patch =
              {
                Serial.println("new patch equal");
                return false;
              }
            }
          }
        }
      }
    }
  #else
    if (new_fw_major_ver > cur_fw_major_ver) return true;
    else if (new_fw_major_ver < cur_fw_major_ver) return false;
    // major are equal:
    else 
    if (new_fw_minor_ver > cur_fw_minor_ver) return true;
    else if (new_fw_minor_ver < cur_fw_minor_ver) return false;
    // minor are ALSO equal
    else 
    if (new_fw_patch_ver > cur_fw_patch_ver) return true;
    else if (new_fw_patch_ver < cur_fw_patch_ver) return false;  
    // patch are ALSO equal
    else return false;
  #endif
}

bool check_new_fw_on_server(char *firmware_path, char *new_firmware_version)  // true if checked, false if cannot check
/*
https://www.ingdemurtas.it/en/electronics/esp32-self-updating-from-server/
*/
{
  #ifdef DEBUG
    Serial.printf("[%s]: ...\n",__func__);
  #endif  
  #ifdef DEBUG
    unsigned long free_mem = ESP.getFreeHeap();
    Serial.printf("[%s]: Free heap=%u bytes\n",__func__,free_mem);
  #endif

  HTTPClient client;
  client.begin(firmware_path);
  int resp = client.GET();

  // testing http responses
  // Serial.printf("[%s]: initial Response: %d\n",__func__,resp);
  uint32_t started_millis = millis();
  while ((millis() <  (started_millis + (WAIT_FOR_HTTP_S * 1000))) and (resp != 200))
  {
    delay(500);
    resp = client.GET();
    #ifdef DEBUG
      Serial.printf("[%s]: next Response: %d\n",__func__,resp);
    #endif
  }
  // testing http responses END

  #ifdef DEBUG
    Serial.printf("[%s]: Response: %d\n",__func__,resp);
  #endif
  if(resp == 200)
  {
      // get length of the file (is -1 when Server sends no Content-Length header)
      int totalLength = client.getSize();
      if (totalLength <= 0)
      {
        Serial.printf("[%s]: totalLength < 0, checking FAILED, exitting\n",__func__);
        return false;
      }
      int len = totalLength;
      #ifdef DEBUG
        Serial.printf("[%s]: FW Size: %u\n",__func__,totalLength);
      #endif
      // buffer for read
      uint8_t buff[128] = { 0 };
      // get tcp stream
      WiFiClient * stream = client.getStreamPtr();
      // read all data from server
      while(client.connected() && (len > 0 || len == -1)) 
      {
        size_t size = stream->available();
        if(size) {
          // read up to 128 byte
          int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
          if(len > 0) {
              len -= c;
          }
        }
        delay(1);
      }
      
      String fw_temp;
      for (int i=0; i<totalLength; i++)
      {
        if (iscntrl((char)buff[i]) == 0)  // remove control characters from the string i.e. \n or \r
        {
          fw_temp += (char)buff[i];
        }
      }
      #ifdef DEBUG
        Serial.printf("[%s]: New SW version on server: %s\n",__func__,fw_temp);
      #endif

      int nbytes = snprintf(NULL,0,"%s",fw_temp) +1;
      snprintf(new_firmware_version, nbytes,"%s",fw_temp);

    return true;
  } else 
  {
    // #ifdef DEBUG
      // Serial.printf("[%s]: NO new SW\n",__func__);
      Serial.printf("[%s]: Cannot check firmware version. Only HTTP response 200 is supported but received: %d\n",__func__,resp);
    // #endif
    snprintf(new_firmware_version, 1,"%s","");
    // how about hibernate here?
    hibernate(false, sleeptime_s);
    // never here 
    return false;
  }
}

void setup()
{
  start_time = millis();

  // lights ON
  #ifdef ACT_BLUE_LED_GPIO
    #ifdef DEBUG
      Serial.printf("[%s]: Configuring and turning ON ACT_BLUE_LED=%d\n",__func__,ACT_BLUE_LED_GPIO);
    #endif
      pinMode(ACT_BLUE_LED_GPIO,OUTPUT);
      digitalWrite(ACT_BLUE_LED_GPIO,HIGH);
  #endif
  #ifdef ERROR_RED_LED_GPIO
    #ifdef DEBUG
      Serial.printf("[%s]: Configuring and turning OFF ERROR_RED_LED=%d\n",__func__,ERROR_RED_LED_GPIO);
    #endif
      pinMode(ERROR_RED_LED_GPIO,OUTPUT);
      digitalWrite(ERROR_RED_LED_GPIO,LOW);
  #endif

  //Initialize NVS or erase it if not initialised (that usually never happens)
  esp_err_t ret = nvs_flash_init();
  if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) 
  {
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
  }
  ESP_ERROR_CHECK(ret);
  //Initialize NVS ... END

  boot_reason = esp_reset_reason();
  wakeup_reason = esp_sleep_get_wakeup_cause();

  myData.md_boot_reason = boot_reason;
  myData.md_wakeup_reason = wakeup_reason;

  #ifdef DEBUG
    #if ARDUINO_USB_CDC_ON_BOOT == 1  // delay for C3 mini or others using native USB
      delay(10000);
    #endif
  #endif 

  Serial.begin(115200);
  delay(1); //50

  Serial.printf("\n======= S T A R T =======\n");
  // apply new MAC address
  change_mac(); // it provides: mac_org_char,  mac_new_char changed globally
  // make new SN char based on old and new MACs
  make_serial_number(mac_org_char,mac_new_char,serial_number);
  make_fw_version(FW_VERSION, fw_version);

  Serial.printf("[%s]: %s, version: %s, compiled on: %s\n",__func__,HOSTNAME,fw_version,COMPILATION_TIME);
  
  // #ifdef DEBUG
    Serial.printf("[%s]: CHECK_FW_BOOTCOUNTS=%d, CHECK_FW_ON_BOOT=%d, ENCRYPT_ESPNOW=%d\n",__func__,CHECK_FW_BOOTCOUNTS,CHECK_FW_ON_BOOT,ENCRYPT_ESPNOW);
  // #endif

  #ifdef DEBUG
    homeSpan.setLogLevel(2);
    char firmware_file[255];
    snprintf(firmware_file,sizeof(firmware_file),"%s/box%d.bin",UPDATE_FIRMWARE_HOST,DEVICE_ID);
    Serial.printf("[%s]: firmware file: %s\n",__func__,firmware_file);
  #else
    homeSpan.setLogLevel(0);
  #endif

  // get channel and sleeptime and bootcount from preferences
  #ifdef DEBUG
    unsigned int start_pref = millis();
  #endif

  read_wifi_credentials();

  String bootCount_str;
  preferences.begin("wifi-secrets", true); // read only
  channel = preferences.getString("rec_channel", "0");
  sleeptime_s_str = preferences.getString("rec_sleep_s", "0");
  bootCount_str = preferences.getString("bootcount", "0");
  bootCount = bootCount_str.toInt();
  preferences.end();
  int wifi_channel = channel.toInt();
  sleeptime_s = sleeptime_s_str.toInt();

  // align bootCount accordingly
  if (boot_reason != 8)
  {
    bootCount = 1;
  } else
  {
    ++bootCount;
  }
  Serial.printf("[%s]: Bootcount=%d\n",__func__,bootCount);
  myData.md_bootcount = bootCount;

  // check sleeptime
  if ((sleeptime_s < 1) or (sleeptime_s > 3600))
  {
    // #ifdef DEBUG
      Serial.printf("[%s]: Found saved INVALID sleep time=%d seconds (it should be: 1-3600 seconds)\n",__func__,sleeptime_s);
    // #endif
    sleeptime_s = SLEEP_TIME_S; // anything between 1s to 3600s or default to SLEEP_TIME_S from config file
    old_sleeptime_s_str = sleeptime_s;

    #ifdef DEBUG
      Serial.printf("[%s]: Will use the default one=%d seconds\n",__func__,sleeptime_s);
      Serial.printf("[%s]: Saving default sleeptime(%d seconds) to preferences\n",__func__,sleeptime_s);
    #endif
  } else
  {
    #ifdef DEBUG
      Serial.printf("[%s]: Found saved sleep time=%d seconds - will use it\n",__func__,sleeptime_s);
    #endif
  }

  // check channel
  bool valid_wifi_channel = false;
  if ((wifi_channel > 0) and (wifi_channel < 14))
  {
    valid_wifi_channel = true;
    #ifdef DEBUG
      Serial.printf("[%s]: Found saved WiFi channel=%d - will use it for ESPnow at first\n",__func__,wifi_channel);
    #endif
  } else
  {
    // #ifdef DEBUG
      Serial.printf("[%s]: Found saved WiFi channel=%d is incorrect - will NOT use it for ESPnow!\n",__func__,wifi_channel);
    // #endif
  }
  // get channel and sleeptime from preferences END



// // fw update thingis  - MOVED TO THE END
//   if ((bootCount % CHECK_FW_BOOTCOUNTS == 0) or ((bootCount == 1) and (CHECK_FW_ON_BOOT == 1)))
//   {
//     if (bootCount % CHECK_FW_BOOTCOUNTS == 0) 
//       Serial.printf("[%s]: Periodic FW check due to bootCount=%d and check every %d\n",__func__,bootCount,CHECK_FW_BOOTCOUNTS);
//     else 
//       Serial.printf("[%s]: Periodic FW check due to bootCount=%d and CHECK_FW_ON_BOOT=%d\n",__func__,bootCount,CHECK_FW_ON_BOOT);

//     connect_wifi();
//     periodic_firmware_check();
//     // esp_err_t wifi_off_result = esp_wifi_stop();
//     // Serial.printf("[%s]: WiFi turned off=%d\n",__func__,wifi_off_result);
//   }
// // fw update thingis END

  // turn ON power for I2C devices
  #ifdef ENABLE_3V_GPIO
    pinMode(ENABLE_3V_GPIO,OUTPUT);
    #ifdef DEBUG
      Serial.printf("[%s]: Enabling 3V to power sensors on GPIO=%d\n",__func__,ENABLE_3V_GPIO);
    #endif
    digitalWrite(ENABLE_3V_GPIO, HIGH);
    while (!digitalRead(ENABLE_3V_GPIO)){}
    delay(10);
  #endif
  
  #if defined(CUSTOM_SDA_GPIO) and defined(CUSTOM_SCL_GPIO)
    #ifdef DEBUG
      Serial.printf("[%s]: CUSTOM I2C GPIO pins applied: SDA=%d, SCL=%d\n",__func__,CUSTOM_SDA_GPIO,CUSTOM_SCL_GPIO);
    #endif
    Wire.setPins(CUSTOM_SDA_GPIO,CUSTOM_SCL_GPIO);
  #endif
  Wire.begin();
  delay(1);

  // MAX17048 - fuel gauge - prepare in advance to get some delay to enable it
  #if (USE_MAX17048 == 1)
    max17048ready = prepare_max17048();
  #endif

  #ifdef DEBUG
    Serial.printf("[%s]: Boot   cause=%d - ",__func__,boot_reason);
  #endif
  switch(boot_reason)
  {
    // 1 = reset/power on
    case ESP_RST_POWERON:
    {
      #ifdef DEBUG
        Serial.printf("power on or reset\n");
      #endif
      #ifdef ACT_BLUE_LED_GPIO
        digitalWrite(ACT_BLUE_LED_GPIO, HIGH);
      #endif
      #ifdef ERROR_RED_LED_GPIO
        digitalWrite(ERROR_RED_LED_GPIO, HIGH);
      #endif
      break;
    }
    // 3 = Software reset via esp_restart
    case ESP_RST_SW:
    {
      #ifdef DEBUG
        Serial.printf("software reset via esp_restart\n");
      #endif
      #ifdef ACT_BLUE_LED_GPIO
        digitalWrite(ACT_BLUE_LED_GPIO, HIGH);
      #endif
      #ifdef ERROR_RED_LED_GPIO
        digitalWrite(ERROR_RED_LED_GPIO, HIGH);
      #endif
      break;
    }
    // 8 = deep sleep
    case ESP_RST_DEEPSLEEP:
    {
      #ifdef DEBUG
        Serial.printf("wake up from deep sleep \n");
      #endif
      break;
    }
    default:
    {
      #ifdef DEBUG
        Serial.printf("other boot cause=%d\n");
      #endif
      break;
    }
  }

  #ifdef DEBUG
    Serial.printf("[%s]: Wakeup cause=%d - ",__func__,wakeup_reason);
  #endif
  switch(wakeup_reason)
  {
    // 0 = not deep sleep
    case ESP_SLEEP_WAKEUP_UNDEFINED:
    {
      #ifdef DEBUG
        Serial.printf("wake up was not caused by exit from deep sleep\n");
      #endif
      break;
    }
    // 2 = not in use
    case ESP_SLEEP_WAKEUP_EXT0:
    {
      break;
    }
    // 3 = fw update (FW_UPGRADE_GPIO) - not for ESP32-C3!
    #if (BOARD_TYPE != 4)
      case ESP_SLEEP_WAKEUP_EXT1:
      {
        wakeup_gpio_mask = esp_sleep_get_ext1_wakeup_status();
        wakeup_gpio = log(wakeup_gpio_mask)/log(2);
        #ifdef DEBUG
          Serial.printf("external signal using GPIO=%d, GPIO_MASK=%ju\n",wakeup_gpio,wakeup_gpio_mask);
        #endif
        #ifdef ERROR_RED_LED_GPIO
          digitalWrite(ERROR_RED_LED_GPIO, HIGH);
        #endif
        #ifdef FW_UPGRADE_GPIO
          if (wakeup_gpio == FW_UPGRADE_GPIO)
          {
            fw_update = true;
            #ifdef DEBUG
              Serial.printf("[%s]: woke up on FW button pressed\n",__func__);
              Serial.printf("[%s]: debouncing for %dms\n",__func__,DEBOUNCE_MS);
            #endif
            delay(DEBOUNCE_MS);
          }
        #endif
        break;
      }
    #endif
    // 4 = wake up on timer (SLEEP_TIME_S or COOLING_SLEEP_DURATION_S)
    case ESP_SLEEP_WAKEUP_TIMER:
    {
      #ifdef DEBUG
        Serial.printf("timer (cooling or heartbeat)\n");
      #endif
      #ifdef ACT_BLUE_LED_GPIO
        digitalWrite(ACT_BLUE_LED_GPIO, HIGH);
      #endif
      break;
    }
    // 5 = not in use
    case ESP_SLEEP_WAKEUP_TOUCHPAD:
    {
      #ifdef DEBUG
        Serial.printf("WAKEUP_TOUCHPAD\n");
      #endif
      break;
    }
    // 6 = not in use
    case ESP_SLEEP_WAKEUP_ULP:
    {
      #ifdef DEBUG
        Serial.printf("WAKEUP_ULP\n");
      #endif
      break;
    }
    // 7 = for ESP32-C3 - ESP_SLEEP_WAKEUP_GPIO,for others it is from light sleep only (ESP32, S2 and S3) so not programmed
    #if (BOARD_TYPE == 4)
      case ESP_SLEEP_WAKEUP_GPIO:
      {
        wakeup_gpio_mask = esp_sleep_get_gpio_wakeup_status();
        wakeup_gpio = log(wakeup_gpio_mask)/log(2);
        #ifdef DEBUG
          Serial.printf("external signal using GPIO=%d, GPIO_MASK=%ju\n",wakeup_gpio,wakeup_gpio_mask);
        #endif
        #ifdef ERROR_RED_LED_GPIO
          digitalWrite(ERROR_RED_LED_GPIO, HIGH);
        #endif
        #ifdef FW_UPGRADE_GPIO
          if (wakeup_gpio == FW_UPGRADE_GPIO)
          {
            fw_update = true;
            #ifdef DEBUG
              Serial.printf("[%s]: woke up on FW button pressed\n",__func__);
              Serial.printf("[%s]: debouncing for %dms\n",__func__,DEBOUNCE_MS);
            #endif
            delay(DEBOUNCE_MS);
          }
        #endif
        break;
      }
    #endif
    default:
    {
      #ifdef DEBUG
        Serial.printf("OTHER Wakeup cause\n");
      #endif
      break;
    }
  }


  // FW upgrade here:
  // 1 time: continue as normal
  // short, < 3s: continue as normal
  // 3-6s, restart
  // >6s: FW update
  #ifdef FW_UPGRADE_GPIO
    // RED LED ON, BLUE OFF if FW_UPGRADE_GPIO was pressed
    if (fw_update)
    {
      #ifdef ACT_BLUE_LED_GPIO
        digitalWrite(ACT_BLUE_LED_GPIO, LOW);
      #endif
      #ifdef ERROR_RED_LED_GPIO
        digitalWrite(ERROR_RED_LED_GPIO, HIGH);
      #endif
      // check if FW_UPGRADE_GPIO is still high after boot
      pinMode(FW_UPGRADE_GPIO,INPUT);
      if ((digitalRead(FW_UPGRADE_GPIO)) and (fw_update))
      {
        Serial.printf("[%s]: FW_UPGRADE_GPIO is still pressed after starting...\n",__func__);
        Serial.printf("[%s]: Waiting for 3s...\n",__func__);
        delay(3000);
        if (digitalRead(FW_UPGRADE_GPIO))
        {
          // first 3s done
          Serial.printf("[%s]: 3s passed\n",__func__);
          Serial.printf("[%s]: FW_UPGRADE_GPIO is still pressed after 3s...\n",__func__);

          #ifdef ERROR_RED_LED_GPIO
            digitalWrite(ERROR_RED_LED_GPIO, LOW);
          #endif
          delay(50);
          #ifdef ACT_BLUE_LED_GPIO
            digitalWrite(ACT_BLUE_LED_GPIO, HIGH);
          #endif
          fw_update = false;
          Serial.printf("[%s]: Waiting for 3s...\n",__func__);
          delay(3000);
          if (digitalRead(FW_UPGRADE_GPIO))
          {
            // second 3s done so 6s in total
            #ifdef ERROR_RED_LED_GPIO
              digitalWrite(ERROR_RED_LED_GPIO, HIGH);
            #endif
            delay(50);
            #ifdef ACT_BLUE_LED_GPIO
              digitalWrite(ACT_BLUE_LED_GPIO, LOW);
            #endif
            Serial.printf("[%s]: Long press > 6s - FW update about to happen now...\n",__func__);
            connect_wifi();
            do_update();
          } else
          {
            // press > 3s but <6s so restart
            // don't turn OFF the LED during connect_wifi - or maybe turn on there? 
            // #ifdef ACT_BLUE_LED_GPIO
            //   digitalWrite(ACT_BLUE_LED_GPIO, LOW);
            // #endif
            Serial.printf("[%s]: Long press but <6s - Starting Captive Portal...\n",__func__);
            reset_wifi_credentials();
            connect_wifi();
            StartCaptivePortal();
            Serial.printf("[%s]: Restarting...\n",__func__);
            do_esp_restart();
          }
        } else // press < 3s (but pressed during ESP start) so nothing - continue
        {
          #ifdef DEBUG
            Serial.printf("[%s]: Short press (<3s) - continuing normal boot...\n",__func__);
          #endif
        }
      }
    }
    else    // FW_UPGRADE_GPIO was never pressed
    {}
  #endif


  // sht31 temperature and humidity
  myData.md_temp= -99.9f;
  myData.md_hum = -99.9f;
  #if (USE_SHT31 == 1)
    get_temp_hum(&myData.md_temp, &myData.md_hum);
  #else
    #ifdef DEBUG
        Serial.printf("[%s]: SHT31 NOT ENABLED\n",__func__);
    #endif
  #endif  
  // sht31 temperature and humidity END

  // tsl2561 light
  myData.md_light = -99.9f;
  #if (USE_TSL2561 == 1)
    get_lux(&myData.md_light);
  #else
    #ifdef DEBUG
        Serial.printf("[%s]: TSL2561 NOT ENABLED\n",__func__);
    #endif
  #endif  
  // tsl2561 light END
  
  // MAX17048 Lipo status
  myData.md_bat = 0;
  myData.md_bat_volts = 0.0f;
  #if (USE_MAX17048 == 1)
    unsigned max17048_start_measure_time = millis();
    if (max17048ready) get_battery(&myData.md_bat_volts, &myData.md_bat);
    #ifdef DEBUG
      Serial.printf("[%s]: Battery=%0.2fV\n",__func__,myData.md_bat_volts);
      Serial.printf("[%s]: Low battery threshold=%0.2fV, Minimum battery threshold=%0.2fV\n",__func__,LOW_BATTERY_VOLTS,MINIMUM_VOLTS);
    #endif
    if (myData.md_bat_volts <= LOW_BATTERY_VOLTS)
          // testing only
          // if (myData.md_bat_volts <= 6)
    {
      if (myData.md_bat_volts <= MINIMUM_VOLTS)
          // testing only
          // if (myData.md_bat_volts <= 5)
      {
        if (max17048ready)              // don't do it if MAX17048 is faulty
        {
          Serial.printf("[%s]: Battery empty (%0.2fV) - going to sleep for %ds\n",__func__,myData.md_bat_volts,(24*60*60));
          hibernate(true, (SLEEP_TIME_H_BATTERY_EMPTY*60*60)); // 24 hours sleep if battery empty
        } else
        {
          Serial.printf("[%s]: Battery measurement is INVALID\n",__func__);
        }
      }
      #ifdef DEBUG
        Serial.printf("[%s]: LOW battery!\n",__func__);
      #endif
    } else
    {
      #ifdef DEBUG
        Serial.printf("[%s]: Battery OK\n",__func__);
      #endif
    }
    #ifdef DEBUG
      unsigned max17048_end_time = millis();
      Serial.printf("[%s]: Measuring battery took %ums\n",__func__,(max17048_end_time-max17048_start_measure_time));
    #endif
    lipo.enableHibernate();     // measurement every 45s
  #endif
  // MAX17048 Lipo status END

  //charging
  myData.md_charging = 0;
  #if defined(POWER_GPIO) and defined(CHARGING_GPIO)
    pinMode(CHARGING_GPIO, INPUT_PULLUP);   // must be PULLUP as TP4056 is Active LOW
    pinMode(POWER_GPIO, INPUT_PULLUP);      // must be PULLUP as TP4056 is Active LOW
    #ifdef DEBUG
      Serial.printf("[%s]: CHARGING_GPIO and POWER_GPIO enabled\n",__func__);
    #endif
    myData.md_charging=get_charging();
  #endif
  //charging END

  // turn OFF power for I2C devices
  #ifdef ENABLE_3V_GPIO
    #ifdef DEBUG
      Serial.printf("[%s]: Disabling 3V power for sensors on GPIO=%d\n",__func__,ENABLE_3V_GPIO);
      digitalWrite(ENABLE_3V_GPIO, LOW);
    #endif
  #endif

  // other info
  myData.md_mcu_model = BOARD_TYPE;
  snprintf(myData.md_version,sizeof(myData.md_version),"%s",fw_version);

  #if (USE_MAX17048 == 1)
    Serial.printf("\n[%s]: Temperature=%0.2fC, Humidity=%0.2f%%, Light=%0.2flx, Battery percent=%d%%, Volts=%0.2fV\n",__func__,myData.md_temp,myData.md_hum,myData.md_light,myData.md_bat,myData.md_bat_volts);
  #else 
    Serial.printf("\n[%s]: Temperature=%0.2fC, Humidity=%0.2f%%, Light=%0.2flx, Battery percent=%d%%\n",__func__,myData.md_temp,myData.md_hum,myData.md_light,myData.md_bat);
  #endif 

  Serial.printf("[%s]: Charging=%d (%s), BOARD_TYPE=%d (%s)\n",__func__,myData.md_charging, charging_states[charging_int], myData.md_mcu_model,MODEL);

  // wifi_ok:
  if (is_setup_done)
    myData.wifi_ok = 1;
  else 
    myData.wifi_ok = 0;
  // wifi_ok: END
  Serial.printf("[%s]: WiFi ok (previous): %d\n\n",__func__,myData.wifi_ok);


  // sending data to bridge
  #ifdef DEBUG
    Serial.printf("[%s]: Sending data to bridge...\n",__func__);
  #endif

  // setup encryption
  #if (ENCRYPT_ESPNOW == 0)
      SpanPoint::setEncryption(false);
  #else
    SpanPoint::setEncryption(true);
  #endif
  mainDevice=new SpanPoint(BRIDGE,sizeof(myData),0);    // create a SpanPoint with send size=sizeof(myData) and receive size=0
  // is ESPnow channel fixed?
/*
if valid channel:
{
  send,
  if successfull
  {
    bye
  } else
  {
    repeat sending for all channels from 1-13
    if successful
    {
      save
      bye
    } else
    {
      bye
    }
  }
} else // no channel provided
{
  sending for all channels from 1-14
  if successful
  {
    save
    bye
  } else
  {
    bye
  }
}
*/

  unsigned long start_send_data = millis();
  unsigned long end_send_data = 0;
  bool send_successful = false;
  if (valid_wifi_channel)
  {
    #ifdef DEBUG
      Serial.printf("[%s]: ESPnow configured to use only one channel=%d\n",__func__,wifi_channel);
    #endif
    SpanPoint::setChannelMask(1<<wifi_channel);
    if (mainDevice->send(&myData))      // first sending, either over 1 channel or over all channels
    {
      end_send_data = millis();
      Serial.printf("[%s]: Sending data to bridge - SUCCESSFULL, it took %ums\n",__func__,(end_send_data - start_send_data));
      send_successful = true;
    } else
    {
      Serial.printf("[%s]: Sending data to bridge - this time over all WiFi channels...\n",__func__);
      start_send_data = millis();
      for (int i=1;i<14;i++)
      {
        SpanPoint::setChannelMask(1<<i);
        if (mainDevice->send(&myData))
        {
          end_send_data = millis();
          send_successful = true;

          Serial.printf("[%s]: Sending data to bridge - SUCCESSFULL, it took %ums\n",__func__,(end_send_data - start_send_data));
          Serial.printf("[%s]: Setting and saving new WiFi channel (CHANNEL=%d) in preferences\n",__func__,i);

          old_channel = String(i);
          break;
        }
      }
      if (!send_successful)
      {
        end_send_data = millis();
        Serial.printf("[%s]: Sending data to bridge - FAILED AGAIN (on ALL CHANNELS), it took %ums\n",__func__,(end_send_data - start_send_data));
      }
    }
  } else
  {
    Serial.printf("[%s]: Sending data to bridge over all WiFi channels...\n",__func__);
    start_send_data = millis();
    for (int i=1;i<14;i++)
    {
      SpanPoint::setChannelMask(1<<i);
      if (mainDevice->send(&myData))
      {
        end_send_data = millis();
        send_successful = true;

        Serial.printf("[%s]: Sending data to bridge - SUCCESSFULL, it took %ums\n",__func__,(end_send_data - start_send_data));
        Serial.printf("[%s]: Setting and saving new WiFi channel in preferences\n",__func__);

        old_channel = String(i);
        break;
      }
    }
    if (!send_successful)
    {
      end_send_data = millis();
      Serial.printf("[%s]: Sending data to bridge - FAILED AGAIN (on ALL CHANNELS), it took %ums\n",__func__,(end_send_data - start_send_data));
    }
  }
  // sending data to bridge END

// fw update thingis - MOVED TO THE END TO AVOID WIFI PROBLEMS (ESPNOW AFTER WIFI WAS CONNECTED ON ANOTHER SSID/CHANNEL)
  if ((bootCount % CHECK_FW_BOOTCOUNTS == 0) or ((bootCount == 1) and (CHECK_FW_ON_BOOT == 1)))
  {
    if (bootCount % CHECK_FW_BOOTCOUNTS == 0) 
      Serial.printf("[%s]: Periodic FW check due to bootCount=%d and check every %d\n",__func__,bootCount,CHECK_FW_BOOTCOUNTS);
    else 
      Serial.printf("[%s]: Periodic FW check due to bootCount=%d and CHECK_FW_ON_BOOT=%d\n",__func__,bootCount,CHECK_FW_ON_BOOT);

    connect_wifi();
    periodic_firmware_check();
  }
// fw update thingis END

  hibernate(false, sleeptime_s);
  Serial.println("if you read this message, it means you lost your life ;-)");
}

void loop() {}
