Files
OpenMQTT_Gateway_RHASK/main.ino
2025-02-10 22:13:31 +01:00

3619 lines
120 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#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 <queue>
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 <ArduinoJson.h>
#include <ArduinoLog.h>
#include <PicoMQTT.h>
#include <memory>
#include "LEDManager.h"
#include "TheengsUtils.h"
LEDManager ledManager;
struct JsonBundle {
StaticJsonDocument<JSON_MSG_BUFFER> doc;
};
std::queue<std::string> jsonQueue;
#ifdef ESP32
# include <driver/adc.h>
// Mutex to protect the queue
SemaphoreHandle_t xQueueMutex;
// Mutex to protect mqtt publish
SemaphoreHandle_t xMqttMutex;
#endif
StaticJsonDocument<JSON_MSG_BUFFER> modulesBuffer;
JsonArray modules = modulesBuffer.to<JsonArray>();
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 <ArduinoOTA.h>
# include <FS.h>
# include <SPIFFS.h>
# include <esp_task_wdt.h>
# include <nvs.h>
# include <nvs_flash.h>
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 <stdio.h>
# include "rom/ets_sys.h"
# include "soc/rtc_cntl_reg.h"
# include "soc/sens_reg.h"
# endif
# ifdef ESP32_ETHERNET
# include <ETH.h>
void WiFiEvent(WiFiEvent_t event);
# endif
# include <WiFiClientSecure.h>
# include <WiFiMulti.h>
WiFiMulti wifiMulti;
# include <WiFiManager.h>
# ifdef MDNS_SD
# include <ESPmDNS.h>
# endif
#elif defined(ESP8266)
# include <ArduinoOTA.h>
# include <DNSServer.h>
# include <ESP8266WebServer.h>
# include <ESP8266WiFi.h>
# include <ESP8266WiFiMulti.h>
# include <FS.h>
# include <WiFiManager.h>
X509List caCert;
# if MQTT_SECURE_SIGNED_CLIENT
X509List* pClCert = nullptr;
PrivateKey* pClKey = nullptr;
# endif
ESP8266WiFiMulti wifiMulti;
# ifdef MDNS_SD
# include <ESP8266mDNS.h>
# endif
#else
# include <Ethernet.h>
#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<MQTTServer> mqtt;
#else
std::unique_ptr< ::Client> eClient;
std::unique_ptr<PicoMQTT::Client> mqtt;
#endif
template <typename T> // 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 <typename T>
void Config_update(JsonObject& data, const char* key, T& var) {
if (data.containsKey(key)) {
if (var != data[key].as<T>()) {
var = data[key].as<T>();
Log.notice(F("Config %s changed to: %T" CR), key, data[key].as<T>());
} else {
Log.notice(F("Config %s unchanged, currently: %T" CR), key, data[key].as<T>());
}
}
}
/*
* 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<const char*>(), 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<JSON_MSG_BUFFER>& 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<JSON_MSG_BUFFER>& 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<std::string>();
// 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<std::string>() == "IBEACON") {
if (Jsondata.containsKey("uuid")) {
topic = Jsondata["uuid"].as<std::string>();
} 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<const char*>());
}
// 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<JsonObject>();
#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<bool>()) {
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<const char*>()) {
topic = String(mqtt_topic) + String(gateway_name) + String(data["origin"].as<const char*>());
data.remove("origin");
} else if (data.containsKey("topic") && data["topic"].is<const char*>()) {
topic = data["topic"].as<const char*>();
if (data.containsKey("info_topic") && data["info_topic"].is<const char*>()) {
// 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<uint64_t>() && 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<uint64_t>());
} 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<uint64_t>());
}
} else if (p.value().is<int>()) {
res = pubMQTT(topic + "/" + String(p.key().c_str()), p.value().as<int>());
} else if (p.value().is<float>()) {
res = pubMQTT(topic + "/" + String(p.key().c_str()), p.value().as<float>());
} else if (p.value().is<char*>()) {
res = pubMQTT(topic + "/" + String(p.key().c_str()), p.value().as<const char*>());
}
}
#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<JSON_MSG_BUFFER> jsonBuffer;
JsonObject SYSdata = jsonBuffer.to<JsonObject>();
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 "<mqtt_topic><gateway_name><toAdd>" equal to "<topicOri>"?
// 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<JSON_MSG_BUFFER> 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<JsonObject>();
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<String, uint16_t> 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<ss_cnt_parameters_backup> 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<WiFiClientSecure*>(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<WiFiClientSecure*>(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<gpio_num_t>(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<esp_sleep_ext1_wakeup_mode_t>(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<WiFiClientSecure*>(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<JsonObject>();
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<const char*>();
}
strcpy(key, "mqtt_client_cert");
strcat(key, index_suffix);
if (json.containsKey(key)) {
cnt_parameters_array[i].client_cert = json[key].as<const char*>();
}
strcpy(key, "mqtt_client_key");
strcat(key, index_suffix);
if (json.containsKey(key)) {
cnt_parameters_array[i].client_key = json[key].as<const char*>();
}
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<const char*>();
}
# else
cnt_parameters_array[i].ota_server_cert = json["ota_server_cert"].as<const char*>();
# endif
}
strcpy(key, "mqtt_server");
strcat(key, index_suffix);
if (json.containsKey(key)) {
strcpy(cnt_parameters_array[i].mqtt_server, json[key].as<const char*>());
}
strcpy(key, "mqtt_port");
strcat(key, index_suffix);
if (json.containsKey(key)) {
strcpy(cnt_parameters_array[i].mqtt_port, json[key].as<const char*>());
}
strcpy(key, "mqtt_user");
strcat(key, index_suffix);
if (json.containsKey(key)) {
strcpy(cnt_parameters_array[i].mqtt_user, json[key].as<const char*>());
}
strcpy(key, "mqtt_pass");
strcat(key, index_suffix);
if (json.containsKey(key)) {
strcpy(cnt_parameters_array[i].mqtt_pass, json[key].as<const char*>());
}
strcpy(key, "mqtt_broker_secure");
strcat(key, index_suffix);
if (json.containsKey(key)) {
cnt_parameters_array[i].isConnectionSecure = json[key].as<bool>();
}
strcpy(key, "mqtt_iscertvalid");
strcat(key, index_suffix);
if (json.containsKey(key)) {
cnt_parameters_array[i].isCertValidate = json[key].as<bool>();
}
strcpy(key, "valid_cnt");
strcat(key, index_suffix);
if (json.containsKey(key)) {
cnt_parameters_array[i].validConnection = json[key].as<bool>();
} 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<int>();
}
# 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", "<br/>mqtt secure", "1", 2, cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure ? "type=\"checkbox\" checked" : "type=\"checkbox\"");
WiFiManagerParameter custom_validate_cert("validate", "<br/>validate cert", "1", 2, cnt_parameters_array[CNT_DEFAULT_INDEX].isCertValidate ? "type=\"checkbox\" checked" : "type=\"checkbox\"");
WiFiManagerParameter custom_mqtt_cert("cert", "<br/>mqtt server cert", "", 4096);
WiFiManagerParameter custom_ota_server_cert("ota_cert", "<br/>ota server cert", "", 4096);
# if MQTT_SECURE_SIGNED_CLIENT
WiFiManagerParameter custom_client_cert("client_cert", "<br/>mqtt client cert", "", 4096);
WiFiManagerParameter custom_client_key("client_key", "<br/>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
RFtoX();
#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<JSON_MSG_BUFFER> 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<JSON_MSG_BUFFER_MAX> jsonBuffer;
JsonObject jsondata = jsonBuffer.to<JsonObject>();
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<const char*>()) {
strTopicOri = jsondata["target"].as<const char*>();
Log.trace(F("BUS Msg target: %s" CR), strTopicOri.c_str());
} else if (jsondata.containsKey("origin") && jsondata["origin"].is<const char*>()) {
strTopicOri = jsondata["origin"].as<const char*>();
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 <HTTPClient.h>
# 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<JSON_MSG_BUFFER> jsonBuffer;
JsonObject jsondata = jsonBuffer.to<JsonObject>();
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<String>();
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 <ESP8266httpUpdate.h>
# 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<JSON_MSG_BUFFER> 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<WiFiClientSecure*>(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<JSON_MSG_BUFFER> jsonBuffer;
JsonObject jsondata = jsonBuffer.to<JsonObject>();
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<float>()) {
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<bool>()) {
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<const char*>() && SYSdata.containsKey("wifi_pass") && SYSdata["wifi_pass"].is<const char*>()) {
#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<const char*>()) ||
#ifdef ZmqttDiscovery
(SYSdata.containsKey("discovery_prefix") && SYSdata["discovery_prefix"].is<const char*>()) ||
#endif
(SYSdata.containsKey("gateway_name") && SYSdata["gateway_name"].is<const char*>()) ||
(SYSdata.containsKey("gw_pass") && SYSdata["gw_pass"].is<const char*>())) {
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<bool>()) {
save_cnt = SYSdata["save_cnt"].as<bool>();
}
if (SYSdata.containsKey("read_cnt") && SYSdata["read_cnt"].is<bool>()) {
read_cnt = SYSdata["read_cnt"].as<bool>();
}
if (SYSdata.containsKey("test_cnt") && SYSdata["test_cnt"].is<bool>()) {
test_cnt = SYSdata["test_cnt"].as<bool>();
}
if (SYSdata.containsKey("cnt_index") && SYSdata["cnt_index"].is<int>()) {
if (SYSdata["cnt_index"].as<int>() < 0 || SYSdata["cnt_index"].as<int>() > 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<int>();
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<const char*>() && SYSdata.containsKey("mqtt_pass") && SYSdata["mqtt_pass"].is<const char*>()) {
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<const char*>()) {
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<const char*>()) {
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<bool>()) {
cnt_parameters_array[cnt_index].isConnectionSecure = SYSdata["mqtt_secure"].as<bool>();
cnt_parameters_array[cnt_index].validConnection = false;
}
if (SYSdata.containsKey("mqtt_validate") && SYSdata["mqtt_validate"].is<bool>()) {
cnt_parameters_array[cnt_index].isCertValidate = SYSdata["mqtt_validate"].as<bool>();
cnt_parameters_array[cnt_index].validConnection = false;
}
// Copy the certs to the memory
if (SYSdata.containsKey("mqtt_server_cert") && SYSdata["mqtt_server_cert"].is<const char*>()) {
cnt_parameters_array[cnt_index].server_cert = TheengsUtils::processCert(SYSdata["mqtt_server_cert"].as<const char*>());
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<const char*>()) {
cnt_parameters_array[cnt_index].client_cert = TheengsUtils::processCert(SYSdata["mqtt_client_cert"].as<const char*>());
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<const char*>()) {
cnt_parameters_array[cnt_index].client_key = TheengsUtils::processCert(SYSdata["mqtt_client_key"].as<const char*>());
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<const char*>()) {
cnt_parameters_array[cnt_index].ota_server_cert = TheengsUtils::processCert(SYSdata["ota_server_cert"].as<const char*>());
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<bool>()) {
SYSConfig.mqtt = SYSdata["mqtt"];
Log.notice(F("xtomqtt: %T" CR), SYSConfig.mqtt);
}
if (SYSdata.containsKey("serial") && SYSdata["serial"].is<bool>()) {
SYSConfig.serial = SYSdata["serial"];
Log.notice(F("SERIAL: %T" CR), SYSConfig.serial);
}
if (SYSdata.containsKey("offline") && SYSdata["offline"].is<bool>()) {
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<int>()) {
SYSConfig.powerMode = SYSdata["powermode"];
Log.notice(F("Power mode: %d" CR), SYSConfig.powerMode);
}
#if USE_BLUFI
if (SYSdata.containsKey("blufi") && SYSdata["blufi"].is<bool>()) {
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<bool>()) {
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<bool>()) {
SYSConfig_save();
}
if (publishState) {
stateMeasures();
}
if (restartESP) {
ESPRestart(7);
}
}
}