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);
+ }
+ }
+}