diff --git a/ZgatewayRF_RHASK.ino b/ZgatewayRF_RHASK.ino new file mode 100644 index 0000000..57f6943 --- /dev/null +++ b/ZgatewayRF_RHASK.ino @@ -0,0 +1,347 @@ +/* + OpenMQTTGateway - ESP8266 or Arduino program for home automation + + Act as a wifi or ethernet gateway between your 433mhz/infrared IR signal and a MQTT broker + Send and receiving command by MQTT + + This gateway enables to: + - receive MQTT data from a topic and send RF 433Mhz signal corresponding to the received MQTT data + - publish MQTT data to a different topic related to received 433Mhz signal + + Copyright: (c)Florian ROBERT + + This file is part of OpenMQTTGateway. + + OpenMQTTGateway is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenMQTTGateway is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifdef ZgatewayRF_RHASK + +#include +#ifdef RH_HAVE_HARDWARE_SPI +#include // Not actually used but needed to compile +#endif + +int RollingCounter = 0; + +//RH_ASK driver(2000,RF_RECEIVER_PIN,RF_EMITTER_PIN); +RH_ASK driver(500,4,0); + +void setupRF(){ + //RF init parameters + Serial.println("Setup RF"); + if (!driver.init()) + { + + } + else + { + + } +// #ifdef RH_HAVE_SERIAL +// trc(F("init failed")); +// #else +// ; +// trc(F("RF_EMITTER_PIN ")); +// trc(RF_EMITTER_PIN); +// trc(F("RF_RECEIVER_PIN ")); +// trc(RF_RECEIVER_PIN); +// trc(F("RF setup ok")); +// #endif + +} + +//uint8_t buf_Stored[RH_ASK_MAX_MESSAGE_LEN]; + +int RC_old; +void RFtoMQTT(){ + uint8_t buf[RH_ASK_MAX_MESSAGE_LEN]; + uint8_t buflen = sizeof(buf); + + if (driver.recv(buf, &buflen)) // Non-blocking + { + // Debug + String RFTopicDebug; + RFTopicDebug = "rf/sensors/"; + char RFTopicCharDebug[50]; + RFTopicDebug.toCharArray(RFTopicCharDebug,50); +// pubMQTT(RFTopicCharDebug,"Received Sth"); + // Serial.println("Received sth"); + // Debug End + + + int adressIn, parameterIn, RCin; + float payloadIn; + MessageDecrypt(buf, &adressIn, ¶meterIn, &payloadIn, &RCin); + + // debug + Serial.print("RC_in: ");Serial.println(RCin); +// Serial.print("Buf: ");Serial.println(buf); + Serial.print("Payload: ");Serial.println(payloadIn); + + // debug +// trc("Adresse: ");trc(adressIn); + // trc("Parameter: ");trc(parameterIn); + // trc("Payload: ");trc(payloadIn); + // end debug + Log.notice(F("Message: %i" CR), adressIn); + Log.notice(F("Message: %i" CR), parameterIn); + Log.notice(F("Message: %i" CR), RCin); + + + // Compare old vs new value + bool newValue = false; + if ((!isAduplicateSignal((uint64_t)buf) || RCin != RC_old) && (unsigned long)buf != 0) + { + newValue = true; + } + else + { +// trc("Don't publish"); + + } + storeSignalValue((uint64_t)buf); // Store value to compare with new values +// Serial.print("RC in: ");Serial.print(RCin);Serial.print("RC old: ");Serial.println(RC_old); + RC_old = RCin; + + if(newValue) + { + // match Sensor number in the array with sensor ID + int SensorNo; + bool FoundSensor = false; + for(SensorNo = 0;SensorNoMQTT +// int adressIn, payloadIn, parameterIn; +// MessageDecrypt(MQTTvalue, &adressIn, ¶meterIn, &payloadIn); +// // match Sensor number in the array with sensor ID +// int SensorNo; +// bool FoundSensor = false; +// for(SensorNo = 0;SensorNo 4294967295 + String topic = topicOri; + //trc(F("Test")); + + // Get the name of the target + String TargetName; + String ParameterName; + int posDivider = topic.lastIndexOf("/"); + if(posDivider != -1) + { + ParameterName = topic.substring(posDivider+1); + String topicSubstring = topic.substring(0,posDivider); + posDivider = topicSubstring.lastIndexOf("/"); + if(posDivider != -1) + TargetName = topicSubstring.substring(posDivider+1); + } + +// trc(F("Target Name: ")); +// trc(TargetName); +// trc(ParameterName); + + // Get target number in array by sensor name + int TargetNo; + bool TargetFound = false; + for(TargetNo = 0;TargetNo<(sizeof(RFTargetNames)/sizeof(RFTargetNames[0]));TargetNo++) + { + if(RFTargetNames[TargetNo] == TargetName) + { + TargetFound = true; + break; + } + } + + // trc(TargetNo); + + + if(TargetFound) + { + // find parameter ID + int ParId; + for(ParId=0;ParId<(sizeof(ParameterIds)/sizeof(ParameterIds[0]));ParId++) + { + if(ParameterIds[ParId] == ParameterName) + break; + } + // Search for target adress + + byte MessageOut[8]; + MessageEncrypt(RFTargetIDs[TargetNo], ParId, data, MessageOut); + for(int i=0;i<=RF_EMITTER_REPEAT;i++) + { + driver.send(MessageOut,8); + driver.waitPacketSent(); + } + } +} + + +bool MessageDecrypt(byte ByteArray[], int *adress, int *parameter, float *payload, int *RCin) +{ + +// Serial.print("BytAr: ");Serial.println(ByteArray[0]); + + *adress = (int)ByteArray[0]; + // *parameter = (int)ByteArray[1]; + + byte arrayFloat[4]; +// arrayFloat[0] = ByteArray[2]; +// arrayFloat[1] = ByteArray[3]; +// arrayFloat[2] = ByteArray[4]; +// arrayFloat[3] = ByteArray[5]; + + arrayFloat[0] = ByteArray[1]; + arrayFloat[1] = ByteArray[2]; + arrayFloat[2] = ByteArray[3]; + arrayFloat[3] = ByteArray[4]; + +// int i = arrayFloat[0] | (arrayFloat[1] << 8) | (arrayFloat[2] << 16) | (arrayFloat[3] << 24); + float testfloat; + testfloat = *((float*) (arrayFloat)); + *payload = testfloat; + + //testfloat = *((float*)(arrayFloat)); +// Serial.print("Number: ");Serial.println(testfloat); +// Serial.print("Byte: ");Serial.println(arrayFloat[1]); +// byte CRCReceive = ByteArray[6]; + + // CRC Check +// byte CRCValue = CRC8(ByteArray, 6); + +// bool CRCCheckOK = false; +// if(CRCValue == CRCReceive) +// { +// trc(F("CRC OK")); +// CRCCheckOK = true; +// } + +// *RCin = (int)ByteArray[7]; + *RCin = (int)ByteArray[5]; +// Serial.print("RC in: ");Serial.println(RCin); + +// Serial.println(messageInput[0]); + + // return CRCCheckOK; + return true; +} + +void MessageEncrypt(int adressIn, int parameterIn, float payloadIn, byte pByteArray[]) +{ + // byte ByteArray[6]; + pByteArray[0] = (byte)adressIn; + pByteArray[1] = (byte)parameterIn; + + byte * payloadBArray = (byte *)&payloadIn; + pByteArray[2] = payloadBArray[0]; + pByteArray[3] = payloadBArray[1]; + pByteArray[4] = payloadBArray[2]; + pByteArray[5] = payloadBArray[3]; + + // long l = *(long*) &payloadIn; + + byte CRCValue = CRC8(pByteArray, 6); + pByteArray[6]= CRCValue; + + RollingCounter = RollingCounter + 1; + if(RollingCounter > 2) + RollingCounter = 0; + + pByteArray[7] = RollingCounter; +} + +//CRC-8 - based on the CRC8 formulas by Dallas/Maxim +//code released under the therms of the GNU GPL 3.0 license +byte CRC8(const byte *data, byte len) { + byte crc = 0x00; + while (len--) { + byte extract = *data++; + for (byte tempI = 8; tempI; tempI--) { + byte sum = (crc ^ extract) & 0x01; + crc >>= 1; + if (sum) { + crc ^= 0x8C; + } + extract >>= 1; + } + } + return crc; +} + +#endif diff --git a/config_RF_RHASK.h b/config_RF_RHASK.h new file mode 100644 index 0000000..e8913a6 --- /dev/null +++ b/config_RF_RHASK.h @@ -0,0 +1,70 @@ +/*-------------------RF sensor + hardware settings----------------------*/ +const int RFSensorIDs[] = {1,2,3,4,5,6,7}; +const String RFSensorNames[] = {"RainSensor", "Humidity1", "RainSensor1", "LuxSensor1","MoistureSensor1","MoistureSensor_Test","Doorbell"}; +const String RFSensorType[] = {"RainMass", "Humidity", "Rain", "Light","Moisture","Moisture","Door"}; +const String RFSensorBaseName = "rf/sensors/"; +const String RFTargetBaseName = "rf/target/"; + +const int RFTargetIDs[] = {5,6,7}; +const String RFTargetNames[] = {"Light1","Light2","LED1"}; +const String RFTargetType[] = {"Light", "Light","LED"}; +const String ParameterIds[] = {"red","green","blue","brightness", "effect", "effectparameter"}; + +/*-------------------RF topics & parameters----------------------*/ +//433Mhz MQTT Subjects and keys +#define subjectMQTTtoRF Base_Topic Gateway_Name "/commands/MQTTto433" +#define subjectRFtoMQTT Base_Topic Gateway_Name "/433toMQTT" +#define subjectGTWRFtoMQTT Base_Topic Gateway_Name "/433toMQTT" +#define RFprotocolKey "433_" // protocol will be defined if a subject contains RFprotocolKey followed by a value of 1 digit +#define RFbitsKey "RFBITS_" // bits will be defined if a subject contains RFbitsKey followed by a value of 2 digits +#define repeatRFwMQTT false // do we repeat a received signal by using mqtt with RF gateway + +/* +RF supported protocols +433_1 +433_2 +433_3 +433_4 +433_5 +433_6 +*/ +#define RFpulselengthKey "PLSL_" // pulselength will be defined if a subject contains RFprotocolKey followed by a value of 3 digits +// subject monitored to listen traffic processed by other gateways to store data and avoid ntuple +#define subjectMultiGTWRF "+/+/433toMQTT" +//RF number of signal repetition +#define RF_EMITTER_REPEAT 3 + +/*-------------------RF2 topics & parameters----------------------*/ +//433Mhz newremoteswitch MQTT Subjects and keys +#define subjectMQTTtoRF2 Base_Topic Gateway_Name "/commands/MQTTtoRF2" +#define subjectRF2toMQTT Base_Topic Gateway_Name "/RF2toMQTT" +#define subjectGTWRF2toMQTT Base_Topic Gateway_Name "/433toMQTT" +#define RF2codeKey "CODE_" // code will be defined if a subject contains RF2codeKey followed by a value of 7 digits +#define RF2periodKey "PERIOD_" // period will be defined if a subject contains RF2periodKey followed by a value of 3 digits +#define RF2unitKey "UNIT_" // number of your unit value will be defined if a subject contains RF2unitKey followed by a value of 1-2 digits +#define RF2groupKey "GROUP_" // number of your group value will be defined if a subject contains RF2groupKey followed by a value of 1 digit +#define RF2dimKey "DIM" // number of your dim value will be defined if a subject contains RF2dimKey and the payload contains the dim value as digits + +/*-------------------ESPPiLight topics & parameters----------------------*/ +//433Mhz Pilight MQTT Subjects and keys +#define subjectMQTTtoPilight Base_Topic Gateway_Name "/commands/MQTTtoPilight" +#define subjectPilighttoMQTT Base_Topic Gateway_Name "/PilighttoMQTT" +#define subjectGTWPilighttoMQTT Base_Topic Gateway_Name "/PilighttoMQTT" +#define PilightRAW "RAW" + +/*-------------------PIN DEFINITIONS----------------------*/ +//#ifdef ESP8266 +// #define RF_RECEIVER_PIN 5 // D3 on nodemcu +// #define RF_EMITTER_PIN 4 // RX on nodemcu if it doesn't work with 3, try with 4 (D2) +//#elif ESP32 +// #define RF_RECEIVER_PIN 13 // D13 on DOIT ESP32 +// #define RF_EMITTER_PIN 12 // D12 on DOIT ESP32 +//#elif __AVR_ATmega2560__ +// #define RF_RECEIVER_PIN 1 //1 = D3 on mega +// #define RF_EMITTER_PIN 4 +//#else +// //IMPORTANT NOTE: On arduino UNO connect IR emitter pin to D9 , comment #define IR_USE_TIMER2 and uncomment #define IR_USE_TIMER1 on library IRremote/boarddefs.h so as to free pin D3 for RF RECEIVER PIN +// //RF PIN definition +// #define RF_RECEIVER_PIN 1 //1 = D3 on arduino +// #define RF_EMITTER_PIN 4 //4 = D4 on arduino +//#endif diff --git a/main.ino b/main.ino new file mode 100644 index 0000000..fce9190 --- /dev/null +++ b/main.ino @@ -0,0 +1,3618 @@ +/* + OpenMQTTGateway - ESP8266 or Arduino program for home automation + + Act as a gateway between your 433mhz, infrared IR, BLE, LoRa signal and one interface like an MQTT broker + Send and receiving command by MQTT + + This program enables to: + - receive MQTT data from a topic and send signal (RF, IR, BLE, GSM) corresponding to the received MQTT data + - publish MQTT data to a different topic related to received signals (RF, IR, BLE, GSM) + + Copyright: (c)Florian ROBERT + + This file is part of OpenMQTTGateway. + + OpenMQTTGateway is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenMQTTGateway is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "User_config.h" +#define ENV_NAME "Wemos_D1_Mini" + +enum GatewayState { + WAITING_ONBOARDING, + ONBOARDING, + OFFLINE, + NTWK_CONNECTED, + BROKER_CONNECTED, + PROCESSING, + NTWK_DISCONNECTED, + BROKER_DISCONNECTED, + LOCAL_OTA_IN_PROGRESS, + REMOTE_OTA_IN_PROGRESS, + SLEEPING, + ERROR +}; +GatewayState gatewayState = GatewayState::WAITING_ONBOARDING; + +// Macros and structure to enable the duplicates removing on the following gateways +#if defined(ZgatewayRF) || defined(ZgatewayRF_RHASK) || defined(ZgatewayIR) || defined(ZgatewaySRFB) || defined(ZgatewayWeatherStation) || defined(ZgatewayRTL_433) +// array to store previous received RFs, IRs codes and their timestamps +struct ReceivedSignal { + uint64_t value; + uint32_t time; +}; + +ReceivedSignal receivedSignal[] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}; + +# define struct_size (sizeof(receivedSignal) / sizeof(ReceivedSignal)) +#endif + +//Time used to wait for an interval before measures +unsigned long timer_sys_measures = 0; + +// Time used to wait before system checkings +unsigned long timer_sys_checks = 0; + +// First Start of offline mode modules +bool firstStart = true; + +#define ARDUINOJSON_USE_LONG_LONG 1 +#define ARDUINOJSON_ENABLE_STD_STRING 1 + +#include +int queueLength = -1; // We want to give one cycle of the loop before starting modules that are big msgs producers (example BT), to avoid queue overloading +unsigned long queueLengthSum = 0; +unsigned long blockedMessages = 0; +unsigned long receivedMessages = 0; +int maxQueueLength = 0; +#ifndef QueueSize +# define QueueSize 18 +#endif + +/** + * Deep-sleep for the ESP8266 & ESP32 we need some form of indicator that we have posted the measurements and am ready to deep sleep. + * Set this to true in the sensor code after publishing the measurement. + */ +bool ready_to_sleep = false; + +#include +#include +#include + +#include + +#include "LEDManager.h" +#include "TheengsUtils.h" + +LEDManager ledManager; + +struct JsonBundle { + StaticJsonDocument doc; +}; + +std::queue jsonQueue; + +#ifdef ESP32 +# include +// Mutex to protect the queue +SemaphoreHandle_t xQueueMutex; +// Mutex to protect mqtt publish +SemaphoreHandle_t xMqttMutex; +#endif + +StaticJsonDocument modulesBuffer; +JsonArray modules = modulesBuffer.to(); +bool ethConnected = false; + +#ifndef ZgatewayGFSunInverter +// Arduino IDE compiles, it automatically creates all the header declarations for all the functions you have in your *.ino file. +// Unfortunately it ignores #if directives. +// This is a simple workaround for this problem. +struct GfSun2000Data {}; +#endif + +// Modules config inclusion +#if defined(ZwebUI) && defined(ESP32) +# include "config_WebUI.h" +#endif +#if defined(ZgatewayRF) || defined(ZgatewayRF2) || defined(ZgatewayPilight) || defined(ZactuatorSomfy) || defined(ZgatewayRTL_433) +# include "config_RF.h" +#endif +#ifdef ZgatewayRF_RHASK + #include "config_RF_RHASK.h" +#endif +#ifdef ZgatewayWeatherStation +# include "config_WeatherStation.h" +#endif +#ifdef ZgatewayGFSunInverter +# include "config_GFSunInverter.h" +#endif +#ifdef ZgatewayLORA +# include "config_LORA.h" +#endif +#ifdef ZgatewaySRFB +# include "config_SRFB.h" +#endif +#ifdef ZgatewayBT +# include "config_BT.h" +#endif +#ifdef ZgatewayIR +# include "config_IR.h" +#endif +#ifdef Zgateway2G +# include "config_2G.h" +#endif +#ifdef ZactuatorONOFF +# include "config_ONOFF.h" +#endif +#ifdef ZsensorINA226 +# include "config_INA226.h" +#endif +#ifdef ZsensorHCSR501 +# include "config_HCSR501.h" +#endif +#ifdef ZsensorADC +# include "config_ADC.h" +#endif +#ifdef ZsensorBH1750 +# include "config_BH1750.h" +#endif +#ifdef ZsensorMQ2 +# include "config_MQ2.h" +#endif +#ifdef ZsensorTEMT6000 +# include "config_TEMT6000.h" +#endif +#ifdef ZsensorTSL2561 +# include "config_TSL2561.h" +#endif +#ifdef ZsensorBME280 +# include "config_BME280.h" +#endif +#ifdef ZsensorHTU21 +# include "config_HTU21.h" +#endif +#ifdef ZsensorLM75 +# include "config_LM75.h" +#endif +#ifdef ZsensorAHTx0 +# include "config_AHTx0.h" +#endif +#ifdef ZsensorRN8209 +# include "config_RN8209.h" +#endif +#ifdef ZsensorHCSR04 +# include "config_HCSR04.h" +#endif +#ifdef ZsensorC37_YL83_HMRD +# include "config_C37_YL83_HMRD.h" +#endif +#ifdef ZsensorDHT +# include "config_DHT.h" +#endif +#ifdef ZsensorSHTC3 +# include "config_SHTC3.h" +#endif +#ifdef ZsensorDS1820 +# include "config_DS1820.h" +#endif +#ifdef ZgatewayRFM69 +# include "config_RFM69.h" +#endif +#ifdef ZsensorGPIOInput +# include "config_GPIOInput.h" +#endif +#ifdef ZsensorGPIOKeyCode +# include "config_GPIOKeyCode.h" +#endif +#ifdef ZsensorTouch +# include "config_Touch.h" +#endif +#ifdef ZmqttDiscovery +# include "config_mqttDiscovery.h" +#endif +#ifdef ZactuatorFASTLED +# include "config_FASTLED.h" +#endif +#ifdef ZactuatorPWM +# include "config_PWM.h" +#endif +#ifdef ZactuatorSomfy +# include "config_Somfy.h" +#endif +#if defined(ZboardM5STICKC) || defined(ZboardM5STICKCP) || defined(ZboardM5STACK) || defined(ZboardM5TOUGH) +# include "config_M5.h" +#endif +#if defined(ZdisplaySSD1306) +# include "config_SSD1306.h" +#endif +#if defined(ZgatewaySERIAL) +# include "config_SERIAL.h" +#endif +/*------------------------------------------------------------------------*/ + +void setupTLS(int index = CNT_DEFAULT_INDEX); + +char ota_pass[parameters_size] = gw_password; +#ifdef USE_MAC_AS_GATEWAY_NAME +# undef WifiManager_ssid +# undef ota_hostname +# define MAC_NAME_MAX_LEN 30 +char WifiManager_ssid[MAC_NAME_MAX_LEN]; +char ota_hostname[MAC_NAME_MAX_LEN]; +#endif +int failure_number_ntwk = 0; // number of failure connecting to network +int failure_number_mqtt = 0; // number of failure connecting to MQTT + +static unsigned long last_ota_activity_millis = 0; +// Global struct to store live SYS configuration data +SYSConfig_s SYSConfig; + +bool failSafeMode = false; +bool ProcessLock = true; // Process lock when we want to use a critical function like OTA for example +static bool mqttSetupPending = true; +static int cnt_index = CNT_DEFAULT_INDEX; + +#ifdef ESP32 +# include +# include +# include +# include +# include +# include + +bool BTProcessLock = true; // Process lock when we want to use a critical function like OTA for example, at start to true so as to wait for critical functions to be performed before BLE start + +# if !defined(NO_INT_TEMP_READING) +// ESP32 internal temperature reading +# include + +# include "rom/ets_sys.h" +# include "soc/rtc_cntl_reg.h" +# include "soc/sens_reg.h" +# endif +# ifdef ESP32_ETHERNET +# include +void WiFiEvent(WiFiEvent_t event); +# endif + +# include +# include +WiFiMulti wifiMulti; +# include +# ifdef MDNS_SD +# include +# endif + +#elif defined(ESP8266) +# include +# include +# include +# include +# include +# include +# include +X509List caCert; +# if MQTT_SECURE_SIGNED_CLIENT +X509List* pClCert = nullptr; +PrivateKey* pClKey = nullptr; +# endif +ESP8266WiFiMulti wifiMulti; +# ifdef MDNS_SD +# include +# endif + +#else +# include +#endif + +void handle_autodiscovery() { +#ifdef ZmqttDiscovery + static bool connectedOnce = false; + const unsigned long now = millis(); + + // at first connection we publish the discovery payloads + // or, when we have just re-connected (only when discovery_republish_on_reconnect is enabled) + const bool publishDiscovery = SYSConfig.discovery && (!connectedOnce || discovery_republish_on_reconnect); + + if (publishDiscovery) { + pubMqttDiscovery(); +# ifdef ZgatewayLORA + launchLORADiscovery(true); +# endif +# ifdef ZgatewayBT + launchBTDiscovery(true); +# endif +# ifdef ZgatewayRTL_433 + launchRTL_433Discovery(true); +# endif + } + + connectedOnce = true; +#endif +} + +#if MQTT_BROKER_MODE + +class MQTTServer : public PicoMQTT::Server { +public: + size_t get_client_count() const { + return clients.size(); + } + + bool connected() const { + return !clients.empty(); + } + +protected: + virtual void on_subscribe(const char* client_id, const char* topic) override { + // Whenever a client subscribes successfully to some topic, see if this is likely a subscription to a + // autodiscovery topic. If it is, fire handle_autodiscovery(). + const String pattern(topic); + const bool is_autodiscovery_subscription = (pattern == "#") || (pattern.startsWith(String(discovery_prefix) + "/")); + if (is_autodiscovery_subscription) + handle_autodiscovery(); + } +}; + +std::unique_ptr mqtt; +#else +std::unique_ptr< ::Client> eClient; +std::unique_ptr mqtt; +#endif + +template // Declared here to avoid pre-compilation issue (missing "template" in auto declaration by pio) +void Config_update(JsonObject& data, const char* key, T& var); +template +void Config_update(JsonObject& data, const char* key, T& var) { + if (data.containsKey(key)) { + if (var != data[key].as()) { + var = data[key].as(); + Log.notice(F("Config %s changed to: %T" CR), key, data[key].as()); + } else { + Log.notice(F("Config %s unchanged, currently: %T" CR), key, data[key].as()); + } + } +} + +/* + * Dispatch json messages towards the communication layer + * +*/ +bool jsonDispatch(JsonObject& data) { + bool res = false; + if (data.containsKey("origin") || data.containsKey("topic")) { + GatewayState previousGatewayState = gatewayState; + gatewayState = GatewayState::PROCESSING; +#if message_UTCtimestamp == true + data["UTCtime"] = TheengsUtils::UTCtimestamp(); +#endif +#if message_unixtimestamp == true + data["unixtime"] = TheengsUtils::unixtimestamp(); +#endif + if (data.containsKey("origin")) { + pubWebUI((char*)data["origin"].as(), data); + } + if (SYSConfig.mqtt && !SYSConfig.offline) { + res = pub(data); + } +#ifdef ZgatewaySERIAL + if (SYSConfig.serial) { + char jsonStr[JSON_MSG_BUFFER_MAX]; + serializeJson(data, jsonStr); + receivingDATA("", jsonStr); + res = true; // Return the state from receivingDATA + } +#endif + gatewayState = previousGatewayState; // restore the previous state + } else { + Log.error(F("No origin or topic in JSON filtered" CR)); + gatewayState = GatewayState::ERROR; + } + return res; +} + +// Add a document to the queue +boolean enqueueJsonObject(const StaticJsonDocument& jsonDoc, int timeout) { + receivedMessages++; + if (jsonDoc.size() == 0) { + Log.error(F("Empty JSON, skipping" CR)); + gatewayState = GatewayState::ERROR; + return true; + } + if (queueLength >= QueueSize) { + Log.warning(F("%d Doc(s) in queue, doc blocked" CR), queueLength); + blockedMessages++; + return false; + } + Log.trace(F("Enqueue JSON" CR)); + std::string jsonString; + serializeJson(jsonDoc, jsonString); +#ifdef ESP32 + // Semaphore check before enqueueing a document + if (xSemaphoreTake(xQueueMutex, pdMS_TO_TICKS(timeout)) == pdFALSE) { + Log.error(F("xQueueMutex not taken" CR)); + gatewayState = GatewayState::ERROR; + blockedMessages++; + return false; + } +#endif + jsonQueue.push(jsonString); +#ifdef ESP32 + xSemaphoreGive(xQueueMutex); +#endif + Log.trace(F("Queue length: %d" CR), jsonQueue.size()); + return true; +} + +// Semaphore check before enqueueing a document with default timeout QueueSemaphoreTimeOutLoop +bool enqueueJsonObject(const StaticJsonDocument& jsonDoc) { + return enqueueJsonObject(jsonDoc, QueueSemaphoreTimeOutLoop); +} + +#ifdef ESP32 +# include "mbedtls/sha256.h" + +std::string generateHash(const std::string& input) { + unsigned char hash[32]; + mbedtls_sha256((unsigned char*)input.c_str(), input.length(), hash, 0); + + char hashString[65]; // Room for null terminator + for (int i = 0; i < 32; ++i) { + sprintf(&hashString[i * 2], "%02x", hash[i]); + } + + return std::string(hashString); +} +#else +std::string generateHash(const std::string& input) { + return "Not implemented for ESP8266"; +} +#endif + +/* + * Add the jsonObject id as a topic to the jsonObject origin + * +*/ +void buildTopicFromId(JsonObject& Jsondata, const char* origin) { + if (!Jsondata.containsKey("id")) { + Log.error(F("No id in Jsondata" CR)); + gatewayState = GatewayState::ERROR; + return; + } + + std::string topic = Jsondata["id"].as(); + + // Replace ":" in topic + size_t pos = topic.find(":"); + while (pos != std::string::npos) { + topic.erase(pos, 1); + pos = topic.find(":", pos); + } +#ifdef ZgatewayBT + if (BTConfig.pubBeaconUuidForTopic && !BTConfig.extDecoderEnable && Jsondata.containsKey("model_id") && Jsondata["model_id"].as() == "IBEACON") { + if (Jsondata.containsKey("uuid")) { + topic = Jsondata["uuid"].as(); + } else { + Log.error(F("No uuid in Jsondata" CR)); + gatewayState = GatewayState::ERROR; + } + } + + if (BTConfig.extDecoderEnable && !Jsondata.containsKey("model")) + topic = BTConfig.extDecoderTopic.c_str(); +#endif + std::string subjectStr(origin); + topic = subjectStr + "/" + topic; + + Jsondata["origin"] = topic; + + Log.trace(F("Origin: %s" CR), Jsondata["origin"].as()); +} + +// Empty the documents queue +void emptyQueue() { + queueLength = jsonQueue.size(); + if (queueLength > maxQueueLength) { + maxQueueLength = queueLength; + } + if (queueLength <= 0) { + return; + } + Log.trace(F("Dequeue JSON" CR)); + DynamicJsonDocument jsonBuffer(JSON_MSG_BUFFER_MAX); + JsonObject obj = jsonBuffer.to(); +#ifdef ESP32 + if (xSemaphoreTake(xQueueMutex, pdMS_TO_TICKS(QueueSemaphoreTimeOutTask)) == pdFALSE) { + Log.error(F("xQueueMutex not taken" CR)); + gatewayState = GatewayState::ERROR; + return; + } +#endif + auto error = deserializeJson(jsonBuffer, jsonQueue.front()); + jsonQueue.pop(); +#ifdef ESP32 + xSemaphoreGive(xQueueMutex); +#endif + if (error) { + Log.error(F("deserialize jsonQueue.front() failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity()); + gatewayState = GatewayState::ERROR; + } else { + if (jsonDispatch(obj)) + queueLengthSum++; + } +} + +/** + * @brief Publish the payload on default MQTT topic. + * + * @param topicori suffix to add on default MQTT Topic + * @param payload the message to sends + * @param retainFlag true if you what a retain + */ +bool pub(const char* topicori, const char* payload, bool retainFlag) { + String topic = String(mqtt_topic) + String(gateway_name) + String(topicori); + return pubMQTT(topic.c_str(), payload, retainFlag); +} + +/** + * @brief Publish the payload on default MQTT topic + * + * @param topicori suffix to add on default MQTT Topic + * @param data The Json Object that represents the message + */ +bool pub(JsonObject& data) { + bool res = false; + bool ret = sensor_Retain; + if (data.containsKey("retain") && data["retain"].is()) { + ret = data["retain"]; + data.remove("retain"); + } + if (data.size() == 0) { + Log.error(F("Empty JSON, not published" CR)); + gatewayState = GatewayState::ERROR; + return res; + } + String topic; + if (data.containsKey("origin") && data["origin"].is()) { + topic = String(mqtt_topic) + String(gateway_name) + String(data["origin"].as()); + data.remove("origin"); + } else if (data.containsKey("topic") && data["topic"].is()) { + topic = data["topic"].as(); + if (data.containsKey("info_topic") && data["info_topic"].is()) { + // Sometimes it is necessary to provide information about the publishing topic, not just use it. + // This is the case, for example, for the RF2MQTT device trigger announcement message where the + // temporary variable info_topic provides information about the topic that will be used to publish the message, + // and it can be different of current message topic (This is a clever pun, I hope it's clear). + data["topic"].set(data["info_topic"]); + data.remove("info_topic"); + } else { + data.remove("topic"); + } + + } else { + Log.error(F("No topic or origin in JSON, not published" CR)); + gatewayState = GatewayState::ERROR; + return res; + } + +#if valueAsATopic +# ifdef ZgatewayPilight + String value = data["value"]; + String protocol = data["protocol"]; + if (value != "null" && value != 0) { + topic = topic + "/" + protocol + "/" + value; + } +# else + uint64_t value = data["value"]; + if (value != 0) { + topic = topic + "/" + TheengsUtils::toString(value); + } +# endif +#endif + +#if jsonPublishing + String dataAsString = ""; + serializeJson(data, dataAsString); + res = pubMQTT(topic.c_str(), dataAsString.c_str(), ret); +#endif + +#if simplePublishing + Log.trace(F("simplePub - ON" CR)); + // Loop through all the key-value pairs in obj + for (JsonPair p : data) { +# if defined(ESP8266) + yield(); +# endif + if (p.value().is() && strcmp(p.key().c_str(), "rssi") != 0) { //test rssi , bypass solution due to the fact that a int is considered as an uint64_t + if (strcmp(p.key().c_str(), "value") == 0) { // if data is a value we don't integrate the name into the topic + res = pubMQTT(topic, p.value().as()); + } else { // if data is not a value we integrate the name into the topic + res = pubMQTT(topic + "/" + String(p.key().c_str()), p.value().as()); + } + } else if (p.value().is()) { + res = pubMQTT(topic + "/" + String(p.key().c_str()), p.value().as()); + } else if (p.value().is()) { + res = pubMQTT(topic + "/" + String(p.key().c_str()), p.value().as()); + } else if (p.value().is()) { + res = pubMQTT(topic + "/" + String(p.key().c_str()), p.value().as()); + } + } +#endif + return res; +} + +/** + * @brief Publish the payload on default MQTT topic + * + * @param topicori suffix to add on default MQTT Topic + * @param payload the message to sends + */ +bool pub(const char* topicori, const char* payload) { + String topic = String(mqtt_topic) + String(gateway_name) + String(topicori); + return pubMQTT(topic, payload); +} + +/** + * @brief Low level MQTT functions without retain + * + * @param topic the topic + * @param payload the payload + */ +bool pubMQTT(const char* topic, const char* payload) { + return pubMQTT(topic, payload, sensor_Retain); +} + +/** + * @brief Very Low level MQTT functions with retain Flag + * + * @param topic the topic + * @param payload the payload + * @param retainFlag true if retain the retain Flag + */ +bool pubMQTT(const char* topic, const char* payload, bool retainFlag) { + bool res = false; + if (SYSConfig.mqtt && !SYSConfig.offline) { +#ifdef ESP32 + if (xSemaphoreTake(xMqttMutex, pdMS_TO_TICKS(QueueSemaphoreTimeOutTask)) == pdFALSE) { + Log.error(F("xMqttMutex not taken" CR)); + gatewayState = GatewayState::ERROR; + return res; + } +#endif + if (mqtt && mqtt->connected()) { + Log.notice(F("[ OMG->MQTT ] topic: %s msg: %s " CR), topic, payload); + res = mqtt->publish(topic, payload, 0, retainFlag); + } else { + Log.warning(F("MQTT not connected, aborting the publication" CR)); + } +#ifdef ESP32 + xSemaphoreGive(xMqttMutex); +#endif + } else { + Log.notice(F("[ OMG->MQTT deactivated or offline] topic: %s msg: %s " CR), topic, payload); + } + return res; +} + +bool pubMQTT(String topic, const char* payload) { + return pubMQTT(topic.c_str(), payload); +} + +bool pubMQTT(const char* topic, unsigned long payload) { + char val[11]; + sprintf(val, "%lu", payload); + return pubMQTT(topic, val); +} + +bool pubMQTT(const char* topic, unsigned long long payload) { + char val[21]; + sprintf(val, "%llu", payload); + return pubMQTT(topic, val); +} + +bool pubMQTT(const char* topic, String payload) { + return pubMQTT(topic, payload.c_str()); +} + +bool pubMQTT(String topic, String payload) { + return pubMQTT(topic.c_str(), payload.c_str()); +} + +bool pubMQTT(String topic, int payload) { + char val[12]; + sprintf(val, "%d", payload); + return pubMQTT(topic.c_str(), val); +} + +bool pubMQTT(String topic, unsigned long long payload) { + char val[21]; + sprintf(val, "%llu", payload); + return pubMQTT(topic.c_str(), val); +} + +bool pubMQTT(String topic, float payload) { + char val[12]; + dtostrf(payload, 3, 1, val); + return pubMQTT(topic.c_str(), val); +} + +bool pubMQTT(const char* topic, float payload) { + char val[12]; + dtostrf(payload, 3, 1, val); + return pubMQTT(topic, val); +} + +bool pubMQTT(const char* topic, int payload) { + char val[12]; + sprintf(val, "%d", payload); + return pubMQTT(topic, val); +} + +bool pubMQTT(const char* topic, unsigned int payload) { + char val[12]; + sprintf(val, "%u", payload); + return pubMQTT(topic, val); +} + +bool pubMQTT(const char* topic, long payload) { + char val[11]; + sprintf(val, "%ld", payload); + return pubMQTT(topic, val); +} + +bool pubMQTT(const char* topic, double payload) { + char val[16]; + sprintf(val, "%f", payload); + return pubMQTT(topic, val); +} + +bool pubMQTT(String topic, unsigned long payload) { + char val[11]; + sprintf(val, "%lu", payload); + return pubMQTT(topic.c_str(), val); +} + +void delayWithOTA(long waitMillis) { + long waitStep = 100; + for (long waitedMillis = 0; waitedMillis < waitMillis; waitedMillis += waitStep) { +#ifndef ESPWifiManualSetup + checkButton(); // check if a reset of wifi/mqtt settings is asked +#endif + ArduinoOTA.handle(); +#if defined(ZwebUI) && defined(ESP32) + WebUILoop(); +#endif +#ifdef ESP32 + //esp_task_wdt_reset(); +#endif + delay(waitStep); + } +} + +void SYSConfig_init() { + SYSConfig.mqtt = DEFAULT_MQTT; + SYSConfig.serial = DEFAULT_SERIAL; + SYSConfig.offline = DEFAULT_OFFLINE; +#if USE_BLUFI + SYSConfig.blufi = DEFAULT_BLUFI; +#endif +#ifdef ZmqttDiscovery + SYSConfig.discovery = DEFAULT_DISCOVERY; + SYSConfig.ohdiscovery = OpenHABDiscovery; +#endif +#ifdef LED_ADDRESSABLE + SYSConfig.rgbbrightness = DEFAULT_ADJ_BRIGHTNESS; +#endif + SYSConfig.powerMode = DEFAULT_LOW_POWER_MODE; +} + +void SYSConfig_fromJson(JsonObject& SYSdata) { + Config_update(SYSdata, "mqtt", SYSConfig.mqtt); + Config_update(SYSdata, "serial", SYSConfig.serial); + Config_update(SYSdata, "offline", SYSConfig.offline); +#if USE_BLUFI + Config_update(SYSdata, "blufi", SYSConfig.blufi); +#endif +#ifdef ZmqttDiscovery + Config_update(SYSdata, "disc", SYSConfig.discovery); + Config_update(SYSdata, "ohdisc", SYSConfig.ohdiscovery); +#endif +#ifdef LED_ADDRESSABLE + Config_update(SYSdata, "rgbb", SYSConfig.rgbbrightness); +#endif + Config_update(SYSdata, "powermode", SYSConfig.powerMode); +} + +#ifdef ESP32 +void SYSConfig_save() { + StaticJsonDocument jsonBuffer; + JsonObject SYSdata = jsonBuffer.to(); + SYSdata["mqtt"] = SYSConfig.mqtt; + SYSdata["serial"] = SYSConfig.serial; + SYSdata["offline"] = SYSConfig.offline; + SYSdata["powermode"] = SYSConfig.powerMode; +# if USE_BLUFI + SYSdata["blufi"] = SYSConfig.blufi; +# endif +# ifdef ZmqttDiscovery + SYSdata["disc"] = SYSConfig.discovery; + SYSdata["ohdisc"] = SYSConfig.ohdiscovery; +# endif +# ifdef LED_ADDRESSABLE + SYSdata["rgbb"] = SYSConfig.rgbbrightness; +# endif + String conf = ""; + serializeJson(jsonBuffer, conf); + preferences.begin(Gateway_Short_Name, false); + int result = preferences.putString("SYSConfig", conf); + preferences.end(); + Log.notice(F("SYS Config_save: %s, result: %d" CR), conf.c_str(), result); +} +#else // Function not available for ESP8266 +void SYSConfig_save() {} +#endif + +bool cmpToMainTopic(const char* topicOri, const char* toAdd) { + if (strcmp(topicOri, toAdd) == 0) + return true; + // Is string "" equal to ""? + // Compare first part with first chunk + if (strncmp(topicOri, mqtt_topic, strlen(mqtt_topic)) != 0) + return false; + // Move pointer of sizeof chunk + topicOri += strlen(mqtt_topic); + // And so on... + if (strncmp(topicOri, gateway_name, strlen(gateway_name)) != 0) + return false; + topicOri += strlen(gateway_name); + if (strncmp(topicOri, toAdd, strlen(toAdd)) != 0) + return false; + return true; +} + +#if defined(ESP32) +void SYSConfig_load() { + StaticJsonDocument jsonBuffer; + preferences.begin(Gateway_Short_Name, true); + if (preferences.isKey("SYSConfig")) { + auto error = deserializeJson(jsonBuffer, preferences.getString("SYSConfig", "{}")); + preferences.end(); + if (error) { + Log.error(F("SYS config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity()); + gatewayState = GatewayState::ERROR; + return; + } + if (jsonBuffer.isNull()) { + Log.warning(F("SYS config is null" CR)); + return; + } + JsonObject jo = jsonBuffer.as(); + SYSConfig_fromJson(jo); + Log.notice(F("SYS config loaded" CR)); + } else { + preferences.end(); + Log.notice(F("SYS config not found" CR)); + } +} +#else // Function not available for ESP8266 +void SYSConfig_load() {} +#endif + +#if defined(MDNS_SD) +std::pair discoverMQTTbroker() { + Log.trace(F("Browsing for MQTT service" CR)); + int n = MDNS.queryService("mqtt", "tcp"); + if (n == 0) { + Log.warning(F("no services found" CR)); + } else { + Log.trace(F("%d service(s) found" CR), n); + for (int i = 0; i < n; ++i) { + Log.trace(F("Service %d %s found" CR), i, MDNS.hostname(i).c_str()); + Log.trace(F("IP %s Port %d" CR), MDNS.IP(i).toString().c_str(), MDNS.port(i)); + } + if (n == 1) { + Log.trace(F("One MQTT server found setting parameters" CR)); + return {MDNS.IP(0).toString(), uint16_t(MDNS.port(0))}; + } else { + Log.warning(F("Several MQTT servers found, please deactivate mDNS and set your default server" CR)); + } + } + return {"", 0}; +} +#endif + +#if MQTT_BROKER_MODE +void setupMQTT() { + Log.notice(F("Reconfiguring MQTT broker..." CR)); + + mqtt.reset(new MQTTServer()); + mqtt->begin(); +# ifdef ZgatewayBT + BTProcessLock = !BTConfig.enabled; // Release BLE processes at start if enabled +# endif +} +#else + +struct ss_cnt_parameters_backup { + ss_cnt_parameters parameters; + int cnt_index; + bool saveOnSuccess; +}; + +static std::unique_ptr cnt_parameters_backup; + +void setupMQTT() { + Log.notice(F("Reconfiguring MQTT client..." CR)); + + const auto& parameters = cnt_parameters_array[cnt_index]; + + // free the old MQTT client and underlying connection + mqtt.reset(); + eClient.reset(); + failure_number_mqtt = 0; + + // configure socket + if (parameters.isConnectionSecure) { + eClient.reset(new WiFiClientSecure); + if (parameters.isCertValidate) { + setupTLS(cnt_index); + } else { + static_cast(eClient.get())->setInsecure(); + } + } else { + eClient.reset(new WiFiClient); + } + +# if defined(MDNS_SD) + Log.trace(F("Connecting to MQTT by mDNS without MQTT hostname" CR)); + const auto discovered_broker = discoverMQTTbroker(); + const auto broker_host = discovered_broker.first.c_str(); + const auto broker_port = discovered_broker.second; +# else + const auto broker_host = parameters.mqtt_server; + const auto broker_port = String(parameters.mqtt_port).toInt(); +# endif + + Log.trace(F("Mqtt server: %s" CR), broker_host); + Log.trace(F("Port: %u" CR), broker_port); + + mqtt.reset(new PicoMQTT::Client(*eClient, broker_host, broker_port, gateway_name, + parameters.mqtt_user, parameters.mqtt_pass, + 0, // minimum reconnect attempt interval [ms] + 60 * 1000, // keep alive interval [ms] + (GeneralTimeOut - 1) * 1000 // socket timeout [ms] + )); + +# if AWS_IOT + // AWS doesn't support will topic for the moment + mqtt->will.topic = ""; + mqtt->will.payload = ""; + mqtt->will.qos = 0; + mqtt->will.retain = false; +# else + mqtt->will.topic = String(mqtt_topic) + gateway_name + will_Topic; + mqtt->will.payload = will_Message; + mqtt->will.qos = will_QoS; + mqtt->will.retain = will_Retain; +# endif + + mqtt->connected_callback = [] { +# if AWS_IOT + { + // Define a threshold for instability detection + constexpr unsigned int reconnection_threshold = 5; + constexpr unsigned long reconnection_window_millis = 120000; // Consider unstable if more than 5 reconnections in 2 minutes + + static unsigned int reconnection_count = 0; + static unsigned long start_reconnection_window_millis = 0; + + const unsigned long current_millis = millis(); + if (start_reconnection_window_millis == 0 || current_millis - start_reconnection_window_millis > reconnection_window_millis) { + // Reset the count and timestamp at the start of a new window + reconnection_count = 0; + start_reconnection_window_millis = current_millis; + } + reconnection_count++; // Increment reconnection count + Log.trace(F("MQTT connection count: %d" CR), reconnection_count); + if (reconnection_count > reconnection_threshold && SYSConfig.mqtt) { + // Detected instability in MQTT connection + Log.warning(F("MQTT connection instability detected: %d reconnections in the last %d seconds" CR), reconnection_count, reconnection_window_millis / 1000); + // Stop xtoMQTT to see if it helps and still enables to receive data + SYSConfig.mqtt = false; + } + } +# endif +# ifdef ZgatewayBT + BTProcessLock = !BTConfig.enabled; // Release BLE processes at start if enabled +# endif + ProcessLock = false; // Release the loop process + displayPrint("MQTT connected"); + Log.notice(F("Connected to broker" CR)); + gatewayState = GatewayState::BROKER_CONNECTED; + failure_number_mqtt = 0; + // Once connected, publish an announcement... + pub(will_Topic, Gateway_AnnouncementMsg, will_Retain); + + if (cnt_parameters_backup) { + // this was the first attempt to connect to a new server and it succeeded + Log.notice(F("MQTT connection parameters %d successful" CR), cnt_index); + cnt_parameters_array[cnt_index].validConnection = true; + readCntParameters(cnt_index); + +# ifndef ESPWifiManualSetup + if (cnt_parameters_backup->saveOnSuccess) // Save the new parameters to the flash + saveConfig(); +# endif + + cnt_parameters_backup.reset(); + ESPRestart(7); + } + handle_autodiscovery(); + }; + + mqtt->connection_failure_callback = [] { + if ((WiFi.status() != WL_CONNECTED && ethConnected == false) || SYSConfig.offline) { + // No network connection or offline, ignore this failure + return; + } + failure_number_mqtt++; // we count the failure + gatewayState = GatewayState::BROKER_DISCONNECTED; + Log.warning(F("failure_number_mqtt: %d" CR), failure_number_mqtt); + + const auto& parameters = cnt_parameters_array[cnt_index]; + + if (parameters.isConnectionSecure) { + WiFiClientSecure* client = static_cast(eClient.get()); +# if defined(ESP32) + Log.warning(F("failed, ssl error code=%d" CR), client->lastError(nullptr, 0)); +# elif defined(ESP8266) + Log.warning(F("failed, ssl error code=%d" CR), client->getLastSSLError()); +# endif + } + + if (cnt_parameters_backup) { + // this was the first attempt to connect to a new server and it failed, revert to old settings + Log.error(F("MQTT connection failed, reverting to previous settings" CR)); + gatewayState = GatewayState::ERROR; + cnt_parameters_array[cnt_index] = cnt_parameters_backup->parameters; + cnt_index = cnt_parameters_backup->cnt_index; + mqttSetupPending = true; + cnt_parameters_backup.reset(); + ESPRestart(7); + return; + } + + delayWithOTA(10000); + + if (failure_number_mqtt > maxRetryWatchDog) { +# ifndef ESPWifiManualSetup + // Look for the next valid connection + for (int i = 0; i < cnt_parameters_array_size; i++) { + cnt_index++; + if (cnt_index >= cnt_parameters_array_size) { + cnt_index = 0; + } + if (cnt_parameters_array[cnt_index].validConnection) { + Log.notice(F("Connection %d valid, switching" CR), cnt_index); + saveConfig(); + break; + } else { + Log.notice(F("Connection %d not valid" CR), cnt_index); + } + } +# endif + unsigned long millis_since_last_ota; + while ( + // When + // ...incomplete OTA in progress + (last_ota_activity_millis != 0) + // ...AND last OTA activity fairly recently + && ((millis_since_last_ota = millis() - last_ota_activity_millis) < ota_timeout_millis)) { + // ... We consider that OTA might be still active, and we sleep for a while, and giving + // OTA chance to proceed (ArduinoOTA.handle()) + Log.warning(F("OTA might be still active (activity %d ms ago)" CR), millis_since_last_ota); + ArduinoOTA.handle(); + delay(100); + } + ESPRestart(1); + } + }; + + mqtt->subscribe(String(mqtt_topic) + gateway_name + subjectMQTTtoX, receivingDATA, mqtt_max_payload_size); + +# ifdef ZgatewayRF + // subject on which other OMG will publish, this OMG will store these msg and by the way don't republish them if they have been already published + mqtt->subscribe(subjectMultiGTWRF, receivingDATA, mqtt_max_payload_size); +# endif + +# ifdef ZgatewayIR + // subject on which other OMG will publish, this OMG will store these msg and by the way don't republish them if they have been already published + mqtt->subscribe(subjectMultiGTWIR, receivingDATA, mqtt_max_payload_size); +# endif + +# if enableMultiGTWSync + mqtt->subscribe(String(mqtt_topic) + subjectTrackerSync, receivingDATA, mqtt_max_payload_size); +# endif + + mqtt->begin(); +} +#endif + +#if defined(ESP32) && (defined(WifiGMode) || defined(WifiPower)) +void setESPWifiProtocolTxPower() { + //Reduce WiFi interference when using ESP32 using custom WiFi mode and tx power + //https://github.com/espressif/arduino-esp32/search?q=WIFI_PROTOCOL_11G + //https://www.letscontrolit.com/forum/viewtopic.php?t=671&start=20 +# if WifiGMode == true + if (esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G) != ESP_OK) { + Log.error(F("Failed to change WifiMode." CR)); + gatewayState = GatewayState::ERROR; + } +# endif + +# if WifiGMode == false + if (esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N) != ESP_OK) { + Log.error(F("Failed to change WifiMode." CR)); + gatewayState = GatewayState::ERROR; + } +# endif + + uint8_t getprotocol; + esp_err_t err; + err = esp_wifi_get_protocol(WIFI_IF_STA, &getprotocol); + + if (err != ESP_OK) { + Log.notice(F("Could not get protocol!" CR)); + } + if (getprotocol & WIFI_PROTOCOL_11N) { + Log.notice(F("WiFi_Protocol_11n" CR)); + } + if (getprotocol & WIFI_PROTOCOL_11G) { + Log.notice(F("WiFi_Protocol_11g" CR)); + } + if (getprotocol & WIFI_PROTOCOL_11B) { + Log.notice(F("WiFi_Protocol_11b" CR)); + } + +# ifdef WifiPower + Log.notice(F("Requested WiFi power level: %i" CR), WifiPower); + WiFi.setTxPower(WifiPower); +# endif + Log.notice(F("Operating WiFi power level: %i" CR), WiFi.getTxPower()); +} +#endif + +#if defined(ESP8266) && (defined(WifiGMode) || defined(WifiPower)) +void setESPWifiProtocolTxPower() { +# if WifiGMode == true + if (!wifi_set_phy_mode(PHY_MODE_11G)) { + Log.error(F("Failed to change WifiMode." CR)); + gatewayState = GatewayState::ERROR; + } +# endif + +# if WifiGMode == false + if (!wifi_set_phy_mode(PHY_MODE_11N)) { + Log.error(F("Failed to change WifiMode." CR)); + gatewayState = GatewayState::ERROR; + } +# endif + + phy_mode_t getprotocol = wifi_get_phy_mode(); + if (getprotocol == PHY_MODE_11N) { + Log.notice(F("WiFi_Protocol_11n" CR)); + } + if (getprotocol == PHY_MODE_11G) { + Log.notice(F("WiFi_Protocol_11g" CR)); + } + if (getprotocol == PHY_MODE_11B) { + Log.notice(F("WiFi_Protocol_11b" CR)); + } + +# ifdef WifiPower + Log.notice(F("Requested WiFi power level: %i dBm" CR), WifiPower); + + int i_dBm = int(WifiPower * 4.0f); + + // i_dBm 82 == 20.5 dBm + if (i_dBm > 82) { + i_dBm = 82; + } else if (i_dBm < 0) { + i_dBm = 0; + } + + system_phy_set_max_tpw((uint8_t)i_dBm); +# endif +} +#endif + +#ifdef ESP32 +void updateAndHandleLEDsTask(void* pvParameters) { + for (;;) { + updateAndHandleLEDsTask(); + vTaskDelay(pdMS_TO_TICKS(100)); + } +} +#endif + +void updateAndHandleLEDsTask() { + static GatewayState previousGatewayState; + if (previousGatewayState != gatewayState) { +#ifdef LED_POWER + ledManager.setMode(STRIP_POWER, LED_POWER, LEDManager::STATIC, LED_POWER_COLOR, -1); +#endif + switch (gatewayState) { + case PROCESSING: +#ifdef LED_PROCESSING + ledManager.setMode(STRIP_PROCESSING, LED_PROCESSING, LEDManager::BLINK, LED_PROCESSING_COLOR, 3); +#endif + break; + case WAITING_ONBOARDING: +#ifdef LED_BROKER + ledManager.setMode(STRIP_BROKER, LED_BROKER, LEDManager::STATIC, LED_WAITING_ONBOARD_COLOR, -1); +#endif + break; + case ONBOARDING: +#ifdef LED_BROKER + ledManager.setMode(STRIP_BROKER, LED_BROKER, LEDManager::STATIC, LED_ONBOARD_COLOR, -1); +#endif + break; + case NTWK_CONNECTED: +#ifdef LED_NETWORK + ledManager.setMode(STRIP_NETWORK, LED_NETWORK, LEDManager::STATIC, LED_NETWORK_OK_COLOR, -1); +#endif + break; + case BROKER_CONNECTED: +#ifdef LED_BROKER + ledManager.setMode(STRIP_BROKER, LED_BROKER, LEDManager::STATIC, LED_BROKER_OK_COLOR, -1); +#endif + break; + case NTWK_DISCONNECTED: +#ifdef LED_NETWORK + ledManager.setMode(STRIP_NETWORK, LED_NETWORK, LEDManager::BLINK, LED_NETWORK_ERROR_COLOR, -1); +#endif + break; + case BROKER_DISCONNECTED: +#ifdef LED_BROKER + ledManager.setMode(STRIP_BROKER, LED_BROKER, LEDManager::BLINK, LED_BROKER_ERROR_COLOR, -1); +#endif + break; + case SLEEPING: + ledManager.setMode(-1, -1, LEDManager::OFF, 0, -1); + break; + case OFFLINE: +#ifdef LED_NETWORK + ledManager.setMode(STRIP_NETWORK, LED_NETWORK, LEDManager::BLINK, LED_OFFLINE_COLOR, -1); +#endif + break; + case LOCAL_OTA_IN_PROGRESS: +#ifdef LED_PROCESSING + ledManager.setMode(STRIP_PROCESSING, LED_PROCESSING, LEDManager::BLINK, LED_OTA_LOCAL_COLOR, -1); +#endif + break; + case REMOTE_OTA_IN_PROGRESS: +#ifdef LED_PROCESSING + ledManager.setMode(STRIP_PROCESSING, LED_PROCESSING, LEDManager::BLINK, LED_OTA_REMOTE_COLOR, -1); +#endif + break; + case ERROR: +#ifdef LED_ERROR + ledManager.setMode(STRIP_ERROR, LED_ERROR, LEDManager::BLINK, LED_ERROR_COLOR, 3); +#endif + break; + default: + break; + } + previousGatewayState = gatewayState; + } + ledManager.update(); +} + +void setup() { + //Launch serial for debugging purposes + Serial.begin(SERIAL_BAUD); + Log.begin(LOG_LEVEL, &Serial); + Log.notice(F(CR "************* WELCOME TO OpenMQTTGateway **************" CR)); +#if defined(TRIGGER_GPIO) && !defined(ESPWifiManualSetup) + pinMode(TRIGGER_GPIO, INPUT_PULLUP); + checkButton(); +#endif + + delay(100); //give time to start the flash and avoid issue when reading the preferences + + SYSConfig_init(); + SYSConfig_load(); + + if (SYSConfig.offline) { + gatewayState = GatewayState::OFFLINE; + } + +#ifdef LED_ADDRESSABLE +# ifdef LED_ADDRESSABLE_PIN1 + ledManager.addLEDStrip(LED_ADDRESSABLE_PIN1, LED_ADDRESSABLE_NUM); +# endif +# ifdef LED_ADDRESSABLE_PIN2 + ledManager.addLEDStrip(LED_ADDRESSABLE_PIN2, LED_ADDRESSABLE_NUM); +# endif +# ifdef LED_ADDRESSABLE_PIN3 + ledManager.addLEDStrip(LED_ADDRESSABLE_PIN3, LED_ADDRESSABLE_NUM); +# endif + ledManager.setBrightness(SYSConfig.rgbbrightness); +#elif LED_PIN + ledManager.addLEDStrip(LED_PIN, 1); +#endif + +#ifdef ESP8266 +# ifndef ZgatewaySRFB // if we are not in sonoff rf bridge case we apply the ESP8266 GPIO optimization + Serial.end(); + Serial.begin(SERIAL_BAUD, SERIAL_8N1, SERIAL_TX_ONLY); // enable on ESP8266 to free some pin +# endif +#elif ESP32 + xTaskCreate(updateAndHandleLEDsTask, "updateAndHandleLEDsTask", 2500, NULL, 1, NULL); + xQueueMutex = xSemaphoreCreateMutex(); + xMqttMutex = xSemaphoreCreateMutex(); +# if defined(ZboardM5STICKC) || defined(ZboardM5STICKCP) || defined(ZboardM5STACK) || defined(ZboardM5TOUGH) + setupM5(); +# endif +# if defined(ZdisplaySSD1306) + setupSSD1306(); + modules.add(ZdisplaySSD1306); +# endif +#endif + + Log.notice(F("OpenMQTTGateway Version: " OMG_VERSION CR)); + +#ifdef ESP32_EXT0_WAKE_PIN + Log.notice(F("Setting EXT0 Wakeup for deep sleep." CR)); + gpio_num_t wake_pin0 = static_cast(ESP32_EXT0_WAKE_PIN); + if (esp_sleep_enable_ext0_wakeup(wake_pin0, ESP32_EXT0_WAKE_PIN_STATE) != ESP_OK) { + Log.error(F("Failed to set deep sleep EXT0 Wakeup." CR)); + gatewayState = GatewayState::ERROR; + } +#endif +#ifdef ESP32_EXT1_WAKE_PIN + Log.notice(F("Setting EXT1 Wakeup for deep sleep." CR)); + uint64_t wake_pin_bitmask = 1ULL << ESP32_EXT1_WAKE_PIN; // Adjust this line if multiple pins are used. + esp_sleep_ext1_wakeup_mode_t wake_state1 = static_cast(ESP32_EXT1_WAKE_PIN_STATE); + if (esp_sleep_enable_ext1_wakeup(wake_pin_bitmask, wake_state1) != ESP_OK) { + Log.error(F("Failed to set deep sleep EXT1 Wakeup." CR)); + gatewayState = GatewayState::ERROR; + } +#endif +/* + Note that the ONOFF module need to start after the RN8209 so that the overCurrent function is launched after the setup of the sensor +*/ +#ifdef ZsensorRN8209 + setupRN8209(); + modules.add(ZsensorRN8209); +#endif +#ifdef ZactuatorONOFF + setupONOFF(); + modules.add(ZactuatorONOFF); +#endif +#ifdef ZgatewaySERIAL + setupSERIAL(); + modules.add(ZgatewaySERIAL); +#endif + +#if defined(ESP32) && defined(USE_BLUFI) + if (SYSConfig.blufi) + startBlufi(); +#endif + +#if defined(ESPWifiManualSetup) + setupWiFiFromBuild(); +#else + if (loadConfigFromFlash()) { // Config present + Log.notice(F("Config loaded from flash" CR)); +# ifdef ESP32_ETHERNET + setup_ethernet_esp32(); +# endif + // If not in failSafeMode and no connection to the network with Ethernet, launch the wifi manager + if (!failSafeMode && !ethConnected) setupWiFiManager(); + } else { // No config in flash +# ifdef ESP32_ETHERNET + setup_ethernet_esp32(); +# endif + +# if SELF_TEST + // Check serial input to trigger a Self Test sequence if required + checkSerial(); +# endif + + Log.notice(F("No config in flash, launching wifi manager" CR)); + // In failSafeMode we don't want to setup wifi manager as it has already been done before + if (!failSafeMode) setupWiFiManager(); + } + +#endif + Log.trace(F("OpenMQTTGateway mac: %s" CR), WiFi.macAddress().c_str()); + Log.trace(F("OpenMQTTGateway ip: %s" CR), WiFi.localIP().toString().c_str()); + Log.trace(F("OpenMQTTGateway index %d" CR), cnt_index); + Log.trace(F("OpenMQTTGateway mqtt topic: %s" CR), mqtt_topic); +#ifdef ZmqttDiscovery + Log.trace(F("OpenMQTTGateway mqtt discovery prefix: %s" CR), discovery_prefix); +#endif + Log.trace(F("OpenMQTTGateway gateway name: %s" CR), gateway_name); +#if !MQTT_BROKER_MODE + Log.trace(F("OpenMQTTGateway mqtt server: %s" CR), cnt_parameters_array[cnt_index].mqtt_server); + Log.trace(F("OpenMQTTGateway mqtt port: %s" CR), cnt_parameters_array[cnt_index].mqtt_port); + Log.trace(F("OpenMQTTGateway mqtt user: %s" CR), cnt_parameters_array[cnt_index].mqtt_user); + Log.trace(F("OpenMQTTGateway secure connection: %s" CR), cnt_parameters_array[cnt_index].isConnectionSecure ? "true" : "false"); + Log.trace(F("OpenMQTTGateway validate cert: %s" CR), cnt_parameters_array[cnt_index].isCertValidate ? "true" : "false"); +#endif + + setOTA(); + +#if defined(ZwebUI) && defined(ESP32) + WebUISetup(); + modules.add(ZwebUI); +#endif + + delay(1500); +#if defined(ZgatewayRF) || defined(ZgatewayPilight) || defined(ZgatewayRTL_433) || defined(ZgatewayRF2) || defined(ZactuatorSomfy) + setupCommonRF(); +#endif +#ifdef ZsensorBME280 + setupZsensorBME280(); + modules.add(ZsensorBME280); +#endif +#ifdef ZsensorHTU21 + setupZsensorHTU21(); + modules.add(ZsensorHTU21); +#endif +#ifdef ZsensorLM75 + setupZsensorLM75(); + modules.add(ZsensorLM75); +#endif +#ifdef ZsensorAHTx0 + setupZsensorAHTx0(); + modules.add(ZsensorAHTx0); +#endif +#ifdef ZsensorBH1750 + setupZsensorBH1750(); + modules.add(ZsensorBH1750); +#endif +#ifdef ZsensorMQ2 + setupZsensorMQ2(); + modules.add(ZsensorMQ2); +#endif +#ifdef ZsensorTEMT6000 + setupZsensorTEMT6000(); + modules.add(ZsensorTEMT6000); +#endif +#ifdef ZsensorTSL2561 + setupZsensorTSL2561(); + modules.add(ZsensorTSL2561); +#endif +#ifdef Zgateway2G + setup2G(); + modules.add(Zgateway2G); +#endif +#ifdef ZgatewayIR + setupIR(); + modules.add(ZgatewayIR); +#endif +#ifdef ZgatewayLORA + setupLORA(); + modules.add(ZgatewayLORA); +#endif +#ifdef ZgatewayRF + modules.add(ZgatewayRF); +# define ACTIVE_RECEIVER ACTIVE_RF +#endif +#ifdef ZgatewayRF_RHASK + setupRF(); +#endif +#ifdef ZgatewayRF2 + modules.add(ZgatewayRF2); +# ifdef ACTIVE_RECEIVER +# undef ACTIVE_RECEIVER +# endif +# define ACTIVE_RECEIVER ACTIVE_RF2 +#endif +#ifdef ZgatewayPilight + modules.add(ZgatewayPilight); +# ifdef ACTIVE_RECEIVER +# undef ACTIVE_RECEIVER +# endif +# define ACTIVE_RECEIVER ACTIVE_PILIGHT +#endif +#ifdef ZgatewayWeatherStation + setupWeatherStation(); + modules.add(ZgatewayWeatherStation); +#endif +#ifdef ZgatewayGFSunInverter + setupGFSunInverter(); + modules.add(ZgatewayGFSunInverter); +#endif +#ifdef ZgatewaySRFB + setupSRFB(); + modules.add(ZgatewaySRFB); +#endif +#ifdef ZgatewayBT + setupBT(); + modules.add(ZgatewayBT); +#endif +#ifdef ZgatewayRFM69 + setupRFM69(); + modules.add(ZgatewayRFM69); +#endif +#ifdef ZsensorINA226 + setupINA226(); + modules.add(ZsensorINA226); +#endif +#ifdef ZsensorHCSR501 + setupHCSR501(); + modules.add(ZsensorHCSR501); +#endif +#ifdef ZsensorHCSR04 + setupHCSR04(); + modules.add(ZsensorHCSR04); +#endif +#ifdef ZsensorGPIOInput + setupGPIOInput(); + modules.add(ZsensorGPIOInput); +#endif +#ifdef ZsensorGPIOKeyCode + setupGPIOKeyCode(); + modules.add(ZsensorGPIOKeyCode); +#endif +#ifdef ZactuatorFASTLED + setupFASTLED(); + modules.add(ZactuatorFASTLED); +#endif +#ifdef ZactuatorPWM + setupPWM(); + modules.add(ZactuatorPWM); +#endif +#ifdef ZactuatorSomfy +# ifdef ACTIVE_RECEIVER +# undef ACTIVE_RECEIVER +# endif +# define ACTIVE_RECEIVER ACTIVE_NONE + setupSomfy(); + modules.add(ZactuatorSomfy); +#endif +#ifdef ZsensorDS1820 + setupZsensorDS1820(); + modules.add(ZsensorDS1820); +#endif +#ifdef ZsensorADC + setupADC(); + modules.add(ZsensorADC); +#endif +#ifdef ZsensorTouch + setupTouch(); + modules.add(ZsensorTouch); +#endif +#ifdef ZsensorC37_YL83_HMRD + setupZsensorC37_YL83_HMRD(); + modules.add(ZsensorC37_YL83_HMRD); +#endif +#ifdef ZsensorDHT + setupDHT(); + modules.add(ZsensorDHT); +#endif +#ifdef ZsensorSHTC3 + setupSHTC3(); +#endif +#ifdef ZgatewayRTL_433 +# ifdef ACTIVE_RECEIVER +# undef ACTIVE_RECEIVER +# endif +# define ACTIVE_RECEIVER ACTIVE_RTL + setupRTL_433(); + modules.add(ZgatewayRTL_433); +#endif + Log.trace(F("mqtt_max_payload_size: %d" CR), mqtt_max_payload_size); + SYSConfig.offline ? Log.notice(F("Offline enabled" CR)) : Log.notice(F("Offline disabled" CR)); + char jsonChar[100]; + serializeJson(modules, jsonChar, measureJson(modules) + 1); + Log.notice(F("OpenMQTTGateway modules: %s" CR), jsonChar); + Log.notice(F("************** Setup OpenMQTTGateway end **************" CR)); +} + +// Bypass for ESP not reconnecting automaticaly the second time https://github.com/espressif/arduino-esp32/issues/2501 +bool wifi_reconnect_bypass() { +#if defined(ESP32) && defined(USE_BLUFI) + extern bool omg_blufi_ble_connected; + if (omg_blufi_ble_connected) { + Log.notice(F("BLUFI is connected, bypassing wifi reconnect" CR)); + gatewayState = GatewayState::ONBOARDING; + return true; + } +#endif + uint8_t wifi_autoreconnect_cnt = 0; +#ifdef ESP32 + while (WiFi.status() != WL_CONNECTED && wifi_autoreconnect_cnt < maxConnectionRetryNetwork) { +#else + while (WiFi.waitForConnectResult() != WL_CONNECTED && wifi_autoreconnect_cnt < maxConnectionRetryNetwork) { +#endif + Log.notice(F("Attempting Wifi connection with saved AP: %d" CR), wifi_autoreconnect_cnt); + + WiFi.begin(); +#if defined(WifiGMode) || defined(WifiPower) + setESPWifiProtocolTxPower(); +#endif + delay(1000); + wifi_autoreconnect_cnt++; + } + if (wifi_autoreconnect_cnt < maxConnectionRetryNetwork) { + return true; + } else { + return false; + } +} + +void setOTA() { + // Port defaults to 8266 + ArduinoOTA.setPort(ota_port); + + // Hostname defaults to esp8266-[ChipID] + ArduinoOTA.setHostname(ota_hostname); + + // No authentication by default + ArduinoOTA.setPassword(ota_pass); + + ArduinoOTA.onStart([]() { + Log.trace(F("Start OTA, lock other functions" CR)); + last_ota_activity_millis = millis(); +#ifdef ESP32 + ProcessLock = true; +# ifdef ZgatewayBT + stopProcessing(true); +# endif +#endif + lpDisplayPrint("OTA in progress"); + }); + ArduinoOTA.onEnd([]() { + Log.trace(F("\nOTA done" CR)); + last_ota_activity_millis = 0; + lpDisplayPrint("OTA done"); + ESPRestart(6); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Log.trace(F("Progress: %u%%\r" CR), (progress / (total / 100))); + gatewayState = GatewayState::LOCAL_OTA_IN_PROGRESS; + last_ota_activity_millis = millis(); + }); + ArduinoOTA.onError([](ota_error_t error) { + last_ota_activity_millis = millis(); + Serial.printf("Error[%u]: ", error); + gatewayState = GatewayState::ERROR; + if (error == OTA_AUTH_ERROR) + Log.error(F("Auth Failed" CR)); + else if (error == OTA_BEGIN_ERROR) + Log.error(F("Begin Failed" CR)); + else if (error == OTA_CONNECT_ERROR) + Log.error(F("Connect Failed" CR)); + else if (error == OTA_RECEIVE_ERROR) + Log.error(F("Receive Failed" CR)); + else if (error == OTA_END_ERROR) + Log.error(F("End Failed" CR)); + ESPRestart(6); + }); + ArduinoOTA.begin(); +} + +#if !MQTT_BROKER_MODE +void setupTLS(int index) { + configTime(0, 0, NTP_SERVER); + WiFiClientSecure* sClient = static_cast(eClient.get()); + Log.notice(F("cnt index used: %d" CR), index); + if (!cnt_parameters_array[index].isCertValidate) { + Log.notice(F("Disabling cert validation" CR)); + sClient->setInsecure(); + } else { + Log.notice(F("Enabling cert validation" CR)); +# if defined(ESP32) + if (cnt_parameters_array[index].server_cert.length() > MIN_CERT_LENGTH) { + sClient->setCACert(cnt_parameters_array[index].server_cert.c_str()); + Log.notice(F("Server cert found from cert array" CR)); + } else if (strlen(ss_server_cert) > MIN_CERT_LENGTH) { + sClient->setCACert(ss_server_cert); + Log.notice(F("Server cert found from ss_server_cert" CR)); + } else { + Log.error(F("No server cert found" CR)); + gatewayState = GatewayState::ERROR; + } + +# if AWS_IOT + if (strcmp(cnt_parameters_array[index].mqtt_port, "443") == 0) { + Log.notice(F("Using ALPN" CR)); + sClient->setAlpnProtocols(alpnProtocols); + } +# endif +# if MQTT_SECURE_SIGNED_CLIENT + if (cnt_parameters_array[index].client_cert.length() > MIN_CERT_LENGTH) { + sClient->setCertificate(cnt_parameters_array[index].client_cert.c_str()); + Log.notice(F("Client cert found from cert array" CR)); + } else if (strlen(ss_client_cert) > MIN_CERT_LENGTH) { + sClient->setCertificate(ss_client_cert); + Log.notice(F("Client cert found from ss_client_cert" CR)); + } else { + Log.error(F("No client cert found" CR)); + gatewayState = GatewayState::ERROR; + } + if (cnt_parameters_array[index].client_key.length() > MIN_CERT_LENGTH) { + sClient->setPrivateKey(cnt_parameters_array[index].client_key.c_str()); + Log.notice(F("Client key found from cert array" CR)); + } else if (strlen(ss_client_key) > MIN_CERT_LENGTH) { + sClient->setPrivateKey(ss_client_key); + Log.notice(F("Client key found from ss_client_key" CR)); + } else { + Log.error(F("No client key found" CR)); + gatewayState = GatewayState::ERROR; + } +# endif +# elif defined(ESP8266) + caCert.append(cnt_parameters_array[index].server_cert.c_str()); + sClient->setTrustAnchors(&caCert); + sClient->setBufferSizes(512, 512); +# if MQTT_SECURE_SIGNED_CLIENT + if (pClCert != nullptr) { + delete pClCert; + } + if (pClKey != nullptr) { + delete pClKey; + } + pClCert = new X509List(cnt_parameters_array[index].client_cert.c_str()); + pClKey = new PrivateKey(cnt_parameters_array[index].client_key.c_str()); + sClient->setClientRSACert(pClCert, pClKey); +# endif +# endif + } +} +#endif + +/* + Reboot for Reason Codes + 0 - Erase and Restart + 1 - Repeated MQTT Connection Failure + 2 - Repeated WiFi Connection Failure + 3 - Failed WiFiManager configuration portal + 4 - BLE Scan watchdog + 5 - User requested reboot + 6 - OTA Update + 7 - Parameters changed + 8 - not enough memory to pursue + 9 - SELFTEST end +*/ +void ESPRestart(byte reason) { +#ifdef SecondaryModule + // Erase the secondary module config + String restartCmdStr = "{\"cmd\":\"" + String(restartCmd) + "\"}"; + Log.notice(F("Restarting secondary module : %s" CR), restartCmdStr.c_str()); + receivingDATA(subjectMQTTtoSYSsetSecondaryModule, restartCmdStr.c_str()); + delay(2000); +#endif + StaticJsonDocument<128> jsonBuffer; + JsonObject jsondata = jsonBuffer.to(); + jsondata["reason"] = reason; + jsondata["retain"] = true; + jsondata["uptime"] = uptime(); + jsondata["origin"] = subjectLOGtoMQTT; + pub(jsondata); // We go to MQTT bypassing the queue to ensure the message is sent + // Clean queue + while (!jsonQueue.empty()) { + jsonQueue.pop(); + } + Log.warning(F("Rebooting for reason code %d" CR), reason); +#if defined(ESP32) + ESP.restart(); +#elif defined(ESP8266) + ESP.reset(); +#endif +} + +#if defined(ESPWifiManualSetup) +void setupWiFiFromBuild() { + WiFi.mode(WIFI_STA); + wifiMulti.addAP(wifi_ssid, wifi_password); + Log.trace(F("Connecting to %s" CR), wifi_ssid); +# ifdef wifi_ssid1 + wifiMulti.addAP(wifi_ssid1, wifi_password1); + Log.trace(F("Connecting to %s" CR), wifi_ssid1); +# endif + delay(10); + + // We start by connecting to a WiFi network + +# ifdef NetworkAdvancedSetup + IPAddress ip_adress; + IPAddress gateway_adress; + IPAddress subnet_adress; + IPAddress dns_adress; + ip_adress.fromString(NET_IP); + gateway_adress.fromString(NET_GW); + subnet_adress.fromString(NET_MASK); + dns_adress.fromString(NET_DNS); + + if (!WiFi.config(ip_adress, gateway_adress, subnet_adress, dns_adress)) { + Log.error(F("Wifi STA Failed to configure" CR)); + gatewayState = GatewayState::ERROR; + } + +# endif + + while (wifiMulti.run() != WL_CONNECTED) { + delay(500); + Log.trace(F("." CR)); + failure_number_ntwk++; +# if defined(ESP32) && defined(ZgatewayBT) + if (SYSConfig.powerMode) { + if (failure_number_ntwk > maxConnectionRetryNetwork) { + sleep(); + } + } else { + if (failure_number_ntwk > maxRetryWatchDog) { + ESPRestart(2); + } + } +# else + if (failure_number_ntwk > maxRetryWatchDog) { + ESPRestart(2); + } +# endif + } + Log.notice(F("WiFi ok with manual config credentials" CR)); + displayPrint("Wifi connected"); +} + +#else + +WiFiManager wifiManager; + +//flag for saving data +bool shouldSaveConfig = false; +//do we have been connected once to MQTT + +//callback notifying us of the need to save config +void saveConfigCallback() { + Log.trace(F("Should save config" CR)); + shouldSaveConfig = true; +} + +# ifdef TRIGGER_GPIO +/** + * Identify a long button press to trigger a reset or a failsafe mode +* */ +void blockingWaitForReset() { + if (digitalRead(TRIGGER_GPIO) == LOW) { + delay(50); + if (digitalRead(TRIGGER_GPIO) == LOW) { + Log.trace(F("Trigger button Pressed" CR)); + delay(3000); // reset delay hold + if (digitalRead(TRIGGER_GPIO) == LOW) { + Log.notice(F("Button Held" CR)); +// Switching off the relay during reset or failsafe operations +# ifdef ZactuatorONOFF + uint8_t level = digitalRead(ACTUATOR_ONOFF_GPIO); + if (level == ACTUATOR_ON) { + ActuatorTrigger(); + } +# endif + gatewayState = GatewayState::WAITING_ONBOARDING; + // Checking if the flash has already been erased to identify if we erase it or go into failsafe mode + // going to failsafe mode is done by doing a long button press from a state where the flash has already been erased + if (SPIFFS.begin()) { + Log.trace(F("mounted file system" CR)); + if (SPIFFS.exists("/config.json")) { + Log.notice(F("Erasing ESP Config, restarting" CR)); + eraseConfig(); + } + } + delay(30000); + if (digitalRead(TRIGGER_GPIO) == LOW) { + Log.notice(F("Going into failsafe mode without peripherals" CR)); + // Failsafe mode enable to connect to Wifi or change the firmware without the peripherals setup + failSafeMode = true; + setupWiFiManager(); + } + } + } + } +} + +/** + * Check if button is pressed so as to reset the credentials and parameters stored into the flash +* */ +void checkButton() { + unsigned long timeFromStart = millis(); + // Identify if the reset button is pushed at start + if (timeFromStart < TimeToResetAtStart) { + blockingWaitForReset(); + } else { // When we are not at start we either check the button as a regular input (ZsensorGPIOInput used) or for a reset +# if defined(INPUT_GPIO) && defined(ZsensorGPIOInput) && INPUT_GPIO == TRIGGER_GPIO + MeasureGPIOInput(); +# else + blockingWaitForReset(); +# endif + } +} +# else +void checkButton() {} +# endif + +void saveConfig() { + Log.trace(F("saving configs" CR)); + + int totalSize = 512; +# if !MQTT_BROKER_MODE + for (int i = 0; i < 3; ++i) { // index 0 contains the default values from the build, these values can't be changed at runtime + if (cnt_parameters_array[i].validConnection) { + totalSize += cnt_parameters_array[i].server_cert.length(); + totalSize += cnt_parameters_array[i].client_cert.length(); + totalSize += cnt_parameters_array[i].client_key.length(); + totalSize += cnt_parameters_array[i].ota_server_cert.length(); + } + } +# endif + + Log.notice(F("Total size: %d" CR), totalSize); + + DynamicJsonDocument json(512 + totalSize); + +# if !MQTT_BROKER_MODE + for (int i = 0; i < 3; ++i) { + if (cnt_parameters_array[i].validConnection) { + char index_suffix[2]; + if (i == 0) { + index_suffix[0] = '\0'; + } else { + index_suffix[0] = '0' + i; + index_suffix[1] = '\0'; + } + char key[mqtt_key_max_size]; + if (cnt_parameters_array[i].server_cert.length() > MIN_CERT_LENGTH) { + strcpy(key, "mqtt_broker_cert"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].server_cert; + } + if (cnt_parameters_array[i].client_cert.length() > MIN_CERT_LENGTH) { + strcpy(key, "mqtt_client_cert"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].client_cert; + } + if (cnt_parameters_array[i].client_key.length() > MIN_CERT_LENGTH) { + strcpy(key, "mqtt_client_key"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].client_key; + } + if (cnt_parameters_array[i].ota_server_cert.length() > MIN_CERT_LENGTH) { + strcpy(key, "ota_server_cert"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].ota_server_cert; + } + strcpy(key, "mqtt_server"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].mqtt_server; + strcpy(key, "mqtt_port"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].mqtt_port; + strcpy(key, "mqtt_user"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].mqtt_user; + strcpy(key, "mqtt_pass"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].mqtt_pass; + strcpy(key, "mqtt_broker_secure"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].isConnectionSecure; + strcpy(key, "mqtt_iscertvalid"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].isCertValidate; + strcpy(key, "valid_cnt"); + strcat(key, index_suffix); + json[key] = cnt_parameters_array[i].validConnection; + } + } + + if (cnt_parameters_array[cnt_index].validConnection) { + json["cnt_index"] = cnt_index; + } +# endif + + json["mqtt_topic"] = mqtt_topic; +# ifdef ZmqttDiscovery + json["discovery_prefix"] = discovery_prefix; +# endif + json["gateway_name"] = gateway_name; + json["ota_pass"] = ota_pass; + + File configFile = SPIFFS.open("/config.json", "w"); + if (!configFile) { + Log.error(F("failed to open config file for writing" CR)); + gatewayState = GatewayState::ERROR; + } + + serializeJson(json, configFile); + configFile.close(); +} + +bool loadConfigFromFlash() { + Log.trace(F("mounting FS..." CR)); + bool result = false; + + if (SPIFFS.begin()) { + Log.trace(F("mounted file system" CR)); + } else { + Log.warning(F("failed to mount FS -> formating" CR)); + SPIFFS.format(); + if (SPIFFS.begin()) + Log.trace(F("mounted file system after formating" CR)); + } + if (SPIFFS.exists("/config.json")) { + //file exists, reading and loading + Log.trace(F("reading config file" CR)); + File configFile = SPIFFS.open("/config.json", "r"); + if (configFile) { + Log.trace(F("opened config file" CR)); + DynamicJsonDocument json(configFile.size() * 2); + auto error = deserializeJson(json, configFile); + if (error) { + Log.error(F("deserialize config failed: %s, buffer capacity: %u" CR), error.c_str(), json.capacity()); + gatewayState = GatewayState::ERROR; + } + if (!json.isNull()) { + Log.trace(F("\nparsed json, size: %u" CR), json.memoryUsage()); + // Print json to serial port + //serializeJsonPretty(json, Serial); + +# if !MQTT_BROKER_MODE + for (int i = 0; i < 3; ++i) { + char index_suffix[2]; // Large enough for 0, 1, or 2 and the null terminator + if (i == 0) { + index_suffix[0] = '\0'; // Empty string + } else { + index_suffix[0] = '0' + i; // Convert int to char + index_suffix[1] = '\0'; // Null terminator + } + char key[mqtt_key_max_size]; + strcpy(key, "mqtt_broker_cert"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + cnt_parameters_array[i].server_cert = json[key].as(); + } + strcpy(key, "mqtt_client_cert"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + cnt_parameters_array[i].client_cert = json[key].as(); + } + strcpy(key, "mqtt_client_key"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + cnt_parameters_array[i].client_key = json[key].as(); + } + strcpy(key, "ota_server_cert"); + strcat(key, index_suffix); + if (json.containsKey(key)) { +# ifdef ESP32 + // Read hash from the file + std::string hash = generateHash(json["ota_server_cert"]); + // Compare the hash with the expected hash + if (hash == GITHUB_OTA_SERVER_CERT_HASH) { + // Do nothing + Log.warning(F("Old Github OTA server detected, skipping" CR)); + } else { + Log.notice(F("OTA server cert hash: %s" CR), hash.c_str()); + cnt_parameters_array[i].ota_server_cert = json["ota_server_cert"].as(); + } +# else + cnt_parameters_array[i].ota_server_cert = json["ota_server_cert"].as(); +# endif + } + strcpy(key, "mqtt_server"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + strcpy(cnt_parameters_array[i].mqtt_server, json[key].as()); + } + strcpy(key, "mqtt_port"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + strcpy(cnt_parameters_array[i].mqtt_port, json[key].as()); + } + strcpy(key, "mqtt_user"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + strcpy(cnt_parameters_array[i].mqtt_user, json[key].as()); + } + strcpy(key, "mqtt_pass"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + strcpy(cnt_parameters_array[i].mqtt_pass, json[key].as()); + } + strcpy(key, "mqtt_broker_secure"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + cnt_parameters_array[i].isConnectionSecure = json[key].as(); + } + strcpy(key, "mqtt_iscertvalid"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + cnt_parameters_array[i].isCertValidate = json[key].as(); + } + strcpy(key, "valid_cnt"); + strcat(key, index_suffix); + if (json.containsKey(key)) { + cnt_parameters_array[i].validConnection = json[key].as(); + } else if (i == CNT_DEFAULT_INDEX) { + // For backward compatibility, if valid_cnt is not found, we assume the connection is valid for CNT_DEFAULT_INDEX + Log.warning(F("valid_cnt not found, assuming connection is valid" CR)); + cnt_parameters_array[i].validConnection = true; + } + } + if (json.containsKey("cnt_index")) { + cnt_index = json["cnt_index"].as(); + } +# endif + if (json.containsKey("mqtt_topic")) + strcpy(mqtt_topic, json["mqtt_topic"]); +# ifdef ZmqttDiscovery + if (json.containsKey("discovery_prefix")) + strcpy(discovery_prefix, json["discovery_prefix"]); +# endif + if (json.containsKey("gateway_name")) + strcpy(gateway_name, json["gateway_name"]); + if (json.containsKey("ota_pass")) { + strcpy(ota_pass, json["ota_pass"]); +# ifdef WM_PWD_FROM_MAC // From ESP Mac Address, last 8 digits as the password + // Compare the existing ota_pass if ota_pass = OTAPASSWORD then replace with the last 8 digits of the mac address + // This enable user migrating from previous version to have the same WiFi portal password as previously unless they changed it + if (strcmp(ota_pass, "OTAPASSWORD") == 0) { + String s = WiFi.macAddress(); + sprintf(ota_pass, "%.2s%.2s%.2s%.2s", + s.c_str() + 6, s.c_str() + 9, s.c_str() + 12, s.c_str() + 15); + } +# endif + } + result = true; + } else { + Log.warning(F("failed to load json config" CR)); + } + configFile.close(); + } + } else { + Log.notice(F("No config file found defining default values" CR)); +# ifdef USE_MAC_AS_GATEWAY_NAME + String s = WiFi.macAddress(); + sprintf(gateway_name, "%.2s%.2s%.2s%.2s%.2s%.2s", + s.c_str(), s.c_str() + 3, s.c_str() + 6, s.c_str() + 9, s.c_str() + 12, s.c_str() + 15); +# endif +# ifdef WM_PWD_FROM_MAC // From ESP Mac Address, last 8 digits as the password + sprintf(ota_pass, "%.2s%.2s%.2s%.2s", + s.c_str() + 6, s.c_str() + 9, s.c_str() + 12, s.c_str() + 15); +# endif + } + + return result; +} + +void setupWiFiManager() { + delay(10); + WiFi.mode(WIFI_STA); + +# ifdef USE_MAC_AS_GATEWAY_NAME + String s = WiFi.macAddress(); + snprintf(WifiManager_ssid, MAC_NAME_MAX_LEN, "%s_%.2s%.2s", Gateway_Short_Name, s.c_str(), s.c_str() + 3); + strcpy(ota_hostname, WifiManager_ssid); + Log.notice(F("OTA Hostname: %s.local" CR), ota_hostname); +# endif + + wifiManager.setDebugOutput(WM_DEBUG); + + // The extra parameters to be configured (can be either global or just in the setup) + // After connecting, parameter.getValue() will get you the configured value + // id/name placeholder/prompt default +# ifndef WIFIMNG_HIDE_MQTT_CONFIG +# if !MQTT_BROKER_MODE + WiFiManagerParameter custom_mqtt_server("server", "mqtt server", cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_server, parameters_size, " minlength='1' maxlength='64' required"); + WiFiManagerParameter custom_mqtt_port("port", "mqtt port", cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_port, 6, " minlength='1' maxlength='5' required"); + WiFiManagerParameter custom_mqtt_user("user", "mqtt user", cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_user, parameters_size, " maxlength='64'"); + WiFiManagerParameter custom_mqtt_pass("pass", "mqtt pass", MQTT_PASS, parameters_size, " input type='password' maxlength='64'"); + WiFiManagerParameter custom_mqtt_secure("secure", "
mqtt secure", "1", 2, cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure ? "type=\"checkbox\" checked" : "type=\"checkbox\""); + WiFiManagerParameter custom_validate_cert("validate", "
validate cert", "1", 2, cnt_parameters_array[CNT_DEFAULT_INDEX].isCertValidate ? "type=\"checkbox\" checked" : "type=\"checkbox\""); + WiFiManagerParameter custom_mqtt_cert("cert", "
mqtt server cert", "", 4096); + WiFiManagerParameter custom_ota_server_cert("ota_cert", "
ota server cert", "", 4096); +# if MQTT_SECURE_SIGNED_CLIENT + WiFiManagerParameter custom_client_cert("client_cert", "
mqtt client cert", "", 4096); + WiFiManagerParameter custom_client_key("client_key", "
mqtt client key", "", 4096); +# endif +# endif + WiFiManagerParameter custom_mqtt_topic("topic", "mqtt base topic", mqtt_topic, mqtt_topic_max_size, " minlength='1' maxlength='64' required"); + WiFiManagerParameter custom_gateway_name("name", "gateway name", gateway_name, parameters_size, " minlength='1' maxlength='64' required"); + WiFiManagerParameter custom_ota_pass("ota", "gateway password", ota_pass, parameters_size, " input type='password' minlength='8' maxlength='64' required"); +# endif + //WiFiManager + //Local intialization. Once its business is done, there is no need to keep it around + + wifiManager.setConnectTimeout(WiFi_TimeOut); + //Set timeout before going to portal + wifiManager.setConfigPortalTimeout(WifiManager_ConfigPortalTimeOut); + + //set config save notify callback + wifiManager.setSaveConfigCallback(saveConfigCallback); + +//set static IP +# ifdef NetworkAdvancedSetup + Log.trace(F("Adv wifi cfg" CR)); + IPAddress ip_adress; + IPAddress gateway_adress; + IPAddress subnet_adress; + IPAddress dns_adress; + ip_adress.fromString(NET_IP); + gateway_adress.fromString(NET_GW); + subnet_adress.fromString(NET_MASK); + dns_adress.fromString(NET_DNS); + wifiManager.setSTAStaticIPConfig(ip_adress, gateway_adress, subnet_adress, dns_adress); +# endif + +# ifndef WIFIMNG_HIDE_MQTT_CONFIG + //add all your parameters here +# if !MQTT_BROKER_MODE + wifiManager.addParameter(&custom_mqtt_server); + wifiManager.addParameter(&custom_mqtt_port); + wifiManager.addParameter(&custom_mqtt_user); + wifiManager.addParameter(&custom_mqtt_pass); + wifiManager.addParameter(&custom_mqtt_secure); + wifiManager.addParameter(&custom_mqtt_cert); + wifiManager.addParameter(&custom_validate_cert); + wifiManager.addParameter(&custom_ota_server_cert); +# if MQTT_SECURE_SIGNED_CLIENT + wifiManager.addParameter(&custom_client_cert); + wifiManager.addParameter(&custom_client_key); +# endif +# endif + wifiManager.addParameter(&custom_gateway_name); + wifiManager.addParameter(&custom_mqtt_topic); + wifiManager.addParameter(&custom_ota_pass); +# endif + //set minimum quality of signal so it ignores AP's under that quality + wifiManager.setMinimumSignalQuality(MinimumWifiSignalQuality); + + if (SPIFFS.begin()) { + Log.trace(F("mounted file system" CR)); + // Check if the config file exists and prevent the portal from showing if yes + // Showing the portal if the config file exist would enable access to the configuration data and to the ESP update page + // This is a security risk if an attacker has access to the gateway password + if (SPIFFS.exists("/config.json")) { + wifiManager.setEnableConfigPortal(false); + } + } + +# ifdef ESP32_ETHERNET + wifiManager.setBreakAfterConfig(true); // If ethernet is used, we don't want to block the connection by keeping the portal up +# endif + + if (!SYSConfig.offline && !wifi_reconnect_bypass()) // if we didn't connect with saved credential we start Wifimanager web portal + { + Log.notice(F("Connect your phone to WIFI AP: %s with PWD: %s" CR), WifiManager_ssid, ota_pass); + gatewayState = GatewayState::ONBOARDING; + //fetches ssid and pass and tries to connect + //if it does not connect it starts an access point with the specified name + //and goes into a blocking loop awaiting configuration + if (!wifiManager.autoConnect(WifiManager_ssid, ota_pass)) { + Log.warning(F("failed to connect and hit timeout" CR)); + delay(3000); + +# ifdef ESP32 + /* Workaround for bug in arduino core that causes the AP to become unsecure on reboot */ + esp_wifi_set_mode(WIFI_MODE_AP); + esp_wifi_start(); + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_AP, &conf); + conf.ap.ssid_hidden = 1; + esp_wifi_set_config(WIFI_IF_AP, &conf); +# endif + + bool shouldRestart = (gatewayState != GatewayState::BROKER_CONNECTED && !ethConnected && gatewayState != GatewayState::NTWK_CONNECTED); + +# ifdef USE_BLUFI + shouldRestart = shouldRestart && !isStaConnecting(); +# endif + // Restart/sleep only if not connected and not serial communication mode + if (shouldRestart && !SYSConfig.serial) { +# ifdef DEEP_SLEEP_IN_US + sleep(); +# endif + ESPRestart(3); + } + } + } + + displayPrint("Network connected"); + gatewayState = GatewayState::NTWK_CONNECTED; + + if (shouldSaveConfig) { + //read updated parameters + cnt_index = CNT_DEFAULT_INDEX; +# ifndef WIFIMNG_HIDE_MQTT_CONFIG +# if !MQTT_BROKER_MODE + strcpy(cnt_parameters_array[cnt_index].mqtt_server, custom_mqtt_server.getValue()); + strcpy(cnt_parameters_array[cnt_index].mqtt_port, custom_mqtt_port.getValue()); + strcpy(cnt_parameters_array[cnt_index].mqtt_user, custom_mqtt_user.getValue()); + // Check if the MQTT password field contains the default value + if (strcmp(custom_mqtt_pass.getValue(), MQTT_PASS) != 0) { + // If it's not the default password, update the MQTT password + strcpy(cnt_parameters_array[cnt_index].mqtt_pass, custom_mqtt_pass.getValue()); + } + strcpy(mqtt_topic, custom_mqtt_topic.getValue()); + if (mqtt_topic[strlen(mqtt_topic) - 1] != '/' && strlen(mqtt_topic) < parameters_size) { + strcat(mqtt_topic, "/"); + } + + cnt_parameters_array[cnt_index].isConnectionSecure = *custom_mqtt_secure.getValue(); + cnt_parameters_array[cnt_index].isCertValidate = *custom_validate_cert.getValue(); + if (strlen(custom_mqtt_cert.getValue()) > MIN_CERT_LENGTH) { + cnt_parameters_array[cnt_index].server_cert = TheengsUtils::processCert(custom_mqtt_cert.getValue()); + } + if (strlen(custom_ota_server_cert.getValue()) > MIN_CERT_LENGTH) { + cnt_parameters_array[cnt_index].ota_server_cert = TheengsUtils::processCert(custom_ota_server_cert.getValue()); + } +# if MQTT_SECURE_SIGNED_CLIENT + if (strlen(custom_client_cert.getValue()) > MIN_CERT_LENGTH) { + cnt_parameters_array[cnt_index].client_cert = TheengsUtils::processCert(custom_client_cert.getValue()); + } + if (strlen(custom_client_key.getValue()) > MIN_CERT_LENGTH) { + cnt_parameters_array[cnt_index].client_key = TheengsUtils::processCert(custom_client_key.getValue()); + } +# endif +# endif + strcpy(gateway_name, custom_gateway_name.getValue()); + strcpy(ota_pass, custom_ota_pass.getValue()); +# endif + +# if !MQTT_BROKER_MODE + // We suppose the connection is valid (could be tested before) + cnt_parameters_array[cnt_index].validConnection = true; +# endif + + //save the custom parameters to FS + saveConfig(); + } +} +# ifdef ESP32_ETHERNET +void setup_ethernet_esp32() { + bool ethBeginSuccess = false; + WiFi.onEvent(WiFiEvent); +# ifdef NetworkAdvancedSetup + IPAddress ip_adress; + IPAddress gateway_adress; + IPAddress subnet_adress; + IPAddress dns_adress; + ip.fromString(NET_IP); + gateway.fromString(NET_GW); + subnet.fromString(NET_MASK); + Dns.fromString(NET_DNS); + + Log.trace(F("Adv eth cfg" CR)); + ETH.config(ip, gateway, subnet, Dns); + ethBeginSuccess = ETH.begin(); +# else + Log.notice(F("Spl eth cfg" CR)); + ethBeginSuccess = ETH.begin(); +# endif + if (ethBeginSuccess) { + Log.notice(F("Ethernet started" CR)); + while (!ethConnected && failure_number_ntwk <= maxConnectionRetryNetwork) { + delay(500); + Log.notice(F("." CR)); + failure_number_ntwk++; + } + } else { + Log.error(F("Ethernet not started" CR)); + gatewayState = GatewayState::ERROR; + } +} + +void WiFiEvent(WiFiEvent_t event) { + switch (event) { + case ARDUINO_EVENT_ETH_START: + Log.trace(F("Ethernet Started" CR)); + ETH.setHostname(gateway_name); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + Log.notice(F("Ethernet Connected" CR)); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + Log.notice(F("OpenMQTTGateway Ethernet MAC: %s" CR), ETH.macAddress().c_str()); + Log.notice(F("OpenMQTTGateway Ethernet IP: %s" CR), ETH.localIP().toString().c_str()); + Log.notice(F("OpenMQTTGateway Ethernet link speed: %d Mbps" CR), ETH.linkSpeed()); + gatewayState = GatewayState::NTWK_CONNECTED; + ethConnected = true; + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + Log.warning(F("Ethernet Disconnected" CR)); + ethConnected = false; + break; + case ARDUINO_EVENT_ETH_STOP: + Log.warning(F("Ethernet Stopped" CR)); + ethConnected = false; + break; + default: + break; + } +} +# endif +#endif + +#if DEFAULT_LOW_POWER_MODE != DEACTIVATED && defined(ESP32) +/** + * Deep-sleep for the ESP32 - e.g. DEEP_SLEEP_IN_US 30000000 for 30 seconds / wake by ESP32_EXT0_WAKE_PIN/ESP32_EXT1_WAKE_PIN. + * Everything is off and (almost) all execution state is lost. + */ +void sleep() { + if (SYSConfig.powerMode < PowerMode::INTERVAL) + return; + Log.notice(F("Entering deep sleep" CR)); + gatewayState = GatewayState::SLEEPING; + delay(250); // To allow the LEDs to switch off and MQTT message to be sent +# if defined(ZboardM5STACK) || defined(ZboardM5STICKC) || defined(ZboardM5STICKCP) || defined(ZboardM5TOUGH) + sleepScreen(); + esp_sleep_enable_ext0_wakeup((gpio_num_t)SLEEP_BUTTON, LOW); +# endif + Log.trace(F("Deactivating ESP32 components" CR)); +# ifdef ZgatewayBT + stopProcessing(true); + ProcessLock = true; +# endif +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" + adc_power_off(); +# pragma GCC diagnostic pop + esp_wifi_stop(); +# ifdef ESP32_EXT0_WAKE_PIN + Log.notice(F("Entering deep sleep, EXT0 Wakeup by pin : %l." CR), ESP32_EXT0_WAKE_PIN); +# endif +# ifdef ESP32_EXT1_WAKE_PIN + Log.notice(F("Entering deep sleep, EXT1 Wakeup by pin : %l." CR), ESP32_EXT1_WAKE_PIN); +# endif + if (SYSConfig.powerMode == PowerMode::ACTION) { + esp_deep_sleep_start(); + } else if (SYSConfig.powerMode == PowerMode::INTERVAL) { + Log.notice(F("Entering deep sleep for %l us." CR), DEEP_SLEEP_IN_US); + esp_deep_sleep(DEEP_SLEEP_IN_US); + } +} +#else +void sleep() {} +#endif + +void loop() { +#ifndef ESPWifiManualSetup + checkButton(); // check if a reset of wifi/mqtt settings is asked +#endif + +#ifdef ESP8266 + updateAndHandleLEDsTask(); // With ESP8266 we need to update the LEDs in the loop +#endif + if (!SYSConfig.offline) { // Online mode + if (mqttSetupPending) { + setupMQTT(); + mqttSetupPending = false; + } + // When online the MQTT connection callback release the processes + } + if (firstStart) { +#ifdef ZgatewaySERIAL + if (SYSConfig.serial && isSerialReady()) { +# ifdef ZgatewayBT + BTProcessLock = !BTConfig.enabled; +# endif + ProcessLock = false; + firstStart = false; + } +#else + ProcessLock = false; + firstStart = false; +#endif + } + unsigned long now = millis(); + +#ifdef ZgatewaySERIAL // Serial is a module and a communication layer so it's always processed + SERIALtoX(); +#endif + + if (ethConnected || WiFi.status() == WL_CONNECTED) { + if (ethConnected && WiFi.status() == WL_CONNECTED) { + WiFi.disconnect(); // we disconnect the wifi as we are connected to ethernet + } + ArduinoOTA.handle(); + failure_number_ntwk = 0; + if (now > (timer_sys_checks + (TimeBetweenCheckingSYS * 1000)) || !timer_sys_checks) { +#if message_UTCtimestamp || message_unixtimestamp + TheengsUtils::syncNTP(); +#endif + if (!timer_sys_checks) { // Update check at start up only +#if defined(ESP32) && defined(MQTT_HTTPS_FW_UPDATE) + checkForUpdates(); +#endif + } + timer_sys_checks = millis(); + } +#if defined(ZwebUI) && defined(ESP32) + WebUILoop(); +#endif + mqtt->loop(); + if (mqtt->connected()) { // MQTT client is still connected + failure_number_ntwk = 0; + +#ifdef ZmqttDiscovery + // Deactivate autodiscovery after DiscoveryAutoOffTimer. + // Exception: when discovery_republish_on_reconnect is enabled, we never never automatically disable discovery + if (!discovery_republish_on_reconnect && SYSConfig.discovery && (now > lastDiscovery + DiscoveryAutoOffTimer)) + SYSConfig.discovery = false; +#endif + } + } else if (!SYSConfig.offline && !SYSConfig.serial) { // disconnected from network + Log.warning(F("Network disconnected" CR)); + gatewayState = GatewayState::NTWK_DISCONNECTED; + if (!wifi_reconnect_bypass()) { + sleep(); + } else { + gatewayState = GatewayState::NTWK_CONNECTED; + } + } + if (!ProcessLock) { + if (now > (timer_sys_measures + (TimeBetweenReadingSYS * 1000)) || !timer_sys_measures) { + timer_sys_measures = millis(); + stateMeasures(); +#ifdef ZgatewayBT + stateBTMeasures(false); +#endif +#ifdef ZactuatorONOFF + stateONOFFMeasures(); +#endif +#ifdef ZdisplaySSD1306 + stateSSD1306Display(); +#endif +#ifdef ZgatewayLORA + stateLORAMeasures(); +#endif +#if defined(ZgatewayRTL_433) || defined(ZgatewayPilight) || defined(ZgatewayRF) || defined(ZgatewayRF2) || defined(ZactuatorSomfy) + stateRFMeasures(); +#endif +#if defined(ZwebUI) && defined(ESP32) + stateWebUIStatus(); +#endif + } +// Function that doesn't need an active connection +#if defined(ZboardM5STICKC) || defined(ZboardM5STICKCP) || defined(ZboardM5STACK) || defined(ZboardM5TOUGH) + loopM5(); +#endif +#if defined(ZdisplaySSD1306) + loopSSD1306(); +#endif +#ifdef ZsensorBME280 + MeasureTempHumAndPressure(); //Addon to measure Temperature, Humidity, Pressure and Altitude with a Bosch BME280/BMP280 +#endif +#ifdef ZsensorHTU21 + MeasureTempHum(); //Addon to measure Temperature, Humidity, of a HTU21 sensor +#endif +#ifdef ZsensorLM75 + MeasureTemp(); //Addon to measure Temperature of an LM75 sensor +#endif +#ifdef ZsensorAHTx0 + MeasureAHTTempHum(); //Addon to measure Temperature, Humidity, of an 'AHTx0' sensor +#endif +#ifdef ZsensorHCSR04 + MeasureDistance(); //Addon to measure distance with a HC-SR04 +#endif +#ifdef ZsensorBH1750 + MeasureLightIntensity(); //Addon to measure Light Intensity with a BH1750 +#endif +#ifdef ZsensorMQ2 + MeasureGasMQ2(); +#endif +#ifdef ZsensorTEMT6000 + MeasureLightIntensityTEMT6000(); +#endif +#ifdef ZsensorTSL2561 + MeasureLightIntensityTSL2561(); +#endif +#ifdef ZsensorC37_YL83_HMRD + MeasureC37_YL83_HMRDWater(); //Addon for leak detection with a C-37 YL-83 H-MRD +#endif +#ifdef ZsensorDHT + MeasureTempAndHum(); //Addon to measure the temperature with a DHT +#endif +#ifdef ZsensorSHTC3 + MeasureTempAndHum(); //Addon to measure the temperature with a DHT +#endif +#ifdef ZsensorDS1820 + MeasureDS1820Temp(); //Addon to measure the temperature with DS1820 sensor(s) +#endif +#ifdef ZsensorINA226 + MeasureINA226(); +#endif +#ifdef ZsensorHCSR501 + MeasureHCSR501(); +#endif +#ifdef ZsensorGPIOInput + MeasureGPIOInput(); +#endif +#ifdef ZsensorGPIOKeyCode + MeasureGPIOKeyCode(); +#endif +#ifdef ZsensorADC + MeasureADC(); //Addon to measure the analog value of analog pin +#endif +#ifdef ZsensorTouch + MeasureTouch(); +#endif +#ifdef ZgatewayLORA + LORAtoX(); +# ifdef ZmqttDiscovery + if (SYSConfig.discovery) + launchLORADiscovery(false); +# endif +#endif +#ifdef ZgatewayRF + RFtoX(); +#endif +#ifdef ZgatewayRF_RHASK + RFtoMQTT(); +#endif +#ifdef ZgatewayRF2 + RF2toX(); +#endif +#ifdef ZgatewayWeatherStation + ZgatewayWeatherStationtoX(); +#endif +#ifdef ZgatewayGFSunInverter + ZgatewayGFSunInverterMQTT(); +#endif +#ifdef ZgatewayPilight + PilighttoX(); +#endif +#ifdef ZgatewayBT +# ifdef ZmqttDiscovery + if (SYSConfig.discovery) + launchBTDiscovery(false); +# endif +#endif +#ifdef ZgatewaySRFB + SRFBtoX(); +#endif +#ifdef ZgatewayIR + IRtoX(); +#endif +#ifdef Zgateway2G + if (_2GtoX()) + Log.trace(F("2GtoMQTT OK" CR)); +#endif +#ifdef ZgatewayRFM69 + if (RFM69toX()) + Log.trace(F("RFM69toMQTT OK" CR)); +#endif +#ifdef ZactuatorFASTLED + FASTLEDLoop(); +#endif +#ifdef ZactuatorPWM + PWMLoop(); +#endif +#ifdef ZgatewayRTL_433 + RTL_433Loop(); +# ifdef ZmqttDiscovery + if (SYSConfig.discovery) + launchRTL_433Discovery(false); +# endif +#endif + } + // Empty the queue + emptyQueue(); + // Sleep if ready + if (ready_to_sleep) { + sleep(); + } +} + +/** + * Calculate uptime and take into account the millis() rollover + */ +unsigned long uptime() { + static unsigned long lastUptime = 0; + static unsigned long uptimeAdd = 0; + unsigned long uptime = millis() / 1000 + uptimeAdd; + if (uptime < lastUptime) { + uptime += 4294967; + uptimeAdd += 4294967; + } + lastUptime = uptime; + return uptime; +} + +/** + * Calculate internal ESP32 temperature + */ +#if defined(ESP32) && !defined(NO_INT_TEMP_READING) +float intTemperatureRead() { + SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 10, SENS_TSENS_CLK_DIV_S); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); + ets_delay_us(100); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); + ets_delay_us(5); + float temp_f = (float)GET_PERI_REG_BITS2(SENS_SAR_SLAVE_ADDR3_REG, SENS_TSENS_OUT, SENS_TSENS_OUT_S); + float temp_c = (temp_f - 32) / 1.8; + return temp_c; +} +#endif + +/* + Erase config and restart the ESP +*/ +void eraseConfig() { +#ifdef SecondaryModule + // Erase the secondary module config + String eraseCmdStr = "{\"cmd\":\"" + String(eraseCmd) + "\"}"; + Log.notice(F("Erasing secondary module config: %s" CR), eraseCmdStr.c_str()); + receivingDATA(subjectMQTTtoSYSsetSecondaryModule, eraseCmdStr.c_str()); + delay(2000); +#endif + Log.trace(F("Formatting requested, result: %d" CR), SPIFFS.format()); + +#if defined(ESP8266) + WiFi.disconnect(true); + ESP.eraseConfig(); +#else + nvs_flash_erase(); +#endif + ESPRestart(0); +} + +String stateMeasures() { + StaticJsonDocument SYSdata; + + SYSdata["uptime"] = uptime(); + + SYSdata["version"] = OMG_VERSION; +#ifdef LED_ADDRESSABLE + SYSdata["rgbb"] = SYSConfig.rgbbrightness; +#endif +#if USE_BLUFI + SYSdata["blufi"] = SYSConfig.blufi; +#endif + SYSdata["mqtt"] = SYSConfig.mqtt; + SYSdata["serial"] = SYSConfig.serial; +#ifdef ZmqttDiscovery + SYSdata["disc"] = SYSConfig.discovery; + SYSdata["ohdisc"] = SYSConfig.ohdiscovery; +#endif + SYSdata["env"] = ENV_NAME; + uint32_t freeMem; + uint32_t minFreeMem; + freeMem = ESP.getFreeHeap(); +#ifdef ZgatewayRTL_433 + // Some RTL_433 decoders have memory leak, this is a temporary workaround + if (freeMem < MinimumMemory) { + Log.error(F("Not enough memory %d, restarting" CR), freeMem); + gatewayState = GatewayState::ERROR; + ESPRestart(8); + } +#endif + SYSdata["freemem"] = freeMem; +#if !MQTT_BROKER_MODE + SYSdata["mqttp"] = cnt_parameters_array[cnt_index].mqtt_port; + SYSdata["mqtts"] = cnt_parameters_array[cnt_index].isConnectionSecure; + SYSdata["mqttv"] = cnt_parameters_array[cnt_index].isCertValidate; +#endif + SYSdata["msgprc"] = queueLengthSum; + SYSdata["msgblck"] = blockedMessages; + SYSdata["msgrcv"] = receivedMessages; + SYSdata["maxq"] = maxQueueLength; + SYSdata["cnt_index"] = cnt_index; +#ifdef ESP32 + minFreeMem = ESP.getMinFreeHeap(); + SYSdata["minmem"] = minFreeMem; +# ifndef NO_INT_TEMP_READING + SYSdata["tempc"] = TheengsUtils::round2(intTemperatureRead()); +# endif + SYSdata["freestck"] = uxTaskGetStackHighWaterMark(NULL); + SYSdata["powermode"] = SYSConfig.powerMode; +#endif + + SYSdata["eth"] = ethConnected; + if (ethConnected) { +#ifdef ESP32_ETHERNET + SYSdata["mac"] = (char*)ETH.macAddress().c_str(); + SYSdata["ip"] = TheengsUtils::ip2CharArray(ETH.localIP()); + ETH.fullDuplex() ? SYSdata["fd"] = (bool)"true" : SYSdata["fd"] = (bool)"false"; + SYSdata["linkspeed"] = (int)ETH.linkSpeed(); +#endif + } else { + SYSdata["rssi"] = (long)WiFi.RSSI(); + SYSdata["SSID"] = (char*)WiFi.SSID().c_str(); + SYSdata["BSSID"] = (char*)WiFi.BSSIDstr().c_str(); + SYSdata["ip"] = TheengsUtils::ip2CharArray(WiFi.localIP()); + SYSdata["mac"] = (char*)WiFi.macAddress().c_str(); + } +#ifdef ZboardM5STACK + M5.Power.begin(); + SYSdata["m5battlevel"] = (int8_t)M5.Power.getBatteryLevel(); + SYSdata["m5ischarging"] = (bool)M5.Power.isCharging(); + SYSdata["m5ischargefull"] = (bool)M5.Power.isChargeFull(); +#endif +#if defined(ZboardM5STICKC) || defined(ZboardM5STICKCP) || defined(ZboardM5TOUGH) + M5.Axp.EnableCoulombcounter(); + SYSdata["m5batvoltage"] = (float)M5.Axp.GetBatVoltage(); + SYSdata["m5batcurrent"] = (float)M5.Axp.GetBatCurrent(); + SYSdata["m5vinvoltage"] = (float)M5.Axp.GetVinVoltage(); + SYSdata["m5vincurrent"] = (float)M5.Axp.GetVinCurrent(); + SYSdata["m5vbusvoltage"] = (float)M5.Axp.GetVBusVoltage(); + SYSdata["m5vbuscurrent"] = (float)M5.Axp.GetVBusCurrent(); + SYSdata["m5tempaxp"] = (float)M5.Axp.GetTempInAXP192(); + SYSdata["m5batpower"] = (float)M5.Axp.GetBatPower(); + SYSdata["m5batchargecurrent"] = (float)M5.Axp.GetBatChargeCurrent(); + SYSdata["m5apsvoltage"] = (float)M5.Axp.GetAPSVoltage(); +#endif + SYSdata["modules"] = modules; + + SYSdata["origin"] = subjectSYStoMQTT; + enqueueJsonObject(SYSdata); + + char jsonChar[100]; + serializeJson(modules, jsonChar, 99); + + String _modules = jsonChar; + + _modules.replace(",", ", "); + _modules.replace("[", ""); + _modules.replace("]", ""); + _modules.replace("\"", "'"); + + SYSdata["modules"] = _modules.c_str(); + + String output; + serializeJson(SYSdata, output); + Log.notice(F("SYS json: %s" CR), output.c_str()); + return output; +} + +#if defined(ZgatewayRF) || defined(ZgatewayRF_RHASK) || defined(ZgatewayIR) || defined(ZgatewaySRFB) || defined(ZgatewayWeatherStation) || defined(ZgatewayRTL_433) +/** + * Store signal values from RF, IR, SRFB or Weather stations so as to avoid duplicates + */ +void storeSignalValue(uint64_t MQTTvalue) { + unsigned long now = millis(); + // find oldest value of the buffer + int o = getMin(); + Log.trace(F("Min ind: %d" CR), o); + // replace it by the new one + receivedSignal[o].value = MQTTvalue; + receivedSignal[o].time = now; + + // Casting "receivedSignal[o].value" to (unsigned long) because ArduinoLog doesn't support uint64_t for ESP's + Log.trace(F("store code : %u / %u" CR), (unsigned long)receivedSignal[o].value, receivedSignal[o].time); + Log.trace(F("Col: val/timestamp" CR)); + for (int i = 0; i < struct_size; i++) { + Log.trace(F("mem code : %u / %u" CR), (unsigned long)receivedSignal[i].value, receivedSignal[i].time); + } +} + +/** + * get oldest time index from the values array from RF, IR, SRFB or Weather stations so as to avoid duplicates + */ +int getMin() { + unsigned int minimum = receivedSignal[0].time; + int minindex = 0; + for (int i = 1; i < struct_size; i++) { + if (receivedSignal[i].time < minimum) { + minimum = receivedSignal[i].time; + minindex = i; + } + } + return minindex; +} + +/** + * Check if signal values from RF, IR, SRFB or Weather stations are duplicates + */ +bool isAduplicateSignal(uint64_t value) { + Log.trace(F("isAdupl?" CR)); + for (int i = 0; i < struct_size; i++) { + if (receivedSignal[i].value == value) { + unsigned long now = millis(); + if (now - receivedSignal[i].time < time_avoid_duplicate) { // change + Log.trace(F("no pub. dupl" CR)); + return true; + } + } + } + return false; +} +#endif + +void receivingDATA(const char* topicOri, const char* datacallback) { + std::string strTopicOri = topicOri; + StaticJsonDocument jsonBuffer; + JsonObject jsondata = jsonBuffer.to(); + DeserializationError error = deserializeJson(jsonBuffer, datacallback); + if (error || jsondata.isNull()) { + Log.error(F("deserialize MQTT data failed: %s" CR), error.c_str()); + gatewayState = GatewayState::ERROR; + return; + } + if (topicOri == nullptr || strcmp(topicOri, "") == 0) { + if (jsondata.containsKey("target") && jsondata["target"].is()) { + strTopicOri = jsondata["target"].as(); + Log.trace(F("BUS Msg target: %s" CR), strTopicOri.c_str()); + } else if (jsondata.containsKey("origin") && jsondata["origin"].is()) { + strTopicOri = jsondata["origin"].as(); + Log.trace(F("BUS Msg origin: %s" CR), strTopicOri.c_str()); + } + } else { +#if defined(SecondaryModule) // Redirect certain commands to Serial + String topicSerial = String(mqtt_topic) + String(gateway_name) + subjectMQTTtoSERIAL; + const char* cSecondaryModule = SecondaryModule; + if (strcmp(cSecondaryModule, "BT") == 0) { + if (cmpToMainTopic(topicOri, subjectMQTTtoBTset)) { + strTopicOri = topicSerial.c_str(); + jsondata["target"] = subjectMQTTtoBTset; + } else if (cmpToMainTopic(topicOri, subjectMQTTtoBT)) { + strTopicOri = topicSerial.c_str(); + jsondata["target"] = subjectMQTTtoBT; + } + } + if (cmpToMainTopic(topicOri, subjectMQTTtoSYSsetSecondaryModule)) { + strTopicOri = topicSerial.c_str(); + jsondata["target"] = subjectMQTTtoSYSsetSecondaryModule; + } +#endif + Log.trace(F("MQTT Msg topic: %s" CR), strTopicOri.c_str()); + } + +#if defined(ZgatewayRF) || defined(ZgatewayIR) || defined(ZgatewaySRFB) || defined(ZgatewayWeatherStation) + if (strstr(strTopicOri.c_str(), subjectMultiGTWKey) != NULL) { // storing received value so as to avoid publishing this value if it has been already sent by this or another OpenMQTTGateway + uint64_t data = jsondata.isNull() ? strtoull(datacallback, NULL, 10) : jsondata["value"]; + if (data != 0 && !isAduplicateSignal(data)) { + storeSignalValue(data); + } + } +#endif + + if (!jsondata.isNull()) { // json object ok -> json decoding + // log the received json + String buffer = ""; + serializeJson(jsondata, buffer); + //Log.notice(F("[ MQTT->OMG ]: %s" CR), buffer.c_str()); + +#ifdef ZgatewayPilight // ZgatewayPilight is only defined with json publishing due to its numerous parameters + XtoPilight(strTopicOri.c_str(), jsondata); +#endif +#if defined(ZgatewayRTL_433) || defined(ZgatewayPilight) || defined(ZgatewayRF) || defined(ZgatewayRF2) || defined(ZactuatorSomfy) + XtoRFset(strTopicOri.c_str(), jsondata); +#endif +#if jsonReceiving +# ifdef ZgatewayLORA + XtoLORA(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZgatewayRF + XtoRF(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZgatewayRF2 + XtoRF2(strTopicOri.c_str(), jsondata); +# endif +# ifdef Zgateway2G + Xto2G(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZgatewaySRFB + XtoSRFB(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZgatewayIR + XtoIR(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZgatewayRFM69 + XtoRFM69(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZgatewayBT + XtoBT(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZactuatorFASTLED + XtoFASTLED(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZactuatorPWM + XtoPWM(strTopicOri.c_str(), jsondata); +# endif +# if defined(ZboardM5STICKC) || defined(ZboardM5STICKCP) || defined(ZboardM5STACK) || defined(ZboardM5TOUGH) + XtoM5(strTopicOri.c_str(), jsondata); +# endif +# if defined(ZdisplaySSD1306) + XtoSSD1306(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZactuatorONOFF + XtoONOFF(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZactuatorSomfy + XtoSomfy(strTopicOri.c_str(), jsondata); +# endif +# ifdef ZgatewaySERIAL + XtoSERIAL(strTopicOri.c_str(), jsondata); +# endif +# ifdef MQTT_HTTPS_FW_UPDATE + MQTTHttpsFWUpdate(strTopicOri.c_str(), jsondata); +# endif +# if defined(ZwebUI) && defined(ESP32) + XtoWebUI(strTopicOri.c_str(), jsondata); +# endif +#endif + + XtoSYS(strTopicOri.c_str(), jsondata); + } else { // not a json object --> simple decoding +#if simpleReceiving +# ifdef ZgatewayLORA + XtoLORA(strTopicOri.c_str(), datacallback); +# endif +# ifdef ZgatewayRF + XtoRF(strTopicOri.c_str(), datacallback); +# endif +# ifdef ZgatewayRF315 + XtoRF315(strTopicOri.c_str(), datacallback); +# endif +# ifdef ZgatewayRF2 + XtoRF2(strTopicOri.c_str(), datacallback); +# endif +# ifdef Zgateway2G + Xto2G(strTopicOri.c_str(), datacallback); +# endif +# ifdef ZgatewaySRFB + XtoSRFB(strTopicOri.c_str(), datacallback); +# endif +# ifdef ZgatewayRFM69 + XtoRFM69(strTopicOri.c_str(), datacallback); +# endif +# ifdef ZactuatorFASTLED + XtoFASTLED(strTopicOri.c_str(), datacallback); +# endif +#endif +#ifdef ZactuatorONOFF + XtoONOFF(strTopicOri.c_str(), datacallback); +#endif + } +} + +#ifdef MQTT_HTTPS_FW_UPDATE +String latestVersion; +# ifdef ESP32 +# include + +# include "zzHTTPUpdate.h" + +# if CHECK_OTA_UPDATE +/** + * Check on a server the latest version information to build a releaseLink + * The release link will be used when the user trigger an OTA update command + * Only available for ESP32 + */ +bool checkForUpdates() { + Log.notice(F("Update check, free heap: %d"), ESP.getFreeHeap()); + HTTPClient http; + http.setTimeout((GeneralTimeOut - 1) * 1000); // -1 to avoid WDT + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + + std::string ota_cert; +# if !MQTT_BROKER_MODE + if (cnt_parameters_array[cnt_index].ota_server_cert.length() > MIN_CERT_LENGTH) { + Log.notice(F("Using memory cert" CR)); + ota_cert = cnt_parameters_array[cnt_index].ota_server_cert; + } else +# endif + { + Log.notice(F("Using config cert" CR)); + ota_cert = OTAserver_cert; + } + + http.begin(OTA_JSON_URL, ota_cert.c_str()); + int httpCode = http.GET(); + StaticJsonDocument jsonBuffer; + JsonObject jsondata = jsonBuffer.to(); + + if (httpCode > 0) { //Check for the returning code + String payload = http.getString(); + auto error = deserializeJson(jsonBuffer, payload); + if (error) { + Log.error(F("Deserialize MQTT data failed: %s" CR), error.c_str()); + gatewayState = GatewayState::ERROR; + } + Log.trace(F("HttpCode %d" CR), httpCode); + Log.trace(F("Payload %s" CR), payload.c_str()); + } else { + Log.error(F("Error on HTTP request")); + gatewayState = GatewayState::ERROR; + } + http.end(); //Free the resources + Log.notice(F("Update check done, free heap: %d"), ESP.getFreeHeap()); + if (jsondata.containsKey("latest_version")) { + jsondata["installed_version"] = OMG_VERSION; + jsondata["entity_picture"] = ENTITY_PICTURE; + if (!jsondata.containsKey("release_summary")) + jsondata["release_summary"] = ""; + latestVersion = jsondata["latest_version"].as(); + jsondata["origin"] = subjectRLStoMQTT; + jsondata["retain"] = true; + enqueueJsonObject(jsondata); + + Log.trace(F("Update file found on server" CR)); + return true; + } else { + Log.trace(F("No update file found on server" CR)); + return false; + } +} + +# else +bool checkForUpdates() { + return false; +} +# endif +# elif ESP8266 +# include +# endif + +void MQTTHttpsFWUpdate(const char* topicOri, JsonObject& HttpsFwUpdateData) { + if (strstr(topicOri, subjectMQTTtoSYSupdate) != NULL) { + const char* version = HttpsFwUpdateData["version"] | "latest"; + if (version && ((strlen(version) != strlen(OMG_VERSION)) || strcmp(version, OMG_VERSION) != 0)) { + const char* url = HttpsFwUpdateData["url"]; + String systemUrl; + if (url) { + if (!strstr((url + (strlen(url) - 5)), ".bin")) { + Log.error(F("Invalid firmware extension" CR)); + gatewayState = GatewayState::ERROR; + return; + } +# if MQTT_HTTPS_FW_UPDATE_USE_PASSWORD > 0 + const char* pwd = HttpsFwUpdateData["password"]; + if (pwd) { + if (strcmp(pwd, ota_pass) != 0) { + Log.error(F("Invalid OTA password" CR)); + gatewayState = GatewayState::ERROR; + return; + } + } else { + Log.error(F("No password sent" CR)); + gatewayState = GatewayState::ERROR; + return; + } +# endif +# ifdef ESP32 + } else if (strcmp(version, "latest") == 0) { + systemUrl = RELEASE_LINK + latestVersion + "/" + ENV_NAME + "-firmware.bin"; + url = systemUrl.c_str(); + Log.notice(F("Using system OTA url with latest version %s" CR), url); + } else if (strcmp(version, "dev") == 0) { + systemUrl = String(RELEASE_LINK_DEV) + ENV_NAME + "-firmware.bin"; + url = systemUrl.c_str(); + Log.notice(F("Using system OTA url with dev version %s" CR), url); + } else if (version[0] == 'v') { + systemUrl = String(RELEASE_LINK) + version + "/" + ENV_NAME + "-firmware.bin"; + url = systemUrl.c_str(); + Log.notice(F("Using system OTA url with defined version %s" CR), url); +# endif + } else { + Log.error(F("Invalid URL" CR)); + gatewayState = GatewayState::ERROR; + return; + } +# ifdef ESP32 + ProcessLock = true; +# ifdef ZgatewayBT + stopProcessing(true); +# endif +# endif + Log.warning(F("Starting firmware update with %d freeHeap" CR), ESP.getFreeHeap()); + gatewayState = GatewayState::REMOTE_OTA_IN_PROGRESS; + + StaticJsonDocument jsondata; + jsondata["release_summary"] = "Update in progress ..."; + jsondata["origin"] = subjectRLStoMQTT; + enqueueJsonObject(jsondata); + + std::string ota_cert = TheengsUtils::processCert(HttpsFwUpdateData["ota_server_cert"] | ""); + Log.notice(F("OTA cert: %s" CR), ota_cert.c_str()); + if (ota_cert.length() < MIN_CERT_LENGTH && !strstr(url, "http:")) { +# if !MQTT_BROKER_MODE + if (cnt_parameters_array[cnt_index].ota_server_cert.length() > MIN_CERT_LENGTH) { + Log.notice(F("Using memory cert" CR)); + ota_cert = cnt_parameters_array[cnt_index].ota_server_cert.c_str(); + } else +# endif + { + Log.notice(F("Using config cert" CR)); + ota_cert = OTAserver_cert; + } + } + + t_httpUpdate_return result = HTTP_UPDATE_FAILED; + if (strstr(url, "http:")) { + Log.notice(F("Http update" CR)); + WiFiClient update_client; +# ifdef ESP32 + httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + result = httpUpdate.update(update_client, url); +# elif ESP8266 + ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + result = ESPhttpUpdate.update(update_client, url); +# endif + + } else { + WiFiClientSecure update_client; +# if !MQTT_BROKER_MODE + if (cnt_parameters_array[cnt_index].isConnectionSecure) { + mqtt.reset(); + mqttSetupPending = true; + update_client = *static_cast(eClient.get()); + } +# endif + TheengsUtils::syncNTP(); + +# ifdef ESP32 + update_client.setCACert(ota_cert.c_str()); + update_client.setTimeout(12); + httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + httpUpdate.rebootOnUpdate(false); + result = httpUpdate.update(update_client, url); +# elif ESP8266 + caCert.append(ota_cert.c_str()); + update_client.setTrustAnchors(&caCert); + update_client.setTimeout(12000); + ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + ESPhttpUpdate.rebootOnUpdate(false); + result = ESPhttpUpdate.update(update_client, url); +# endif + } + + switch (result) { + case HTTP_UPDATE_FAILED: +# ifdef ESP32 + Log.error(F("HTTP_UPDATE_FAILED Error (%d): %s\n" CR), httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); +# elif ESP8266 + Log.error(F("HTTP_UPDATE_FAILED Error (%d): %s\n" CR), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); +# endif + gatewayState = GatewayState::ERROR; + break; + + case HTTP_UPDATE_NO_UPDATES: + Log.notice(F("HTTP_UPDATE_NO_UPDATES" CR)); + break; + + case HTTP_UPDATE_OK: + Log.notice(F("HTTP_UPDATE_OK" CR)); + jsondata["release_summary"] = "Update success !"; + jsondata["installed_version"] = latestVersion; + jsondata["origin"] = subjectRLStoMQTT; + enqueueJsonObject(jsondata); +# if !MQTT_BROKER_MODE + if (cnt_index != 0) // We don't enable the change of cert provided at build time + cnt_parameters_array[cnt_index].ota_server_cert = ota_cert; +# endif +# ifndef ESPWifiManualSetup + saveConfig(); +# endif + ESPRestart(6); + break; + } + + ESPRestart(6); + } + } +} +#endif + +#if !MQTT_BROKER_MODE +/** + * Read the certificates from the memory and publish a hash of the cert to the broker for identification purposes +*/ +void readCntParameters(int index) { + if (index < 0 || index > 2) { + Log.warning(F("Invalid cnt index" CR)); + return; + } + StaticJsonDocument jsonBuffer; + JsonObject jsondata = jsonBuffer.to(); + jsondata["cnt_index"] = index; + jsondata["valid_cnt"] = cnt_parameters_array[index].validConnection; + if (cnt_parameters_array[index].server_cert.length() > MIN_CERT_LENGTH) { + jsondata["mqtt_server_cert_hash"] = generateHash(cnt_parameters_array[index].server_cert); + } + if (cnt_parameters_array[index].client_cert.length() > MIN_CERT_LENGTH) { + jsondata["mqtt_client_cert_hash"] = generateHash(cnt_parameters_array[index].client_cert); + } + if (cnt_parameters_array[index].client_key.length() > MIN_CERT_LENGTH) { + jsondata["mqtt_client_key_hash"] = generateHash(cnt_parameters_array[index].client_key); + } + if (cnt_parameters_array[index].ota_server_cert.length() > MIN_CERT_LENGTH) { + jsondata["ota_server_cert_hash"] = generateHash(cnt_parameters_array[index].ota_server_cert); + } + jsondata["mqtt_server"] = cnt_parameters_array[index].mqtt_server; + jsondata["mqtt_port"] = cnt_parameters_array[index].mqtt_port; + jsondata["mqtt_user"] = cnt_parameters_array[index].mqtt_user; + jsondata["mqtt_pass"] = generateHash(cnt_parameters_array[index].mqtt_pass); + jsondata["mqtt_secure"] = cnt_parameters_array[index].isConnectionSecure; + jsondata["mqtt_validate"] = cnt_parameters_array[index].isCertValidate; + jsondata["origin"] = subjectSYStoMQTT; + enqueueJsonObject(jsondata); +} +#endif + +void XtoSYS(const char* topicOri, JsonObject& SYSdata) { // json object decoding + if (cmpToMainTopic(topicOri, subjectMQTTtoSYSset)) { + bool restartESP = false; + bool publishState = false; + Log.trace(F("MQTTtoSYS json" CR)); + if (SYSdata.containsKey("cmd")) { + const char* cmd = SYSdata["cmd"]; + Log.notice(F("Command: %s" CR), cmd); + if (strstr(cmd, restartCmd) != NULL) { //restart + ESPRestart(5); + } else if (strstr(cmd, eraseCmd) != NULL) { //erase and restart + eraseConfig(); + } else if (strstr(cmd, statusCmd) != NULL) { + publishState = true; + } + } +#ifdef LED_ADDRESSABLE + if (SYSdata.containsKey("rgbb") && SYSdata["rgbb"].is()) { + if (SYSdata["rgbb"] >= 0 && SYSdata["rgbb"] <= 255) { + SYSConfig.rgbbrightness = TheengsUtils::round2(SYSdata["rgbb"]); + ledManager.setBrightness(SYSConfig.rgbbrightness); +# ifdef ZactuatorONOFF + updatePowerIndicator(); +# endif + Log.notice(F("RGB brightness: %d" CR), SYSConfig.rgbbrightness); + publishState = true; + } else { + Log.warning(F("RGB brightness value invalid - ignoring command" CR)); + } + } +#endif +#ifdef ZmqttDiscovery + if (SYSdata.containsKey("ohdisc") && SYSdata["ohdisc"].is()) { + SYSConfig.ohdiscovery = SYSdata["ohdisc"]; + Log.notice(F("OpenHAB discovery: %T" CR), SYSConfig.ohdiscovery); + publishState = true; + } +#endif + if (SYSdata.containsKey("wifi_ssid") && SYSdata["wifi_ssid"].is() && SYSdata.containsKey("wifi_pass") && SYSdata["wifi_pass"].is()) { +#ifdef ESP32 + ProcessLock = true; +# ifdef ZgatewayBT + stopProcessing(true); +# endif +#endif + String prev_ssid = WiFi.SSID(); + String prev_pass = WiFi.psk(); + // NOTE: There's no need to disconnect MQTT manually + WiFi.disconnect(true); + + Log.warning(F("Attempting connection to new AP %s" CR), (const char*)SYSdata["wifi_ssid"]); + WiFi.begin((const char*)SYSdata["wifi_ssid"], (const char*)SYSdata["wifi_pass"]); +#if defined(WifiGMode) || defined(WifiPower) + setESPWifiProtocolTxPower(); +#endif + WiFi.waitForConnectResult(WiFi_TimeOut * 1000); + + if (WiFi.status() != WL_CONNECTED) { + Log.warning(F("Failed to connect to new AP; falling back" CR)); + WiFi.disconnect(true); + WiFi.begin(prev_ssid.c_str(), prev_pass.c_str()); +#if defined(WifiGMode) || defined(WifiPower) + setESPWifiProtocolTxPower(); +#endif + } + restartESP = true; + } + + if ((SYSdata.containsKey("mqtt_topic") && SYSdata["mqtt_topic"].is()) || +#ifdef ZmqttDiscovery + (SYSdata.containsKey("discovery_prefix") && SYSdata["discovery_prefix"].is()) || +#endif + (SYSdata.containsKey("gateway_name") && SYSdata["gateway_name"].is()) || + (SYSdata.containsKey("gw_pass") && SYSdata["gw_pass"].is())) { + if (SYSdata.containsKey("mqtt_topic")) { + strncpy(mqtt_topic, SYSdata["mqtt_topic"], parameters_size); + } +#ifdef ZmqttDiscovery + if (SYSdata.containsKey("discovery_prefix")) { + strncpy(discovery_prefix, SYSdata["discovery_prefix"], parameters_size); + } +#endif + if (SYSdata.containsKey("gateway_name")) { + strncpy(gateway_name, SYSdata["gateway_name"], parameters_size); + } + if (SYSdata.containsKey("gw_pass")) { + strncpy(ota_pass, SYSdata["gw_pass"], parameters_size); + restartESP = true; + } +#ifndef ESPWifiManualSetup + saveConfig(); +#endif + mqttSetupPending = true; // trigger reconnect in loop using the new topic/name + } + +#if !MQTT_BROKER_MODE +# ifdef MQTTsetMQTT + + bool save_cnt = false; + bool read_cnt = false; + bool test_cnt = false; + + if (SYSdata.containsKey("save_cnt") && SYSdata["save_cnt"].is()) { + save_cnt = SYSdata["save_cnt"].as(); + } + if (SYSdata.containsKey("read_cnt") && SYSdata["read_cnt"].is()) { + read_cnt = SYSdata["read_cnt"].as(); + } + if (SYSdata.containsKey("test_cnt") && SYSdata["test_cnt"].is()) { + test_cnt = SYSdata["test_cnt"].as(); + } + + if (SYSdata.containsKey("cnt_index") && SYSdata["cnt_index"].is()) { + if (SYSdata["cnt_index"].as() < 0 || SYSdata["cnt_index"].as() > 2) { + Log.warning(F("Invalid cnt index provided - ignoring command" CR)); + return; + } + + // we're overwrittng parameters, create a backup to be able to revert + cnt_parameters_backup.reset(new ss_cnt_parameters_backup()); + cnt_parameters_backup->cnt_index = cnt_index; + cnt_parameters_backup->saveOnSuccess = save_cnt; + + cnt_index = SYSdata["cnt_index"].as(); + cnt_parameters_backup->parameters = cnt_parameters_array[cnt_index]; + + Log.notice(F("MQTT cnt index %d" CR), cnt_index); + + if (SYSdata.containsKey("mqtt_user") && SYSdata["mqtt_user"].is() && SYSdata.containsKey("mqtt_pass") && SYSdata["mqtt_pass"].is()) { + strcpy(cnt_parameters_array[cnt_index].mqtt_user, SYSdata["mqtt_user"]); + strcpy(cnt_parameters_array[cnt_index].mqtt_pass, SYSdata["mqtt_pass"]); + cnt_parameters_array[cnt_index].validConnection = false; + } + + if (SYSdata.containsKey("mqtt_server") && SYSdata["mqtt_server"].is()) { + strcpy(cnt_parameters_array[cnt_index].mqtt_server, SYSdata["mqtt_server"]); + cnt_parameters_array[cnt_index].validConnection = false; + } + + if (SYSdata.containsKey("mqtt_port") && SYSdata["mqtt_port"].is()) { + strcpy(cnt_parameters_array[cnt_index].mqtt_port, SYSdata["mqtt_port"]); + cnt_parameters_array[cnt_index].validConnection = false; + } + + if (SYSdata.containsKey("mqtt_secure") && SYSdata["mqtt_secure"].is()) { + cnt_parameters_array[cnt_index].isConnectionSecure = SYSdata["mqtt_secure"].as(); + cnt_parameters_array[cnt_index].validConnection = false; + } + + if (SYSdata.containsKey("mqtt_validate") && SYSdata["mqtt_validate"].is()) { + cnt_parameters_array[cnt_index].isCertValidate = SYSdata["mqtt_validate"].as(); + cnt_parameters_array[cnt_index].validConnection = false; + } + + // Copy the certs to the memory + if (SYSdata.containsKey("mqtt_server_cert") && SYSdata["mqtt_server_cert"].is()) { + cnt_parameters_array[cnt_index].server_cert = TheengsUtils::processCert(SYSdata["mqtt_server_cert"].as()); + Log.trace(F("Assigning server cert %s" CR), generateHash(cnt_parameters_array[cnt_index].server_cert).c_str()); + cnt_parameters_array[cnt_index].validConnection = false; + } + if (SYSdata.containsKey("mqtt_client_cert") && SYSdata["mqtt_client_cert"].is()) { + cnt_parameters_array[cnt_index].client_cert = TheengsUtils::processCert(SYSdata["mqtt_client_cert"].as()); + Log.trace(F("Assigning client cert %s" CR), generateHash(cnt_parameters_array[cnt_index].client_cert).c_str()); + cnt_parameters_array[cnt_index].validConnection = false; + } + if (SYSdata.containsKey("mqtt_client_key") && SYSdata["mqtt_client_key"].is()) { + cnt_parameters_array[cnt_index].client_key = TheengsUtils::processCert(SYSdata["mqtt_client_key"].as()); + Log.trace(F("Assigning client key %s" CR), generateHash(cnt_parameters_array[cnt_index].client_key).c_str()); + cnt_parameters_array[cnt_index].validConnection = false; + } + if (SYSdata.containsKey("ota_server_cert") && SYSdata["ota_server_cert"].is()) { + cnt_parameters_array[cnt_index].ota_server_cert = TheengsUtils::processCert(SYSdata["ota_server_cert"].as()); + Log.trace(F("Assigning OTA server cert %s" CR), generateHash(cnt_parameters_array[cnt_index].ota_server_cert).c_str()); + } + + // Read the memory certs hash to MQTT + if (read_cnt) + readCntParameters(cnt_index); + } + + // Change of mqtt secure connection or certs + void* prev_client = nullptr; + + if (save_cnt || test_cnt) { + // Stop the processing/disconnect +# ifdef ESP32 + ProcessLock = true; +# ifdef ZgatewayBT + stopProcessing(true); +# endif +# endif + + mqttSetupPending = true; + } else { + // no request to test or save the parameters, forget the old settings + cnt_parameters_backup.reset(); + } +# endif +#endif + + if (SYSdata.containsKey("mqtt") && SYSdata["mqtt"].is()) { + SYSConfig.mqtt = SYSdata["mqtt"]; + Log.notice(F("xtomqtt: %T" CR), SYSConfig.mqtt); + } + if (SYSdata.containsKey("serial") && SYSdata["serial"].is()) { + SYSConfig.serial = SYSdata["serial"]; + Log.notice(F("SERIAL: %T" CR), SYSConfig.serial); + } + if (SYSdata.containsKey("offline") && SYSdata["offline"].is()) { + SYSConfig.offline = SYSdata["offline"]; + Log.notice(F("offline: %T" CR), SYSConfig.offline); + if (SYSConfig.offline) { + gatewayState = GatewayState::OFFLINE; +// Disconnect MQTT +#if !MQTT_BROKER_MODE + mqtt->disconnect(); +#else + mqtt->stop(); +#endif + // Disconnect network + if (WiFi.status() == WL_CONNECTED) + WiFi.disconnect(true); + } + } + if (SYSdata.containsKey("powermode") && SYSdata["powermode"].is()) { + SYSConfig.powerMode = SYSdata["powermode"]; + Log.notice(F("Power mode: %d" CR), SYSConfig.powerMode); + } +#if USE_BLUFI + if (SYSdata.containsKey("blufi") && SYSdata["blufi"].is()) { + bool res = false; + if (SYSdata["blufi"] && !SYSConfig.blufi) { // Start Blufi + res = startBlufi(); + } else if (!SYSdata["blufi"] && SYSConfig.blufi) { // Stop Blufi + res = stopBlufi(); + } + if (res) + SYSConfig.blufi = SYSdata["blufi"]; + publishState = true; + Log.notice(F("Blufi: %T" CR), SYSConfig.blufi); + } +#endif + if (SYSdata.containsKey("disc")) { + if (SYSdata["disc"].is()) { + if (SYSdata["disc"] == true && SYSConfig.discovery == false) + lastDiscovery = millis(); + SYSConfig.discovery = SYSdata["disc"]; + publishState = true; + if (SYSConfig.discovery) + pubMqttDiscovery(); + } else { + Log.warning(F("Discovery command not a boolean" CR)); + } + Log.notice(F("Discovery state: %T" CR), SYSConfig.discovery); + } + if (SYSdata.containsKey("save") && SYSdata["save"].as()) { + SYSConfig_save(); + } + if (publishState) { + stateMeasures(); + } + if (restartESP) { + ESPRestart(7); + } + } +}