Subversion Repositories ESP32_P1_Meter

Rev

Rev 5 | Blame | Compare with Previous | Last modification | View Log | RSS feed

////////////////////////////////  Include libraries  /////////////////////////////////////////
#include <Arduino.h>
#include <ArduinoOTA.h>
#include <EEPROM.h>
#include <FS.h>
#include <LittleFS.h>
#include <ezButton.h>
#include <PubSubClient.h>              // https://github.com/knolleary/pubsubclient/
#include <AsyncFsWebServer.h>          //https://github.com/cotestatnt/async-esp-fs-webserver
#include <HardwareSerial.h>

////////////////////////////////  Include settings  /////////////////////////////////////////
#include "settings.h"

////////////////////////////////  Define constants  /////////////////////////////////////////
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
#define FILESYSTEM LittleFS
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"          // Timezone definition to get properly time from NTP server
#ifndef Default_MQTT_Port
  #define Default_MQTT_Port "1883"
#endif
#define BTN_SAVE  5
// added labels for /setup webpage
#define USER_LABEL "Username"
#define PASSWORD_LABEL "Password"
#define EXTERNAL_ANTENNA_LABEL "External (Extra Internal) Antenna"

#define MQTT_ENABLED_LABEL "MQTT Enabled"
#define MQTT_CLIENT_LABEL "MQTT Client"
#define MQTT_SERVER_LABEL "MQTT Server"
#define MQTT_PORT_LABEL "MQTT Server Port"
#define MQTT_USER_LABEL "MQTT User"
#define MQTT_PASSWORD_LABEL "MQTT Password"
#define MQTT_PREFIX_LABEL "MQTT Prefix"

#define SHORT_PRESS_TIME_MIN 1000 // 1 second
#define SHORT_PRESS_TIME_MAX 2500 // 2.5 seconds
#define LONG_PRESS_TIME  10000 // 10 seconds

const uint8_t logo_png[] PROGMEM = {
  0x89, 0x50, 0x4E, 0x47, /* ... */
};

////////////////////////////////  Create Objects  /////////////////////////////////////////
struct tm Time;

HardwareSerial mySerial(0); // UART0 (Serial0)

AsyncFsWebServer server(FILESYSTEM, 80, "P1_Meter");
ezButton button(9); // create ezButton object that attach to pin GPIO9

WiFiClient espClient;
PubSubClient mqttClient(espClient);

////////////////////////////////  Define variables  /////////////////////////////////////////
bool captiveRun = false;
// Setup Settings
String Username = "admin";
String Password = "admin";
bool External_Antenna = true;

// MQTT Settings
bool MQTT_Enabled = false;
String MQTT_Client = "ESP32_P1_Meter";
String MQTT_Server = "";
String MQTT_Port = Default_MQTT_Port;
String MQTT_User = "";
String MQTT_Password = "";
String MQTT_Prefix = "P1_Meter";
int LEDSpeed = 0;

bool isPressing = false;
unsigned long pressedTime  = 0;
unsigned long releasedTime = 0;

char clientId[16];
char inTopic[24];
char outTopic[24];

////////////////////////////////  Filesystem  /////////////////////////////////////////
bool startFilesystem() {
  if (FILESYSTEM.begin()){
    server.printFileList(FILESYSTEM, "/", 2), Serial;
    return true;
  }
  else {
    Serial.println("ERROR on mounting filesystem. It will be reformatted!");
    FILESYSTEM.format();
    ESP.restart();
  }
  return false;
}
/*
* Getting FS info (total and free bytes) is strictly related to
* filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
*/
#ifdef ESP32
void getFsInfo(fsInfo_t* fsInfo) {
        fsInfo->fsName = "LittleFS";
        fsInfo->totalBytes = LittleFS.totalBytes();
        fsInfo->usedBytes = LittleFS.usedBytes();
}
#endif

// * Send a message to a broker topic
void send_mqtt_message(const char *topic, char *payload)
{
    Serial.printf("MQTT Outgoing on %s: ", topic);
    Serial.println(payload);
    if (MQTT_Enabled) {

      bool result = mqttClient.publish(topic, payload, false);

      if (!result)
      {
          Serial.printf("MQTT publish to topic %s failed\n", topic);
      }
    }
}

///////////////////////////  MQTT callback function  ///////////////////////////////////
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  Serial.printf("Message arrived [%s] ", topic);
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

///////////////////////////  MQTT reconnect function  ///////////////////////////////////
void mqttReconnect() {

  if (WiFi.status() != WL_CONNECTED)  // Check connection
    return;

  static uint32_t lastConnectionTime = 5000;
  if (millis() - lastConnectionTime < 5000)  // Wait 5 seconds before retrying
    return;
  lastConnectionTime = millis();

  if (MQTT_Enabled) {
    Serial.print("Attempting MQTT connection...");
    if (mqttClient.connect(MQTT_Client.c_str(), MQTT_User.c_str(), MQTT_Password.c_str())) {  // Attempt to connect
      Serial.println("connected to MQTT server.");

      String payload = "Hello World from ";
      payload += clientId;
      mqttClient.publish(outTopic, payload.c_str());  // Once connected, publish an announcement...
      mqttClient.subscribe(inTopic);
                      // ... and resubscribe
    } else {
      Serial.printf("failed, rc=%d, try again in 5 seconds\n", mqttClient.state());
    }
  }
}

////////////////////////////////   WebSocket Handler  /////////////////////////////
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
  switch (type) {
    case WS_EVT_DISCONNECT:
      Serial.print("WebSocket client disconnected!\n");
      break;
    case WS_EVT_CONNECT:  {
        IPAddress ip = client->remoteIP();
        Serial.printf("WebSocket client %d.%d.%d.%d connected.\n", ip[0], ip[1], ip[2], ip[3]);
        client->printf("%s", "{\"Connected\": true}");
      }
      break;
    default:
        break;
  }
}

////////////////////////////////  NTP Time  /////////////////////////////////////
void getUpdatedtime(const uint32_t timeout)
{
  uint32_t start = millis();
  Serial.print("Sync time...");
  while (millis() - start < timeout  && Time.tm_year <= (1970 - 1900)) {
    time_t now = time(nullptr);
    Time = *localtime(&now);
    delay(5);
  }
  Serial.println(" done.");
}

////////////////////  Load application options from filesystem  ////////////////////
bool loadOptions() {
  if (FILESYSTEM.exists(server.getConfiFileName())) {
    server.getOptionValue(USER_LABEL, Username);
    server.getOptionValue(PASSWORD_LABEL, Password);
    server.getOptionValue(EXTERNAL_ANTENNA_LABEL, External_Antenna);

    server.getOptionValue(MQTT_ENABLED_LABEL, MQTT_Enabled);
    server.getOptionValue(MQTT_CLIENT_LABEL, MQTT_Client);
    server.getOptionValue(MQTT_SERVER_LABEL, MQTT_Server);
    server.getOptionValue(MQTT_PORT_LABEL, MQTT_Port);
    server.getOptionValue(MQTT_USER_LABEL, MQTT_User);
    server.getOptionValue(MQTT_PASSWORD_LABEL, MQTT_Password);
    server.getOptionValue(MQTT_PREFIX_LABEL, MQTT_Prefix);

    Serial.println("\nThis are the current values stored: \n");
    Serial.printf("MQTT Enabled: %s\n", MQTT_Enabled ? "true" : "false");
    Serial.printf("MQTT Client: %s\n", MQTT_Client.c_str());
    Serial.printf("MQTT Server: %s\n", MQTT_Server.c_str());
    Serial.printf("MQTT Server Port: %s\n", MQTT_Port.c_str());
    Serial.printf("MQTT User: %s\n", MQTT_User.c_str());
    Serial.printf("MQTT password: %s\n", MQTT_Password.c_str());
    Serial.printf("MQTT Prefix: %s\n", MQTT_Prefix.c_str());
    return true;
  }
  else
    Serial.println(F("Config file not exist"));
  return false;
}

////////////////////////////  HTTP Request Handlers  ////////////////////////////////////
void handleLoadOptions(AsyncWebServerRequest *request) {
  request->send(200, "text/plain", "Options loaded");
  loadOptions();
  Serial.println("Application option loaded after web request");
}

////////////////////////////  Send data to MQTT  ////////////////////////////////////
void send_metric(String name, long metric)
{
//    Serial.print(F("Sending metric to broker: "));
    Serial.print(name);
    Serial.print(F("="));
    Serial.println(metric);

    char output[10];
    ltoa(metric, output, sizeof(output));

    String topic = String(MQTT_Prefix) + "/" + name;
    send_mqtt_message(topic.c_str(), output);
}

////////////////////////////  Set data for MQTT  ////////////////////////////////////
void send_data_to_broker()
{
    send_metric("consumption_low_tarif", CONSUMPTION_LOW_TARIF);
    send_metric("consumption_high_tarif", CONSUMPTION_HIGH_TARIF);
    send_metric("returndelivery_low_tarif", RETURNDELIVERY_LOW_TARIF);
    send_metric("returndelivery_high_tarif", RETURNDELIVERY_HIGH_TARIF);
    send_metric("actual_consumption", ACTUAL_CONSUMPTION);
    send_metric("actual_returndelivery", ACTUAL_RETURNDELIVERY);

    send_metric("l1_instant_power_usage", L1_INSTANT_POWER_USAGE);
    send_metric("l2_instant_power_usage", L2_INSTANT_POWER_USAGE);
    send_metric("l3_instant_power_usage", L3_INSTANT_POWER_USAGE);
    send_metric("l1_instant_power_current", L1_INSTANT_POWER_CURRENT);
    send_metric("l2_instant_power_current", L2_INSTANT_POWER_CURRENT);
    send_metric("l3_instant_power_current", L3_INSTANT_POWER_CURRENT);
    send_metric("l1_voltage", L1_VOLTAGE);
    send_metric("l2_voltage", L2_VOLTAGE);
    send_metric("l3_voltage", L3_VOLTAGE);
    
    send_metric("gas_meter_m3", GAS_METER_M3);

    send_metric("actual_tarif_group", ACTUAL_TARIF);
    send_metric("short_power_outages", SHORT_POWER_OUTAGES);
    send_metric("long_power_outages", LONG_POWER_OUTAGES);
    send_metric("short_power_drops", SHORT_POWER_DROPS);
    send_metric("short_power_peaks", SHORT_POWER_PEAKS);
}

////////////////////////////  CRC16 Check  ////////////////////////////////////
unsigned int CRC16(unsigned int crc, unsigned char *buf, int len)
{
        for (int pos = 0; pos < len; pos++)
    {
                crc ^= (unsigned int)buf[pos];    // * XOR byte into least sig. byte of crc
                                          // * Loop over each bit
        for (int i = 8; i != 0; i--)
        {
            // * If the LSB is set
            if ((crc & 0x0001) != 0)
            {
                // * Shift right and XOR 0xA001
                crc >>= 1;
                                crc ^= 0xA001;
                        }
            // * Else LSB is not set
            else
                // * Just shift right
                crc >>= 1;
                }
        }
        return crc;
}

////////////////////////////  Check if data is Number  ////////////////////////////////////
bool isNumber(char *res, int len)
{
    for (int i = 0; i < len; i++)
    {
        if (((res[i] < '0') || (res[i] > '9')) && (res[i] != '.' && res[i] != 0))
            return false;
    }
    return true;
}

////////////////////////////  Char search in Array  ////////////////////////////////////
int FindCharInArrayRev(char array[], char c, int len)
{
    for (int i = len - 1; i >= 0; i--)
    {
        if (array[i] == c)
            return i;
    }
    return -1;
}

////////////////////////////  Get the value from the Telegram  ////////////////////////////////////
long getValue(char *buffer, int maxlen, char startchar, char endchar)
{
    int s = FindCharInArrayRev(buffer, startchar, maxlen - 2);
    int l = FindCharInArrayRev(buffer, endchar, maxlen - 2) - s - 1;

    char res[16];
    memset(res, 0, sizeof(res));

    if (strncpy(res, buffer + s + 1, l))
    {
        if (endchar == '*')
        {
            if (isNumber(res, l))
                // * Lazy convert float to long
                return (1000 * atof(res));
        }
        else if (endchar == ')')
        {
            if (isNumber(res, l))
                return atof(res);
        }
    }
    return 0;
}

////////////////////////////  Decode Telegram  ////////////////////////////////////
bool decode_telegram(int len)
{
    int startChar = FindCharInArrayRev(telegram, '/', len);
    int endChar = FindCharInArrayRev(telegram, '!', len);
//    bool validCRCFound = false;
    bool validCRCFound = true;

    for (int cnt = 0; cnt < len; cnt++) {
        Serial.print(telegram[cnt]);
    }
//    Serial.print("\n");

    if (startChar >= 0)
    {
        // * Start found. Reset CRC calculation
        currentCRC = CRC16(0x0000,(unsigned char *) telegram+startChar, len-startChar);
    }
    else if (endChar >= 0)
    {
        // * Add to crc calc
        currentCRC = CRC16(currentCRC,(unsigned char*)telegram+endChar, 1);

        char messageCRC[5];
        strncpy(messageCRC, telegram + endChar + 1, 4);

        messageCRC[4] = 0;   // * Thanks to HarmOtten (issue 5)
//        validCRCFound = (strtol(messageCRC, NULL, 16) == currentCRC);

        if (validCRCFound)
            Serial.println(F("CRC Valid!"));
        else
            Serial.println(F("CRC Invalid!"));

        currentCRC = 0;
    }
    else
    {
        currentCRC = CRC16(currentCRC, (unsigned char*) telegram, len);
    }

    // 1-0:1.8.1(000992.992*kWh)
    // 1-0:1.8.1 = Elektra verbruik laag tarief (DSMR v4.0)
    if (strncmp(telegram, "1-0:1.8.1", strlen("1-0:1.8.1")) == 0)
    {
        CONSUMPTION_LOW_TARIF = getValue(telegram, len, '(', '*');
    }

    // 1-0:1.8.2(000560.157*kWh)
    // 1-0:1.8.2 = Elektra verbruik hoog tarief (DSMR v4.0)
    if (strncmp(telegram, "1-0:1.8.2", strlen("1-0:1.8.2")) == 0)
    {
        CONSUMPTION_HIGH_TARIF = getValue(telegram, len, '(', '*');
    }
        
    // 1-0:2.8.1(000560.157*kWh)
    // 1-0:2.8.1 = Elektra teruglevering laag tarief (DSMR v4.0)
    if (strncmp(telegram, "1-0:2.8.1", strlen("1-0:2.8.1")) == 0)
    {
        RETURNDELIVERY_LOW_TARIF = getValue(telegram, len, '(', '*');
    }

    // 1-0:2.8.2(000560.157*kWh)
    // 1-0:2.8.2 = Elektra teruglevering hoog tarief (DSMR v4.0)
    if (strncmp(telegram, "1-0:2.8.2", strlen("1-0:2.8.2")) == 0)
    {
        RETURNDELIVERY_HIGH_TARIF = getValue(telegram, len, '(', '*');
    }

    // 1-0:1.7.0(00.424*kW) Actueel verbruik
    // 1-0:1.7.x = Electricity consumption actual usage (DSMR v4.0)
    if (strncmp(telegram, "1-0:1.7.0", strlen("1-0:1.7.0")) == 0)
    {
        ACTUAL_CONSUMPTION = getValue(telegram, len, '(', '*');
    }

    // 1-0:2.7.0(00.000*kW) Actuele teruglevering (-P) in 1 Watt resolution
    if (strncmp(telegram, "1-0:2.7.0", strlen("1-0:2.7.0")) == 0)
    {
        ACTUAL_RETURNDELIVERY = getValue(telegram, len, '(', '*');
    }

    // 1-0:21.7.0(00.378*kW)
    // 1-0:21.7.0 = Instantaan vermogen Elektriciteit levering L1
    if (strncmp(telegram, "1-0:21.7.0", strlen("1-0:21.7.0")) == 0)
    {
        L1_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');
    }

    // 1-0:41.7.0(00.378*kW)
    // 1-0:41.7.0 = Instantaan vermogen Elektriciteit levering L2
    if (strncmp(telegram, "1-0:41.7.0", strlen("1-0:41.7.0")) == 0)
    {
        L2_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');
    }

    // 1-0:61.7.0(00.378*kW)
    // 1-0:61.7.0 = Instantaan vermogen Elektriciteit levering L3
    if (strncmp(telegram, "1-0:61.7.0", strlen("1-0:61.7.0")) == 0)
    {
        L3_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');
    }

    // 1-0:31.7.0(002*A)
    // 1-0:31.7.0 = Instantane stroom Elektriciteit L1
    if (strncmp(telegram, "1-0:31.7.0", strlen("1-0:31.7.0")) == 0)
    {
        L1_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');
    }
    // 1-0:51.7.0(002*A)
    // 1-0:51.7.0 = Instantane stroom Elektriciteit L2
    if (strncmp(telegram, "1-0:51.7.0", strlen("1-0:51.7.0")) == 0)
    {
        L2_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');
    }
    // 1-0:71.7.0(002*A)
    // 1-0:71.7.0 = Instantane stroom Elektriciteit L3
    if (strncmp(telegram, "1-0:71.7.0", strlen("1-0:71.7.0")) == 0)
    {
        L3_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');
    }

    // 1-0:32.7.0(232.0*V)
    // 1-0:32.7.0 = Voltage L1
    if (strncmp(telegram, "1-0:32.7.0", strlen("1-0:32.7.0")) == 0)
    {
        L1_VOLTAGE = getValue(telegram, len, '(', '*');
    }
    // 1-0:52.7.0(232.0*V)
    // 1-0:52.7.0 = Voltage L2
    if (strncmp(telegram, "1-0:52.7.0", strlen("1-0:52.7.0")) == 0)
    {
        L2_VOLTAGE = getValue(telegram, len, '(', '*');
    }   
    // 1-0:72.7.0(232.0*V)
    // 1-0:72.7.0 = Voltage L3
    if (strncmp(telegram, "1-0:72.7.0", strlen("1-0:72.7.0")) == 0)
    {
        L3_VOLTAGE = getValue(telegram, len, '(', '*');
    }

    // 0-1:24.2.1(150531200000S)(00811.923*m3)
    // 0-1:24.2.1 = Gas (DSMR v4.0) on Kaifa MA105 meter
    if (strncmp(telegram, "0-1:24.2.1", strlen("0-1:24.2.1")) == 0)
    {
        GAS_METER_M3 = getValue(telegram, len, '(', '*');
    }

    // 0-0:96.14.0(0001)
    // 0-0:96.14.0 = Actual Tarif
    if (strncmp(telegram, "0-0:96.14.0", strlen("0-0:96.14.0")) == 0)
    {
        ACTUAL_TARIF = getValue(telegram, len, '(', ')');
    }

    // 0-0:96.7.21(00003)
    // 0-0:96.7.21 = Aantal onderbrekingen Elektriciteit
    if (strncmp(telegram, "0-0:96.7.21", strlen("0-0:96.7.21")) == 0)
    {
        SHORT_POWER_OUTAGES = getValue(telegram, len, '(', ')');
    }

    // 0-0:96.7.9(00001)
    // 0-0:96.7.9 = Aantal lange onderbrekingen Elektriciteit
    if (strncmp(telegram, "0-0:96.7.9", strlen("0-0:96.7.9")) == 0)
    {
        LONG_POWER_OUTAGES = getValue(telegram, len, '(', ')');
    }

    // 1-0:32.32.0(00000)
    // 1-0:32.32.0 = Aantal korte spanningsdalingen Elektriciteit in fase 1
    if (strncmp(telegram, "1-0:32.32.0", strlen("1-0:32.32.0")) == 0)
    {
        SHORT_POWER_DROPS = getValue(telegram, len, '(', ')');
    }

    // 1-0:32.36.0(00000)
    // 1-0:32.36.0 = Aantal korte spanningsstijgingen Elektriciteit in fase 1
    if (strncmp(telegram, "1-0:32.36.0", strlen("1-0:32.36.0")) == 0)
    {
        SHORT_POWER_PEAKS = getValue(telegram, len, '(', ')');
    }

    return validCRCFound;
}

void read_p1_hardwareserial()
{
    if (mySerial.available())
    {
        memset(telegram, 0, sizeof(telegram));

        while (mySerial.available())
        {
            int len = mySerial.readBytesUntil('\n', telegram, P1_MAXLINELENGTH);

            processLine(len);
        }
    }
}

void processLine(int len) {
    telegram[len] = '\n';
    telegram[len + 1] = 0;
    yield();

    bool result = decode_telegram(len + 1);
    if (result) {
        send_data_to_broker();
        LAST_UPDATE_SENT = millis();
    }
}

////////////////////////////////  SETUP  /////////////////////////////////////////
void setup() {
  Serial.begin(115200);
  button.setDebounceTime(50); // set debounce time to 50 milliseconds
  pinMode(BTN_SAVE, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);

      // Setup a hw serial connection for communication with the P1 meter and logging (not using inversion)
//    Serial.begin(BAUD_RATE, SERIAL_8N1, 115200);
    Serial.setRxBufferSize(1024);
    Serial.println("");
    Serial.println("Swapping UART0 RX to inverted");
    Serial.flush();

    // Invert the RX serialport by setting a register value, this way the TX might continue normally allowing the serial monitor to read println's
    mySerial.begin(BAUD,SERIAL_8N1,RX_PIN,TX_PIN, true);
    Serial.println("Serial port is ready to recieve.");

  
  // FILESYSTEM INIT
  if (startFilesystem()){
    // Load configuration (if not present, default will be created when webserver will start)
    if (loadOptions())
      Serial.println(F("Application option loaded"));
    else
      Serial.println(F("Application options NOT loaded!"));
  }

  server.setSetupPageTitle("Instellingen P1 Meter");
  server.setSetupPageLogo(logo_png, sizeof(logo_png), "image/png", false);
  server.closeSetupConfiguration();

  // Try to connect to stored SSID, start AP with captive portal if fails after timeout
  IPAddress myIP = server.startWiFi(15000);
  if (!myIP) {
    Serial.println("\n\nNo WiFi connection, start AP and Captive Portal\n");
    server.startCaptivePortal("ESP_AP", "123456789", "/setup");
    myIP = WiFi.softAPIP();
    captiveRun = true;
    LEDSpeed = 250;
  } else {
    LEDSpeed = 1000;
    captiveRun = true;
  }

  // Add custom page handlers to webserver
  server.on("/reload", HTTP_GET, handleLoadOptions);

  // Configure /setup page and start Web Server
  server.addOptionBox("Device Setup");
  server.addOption(USER_LABEL, Username);
  server.addOption(PASSWORD_LABEL, Password);
  server.addOption(EXTERNAL_ANTENNA_LABEL, External_Antenna);
  server.addComment(EXTERNAL_ANTENNA_LABEL, "Restart is necessary after changing values.");
  
  server.addOptionBox("MQTT Setup");
  server.addOption(MQTT_ENABLED_LABEL, MQTT_Enabled);
  server.addOption(MQTT_CLIENT_LABEL, MQTT_Client);
  server.addOption(MQTT_SERVER_LABEL, MQTT_Server);
  server.addOption(MQTT_PORT_LABEL, MQTT_Port);
  server.addOption(MQTT_USER_LABEL, MQTT_User);
  server.addOption(MQTT_PASSWORD_LABEL, MQTT_Password);
  server.addOption(MQTT_PREFIX_LABEL, MQTT_Prefix);
  server.addComment(MQTT_PREFIX_LABEL, "Restart is necessary after changing values.");

  // Enable ACE FS file web editor and add FS info callback function
  server.enableFsCodeEditor();
  #ifdef ESP32
  server.setFsInfoCallback(getFsInfo);
  #endif

  // set /setup and /edit page authentication
  server.setAuthentication(Username.c_str(), Password.c_str());

   // Start server with custom websocket event handler
  server.init(onWsEvent);
  Serial.print(F("ESP Web Server started on IP Address: "));
  Serial.println(server.getServerIP());

  if (External_Antenna) {
    pinMode(3, OUTPUT);    // RF switch power on
    digitalWrite(3, LOW);
    pinMode(14, OUTPUT);   // select external antenna
    digitalWrite(14, HIGH);
    Serial.println("External antenna selected.");
 } else {
    pinMode(14, INPUT);    // select on-board antenna
    pinMode(3, INPUT);   // RF switch power off
    Serial.println("Internal antenna selected.");
 }
 // Get and print Wifi strenght
 long rssi = WiFi.RSSI();
 Serial.print("WiFi Strength (RSSI): ");
 if ((rssi <= -30) && (rssi > -50)) {
   Serial.print("***** (");
   Serial.print(rssi);
   Serial.print("dBm.)");
 } else   if ((rssi <= -50) && (rssi > -60)) {
   Serial.print("****. (");
   Serial.print(rssi);
   Serial.print("dBm.)");
 } else   if ((rssi <= -60) && (rssi > -70)) {
   Serial.print("***.. (");
   Serial.print(rssi);
   Serial.print("dBm.)");
 } else   if ((rssi <= -70) && (rssi > -80)) {
   Serial.print("**... (");
   Serial.print(rssi);
   Serial.print("dBm.)");
 } else   if ((rssi <= -80) && (rssi > -90)) {
   Serial.print("*.... (");
   Serial.print(rssi);
   Serial.print("dBm.)");
 } else   if ((rssi <= -90)) {
   Serial.print("..... (");
   Serial.print(rssi);
   Serial.print("dBm.)");
 }

   // Create a unique mqttClient ID and in/out topics
  snprintf(clientId, sizeof(clientId), "ESP-%llX", ESP.getEfuseMac());
  snprintf(inTopic, sizeof(inTopic), "%s/input", MQTT_Prefix.c_str());
  snprintf(outTopic, sizeof(outTopic), "%s/output", MQTT_Prefix.c_str());

  Serial.print("MQTT CLiend ID: ");
  Serial.println(clientId);
  Serial.print("Publish output topic: ");
  Serial.println(outTopic);
  Serial.print("Subscribe input topic: ");
  Serial.println(inTopic);

  // Set MQTT server and callback function
  mqttClient.setServer(MQTT_Server.c_str(), MQTT_Port.toInt());
  mqttClient.setCallback(mqttCallback);
}

////////////////////////////////  LOOP  /////////////////////////////////////////
void loop() {

  long now = millis();

      // Send ESP system time (epoch) and heap stats to WS client
  static uint32_t sendToClientTime;
  if (millis() - sendToClientTime > LEDSpeed ) {
    sendToClientTime = millis();
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));

    time_t now = time(nullptr);
    using namespace AsyncFSWebServer;
    CJSON::Json doc;
    doc.setBool("addPoint", true);
    doc.setNumber("timestamp", (double)now);
#ifdef ESP32
    ACTUAL_SUMPOWER = ACTUAL_CONSUMPTION - ACTUAL_RETURNDELIVERY;
    doc.setNumber("ACTUAL_CONSUMPTION", (double)ACTUAL_CONSUMPTION);
    doc.setNumber("ACTUAL_RETURNDELIVERY", (double)ACTUAL_RETURNDELIVERY);
    doc.setNumber("ACTUAL_SUMPOWER", (double)ACTUAL_SUMPOWER);
#elif defined(ESP8266)
    uint32_t free;
    uint32_t max;
    ESP.getHeapStats(&free, &max, nullptr);
    doc.setNumber("totalHeap", (double)free);
    doc.setNumber("maxBlock", (double)max);
#endif
    String msg = doc.serialize();
    server.wsBroadcast(msg.c_str());
  }

  if (captiveRun)
    server.updateDNS();

  // Handle MQTT client
  if (MQTT_Enabled) {
    if (!mqttClient.connected()) {
      // Client not connected, try to reconnect every 5 seconds
      mqttReconnect();
    } else {
      // Client connected
      mqttClient.loop();
      // Publish a new message every 5 seconds
      static uint32_t lastMsgTime = millis();
      static uint16_t value = 0;
      if (millis() - lastMsgTime > 5000) {
        lastMsgTime = millis();

        char payload[64];
        snprintf(payload, sizeof(payload), "Hello World from %s #%d", clientId, ++value);

        Serial.print("Publish message: ");
        Serial.println(payload);
        mqttClient.publish(outTopic, payload);
        read_p1_hardwareserial();
      }
    }
  }

// read reset button SHORT Press (>1 & <2,5 second restart, >10 seconds (led stays on factory reset))
  button.loop(); // MUST call the loop() function first

  if (button.isPressed()) {
    pressedTime = millis();
    isPressing = true;
  }
  
  long pressingDuration = millis() - pressedTime;
  
  if ((isPressing == true) && (pressingDuration > LONG_PRESS_TIME))
      digitalWrite(LED_BUILTIN, LOW);

  if (button.isReleased()) {
    releasedTime = millis();
    isPressing = false;

    long pressDuration = releasedTime - pressedTime;

    if ((pressDuration > SHORT_PRESS_TIME_MIN) && (pressDuration < SHORT_PRESS_TIME_MAX)) {
      ESP.restart();
    }

    if (pressDuration > LONG_PRESS_TIME) {
      server.clearConfigFile();
      ESP.restart();
    }
  }
 
  if (now - LAST_UPDATE_SENT > UPDATE_INTERVAL) {
    read_p1_hardwareserial();
  }
}