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 secondsconst 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 GPIO9WiFiClient espClient;PubSubClient mqttClient(espClient);//////////////////////////////// Define variables /////////////////////////////////////////bool captiveRun = false;// Setup SettingsString Username = "admin";String Password = "admin";bool External_Antenna = true;// MQTT Settingsbool 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 ESP32void getFsInfo(fsInfo_t* fsInfo) {fsInfo->fsName = "LittleFS";fsInfo->totalBytes = LittleFS.totalBytes();fsInfo->usedBytes = LittleFS.usedBytes();}#endif// * Send a message to a broker topicvoid 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 connectionreturn;static uint32_t lastConnectionTime = 5000;if (millis() - lastConnectionTime < 5000) // Wait 5 seconds before retryingreturn;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 connectSerial.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;}elseSerial.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 bitfor (int i = 8; i != 0; i--){// * If the LSB is setif ((crc & 0x0001) != 0){// * Shift right and XOR 0xA001crc >>= 1;crc ^= 0xA001;}// * Else LSB is not setelse// * Just shift rightcrc >>= 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 longreturn (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 calculationcurrentCRC = CRC16(0x0000,(unsigned char *) telegram+startChar, len-startChar);}else if (endChar >= 0){// * Add to crc calccurrentCRC = 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!"));elseSerial.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 resolutionif (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 L1if (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 L2if (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 L3if (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 L1if (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 L2if (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 L3if (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 L1if (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 L2if (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 L3if (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 meterif (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 Tarifif (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 Elektriciteitif (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 Elektriciteitif (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 1if (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 1if (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 millisecondspinMode(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'smySerial.begin(BAUD,SERIAL_8N1,RX_PIN,TX_PIN, true);Serial.println("Serial port is ready to recieve.");// FILESYSTEM INITif (startFilesystem()){// Load configuration (if not present, default will be created when webserver will start)if (loadOptions())Serial.println(F("Application option loaded"));elseSerial.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 timeoutIPAddress 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 webserverserver.on("/reload", HTTP_GET, handleLoadOptions);// Configure /setup page and start Web Serverserver.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 functionserver.enableFsCodeEditor();#ifdef ESP32server.setFsInfoCallback(getFsInfo);#endif// set /setup and /edit page authenticationserver.setAuthentication(Username.c_str(), Password.c_str());// Start server with custom websocket event handlerserver.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 ondigitalWrite(3, LOW);pinMode(14, OUTPUT); // select external antennadigitalWrite(14, HIGH);Serial.println("External antenna selected.");} else {pinMode(14, INPUT); // select on-board antennapinMode(3, INPUT); // RF switch power offSerial.println("Internal antenna selected.");}// Get and print Wifi strenghtlong 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 topicssnprintf(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 functionmqttClient.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 clientstatic 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 ESP32ACTUAL_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);#endifString msg = doc.serialize();server.wsBroadcast(msg.c_str());}if (captiveRun)server.updateDNS();// Handle MQTT clientif (MQTT_Enabled) {if (!mqttClient.connected()) {// Client not connected, try to reconnect every 5 secondsmqttReconnect();} else {// Client connectedmqttClient.loop();// Publish a new message every 5 secondsstatic 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 firstif (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();}}