eraseConfig() passed SPIFFS.format() as an argument to THEENGS_LOG_TRACE,
which expands to ((void)0) below LOG_LEVEL_TRACE and discards its arguments
along with the call. On every build using the default LOG_LEVEL_NOTICE the
format therefore never ran, so {"cmd":"erase"} only performed
nvs_flash_erase(): the WiFi credentials were cleared but /config.json
survived and restored the MQTT server, port, user, password, broker and
client certificates, connection index, topic, gateway name, OTA password
and BLE AES keys on the next boot.
Call SPIFFS.format() on its own statement and report a failed format at
ERROR level, so the failure is visible at the default log level.
This was introduced in #2232, which converted the original runtime-checked
Log.trace() call to the compile-time macro. It affects the SYS erase
command, the WebUI erase button and the GPIO factory-reset hold alike,
since all three go through eraseConfig().
Reproduced on a Theengs Plug built at the default log level: after
{"cmd":"erase"} the NVS namespaces are gone ("SYS config not found",
"ONOFF config not found") and the saved APs are empty, yet the same boot
still logs "Config loaded from flash" and restores the OTA server cert.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
3832 lines
129 KiB
C++
3832 lines
129 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 <PicoMQTT.h>
|
|
|
|
#include <memory>
|
|
#include <queue>
|
|
|
|
#include "LEDManager.h"
|
|
#include "TheengsCommon.h"
|
|
#include "TheengsUtils.h"
|
|
|
|
#if defined(CONFIG_IDF_TARGET_ESP32) && !defined(ZgatewayBT) && !defined(USE_BLUFI)
|
|
# include "esp_bt.h" // esp_bt_mem_release() — BT DRAM reclaim in setup()
|
|
#endif
|
|
|
|
GatewayState gatewayState = GatewayState::WAITING_ONBOARDING;
|
|
static GatewayState previousGatewayState = gatewayState;
|
|
|
|
// Macros and structure to enable the duplicates removing on the following gateways
|
|
#if defined(ZgatewayRF) || 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
|
|
|
|
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;
|
|
|
|
//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;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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;
|
|
char mqtt_topic[parameters_size + 1] = Base_Topic;
|
|
char gateway_name[parameters_size + 1] = Gateway_Name;
|
|
unsigned long lastDiscovery = 0;
|
|
|
|
#if BLEDecryptor
|
|
// BLE_AES defaults to "" (see User_config.h) so encrypted PVVX/BTHome/Victron
|
|
// frames are only decrypted when a per-MAC key is configured (ble_aes_keys) or
|
|
// the user enters a default. Sites shipping a stock default key can still set
|
|
// it at build time via -DBLE_AES='"<32-hex-key>"'.
|
|
char ble_aes[parameters_size] = BLE_AES;
|
|
StaticJsonDocument<JSON_BLE_AES_CUSTOM_KEYS> ble_aes_keys;
|
|
#endif
|
|
|
|
#if !MQTT_BROKER_MODE
|
|
ss_cnt_parameters cnt_parameters_array[cnt_parameters_array_size] = CNT_PARAMS_ARR;
|
|
#endif
|
|
|
|
#if defined(ESP32)
|
|
# include <Preferences.h>
|
|
Preferences preferences;
|
|
#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 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
|
|
#if defined(USE_BLUFI) && defined(ESP32)
|
|
extern bool isBlufiConnected();
|
|
extern bool isStaConnecting();
|
|
extern bool startBlufi();
|
|
extern bool stopBlufi();
|
|
extern void set_blufi_mfg_data();
|
|
#endif
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
bool pub(JsonObject& data);
|
|
bool pubMQTT(const char* topic, const char* payload);
|
|
bool pubMQTT(const char* topic, const char* payload, bool retainFlag);
|
|
bool pubMQTT(String topic, const char* payload);
|
|
bool pubMQTT(const char* topic, unsigned long payload);
|
|
bool pubMQTT(const char* topic, unsigned long long payload);
|
|
bool pubMQTT(const char* topic, String payload);
|
|
bool pubMQTT(String topic, String payload);
|
|
bool pubMQTT(String topic, int payload);
|
|
bool pubMQTT(String topic, unsigned long long payload);
|
|
bool pubMQTT(String topic, float payload);
|
|
bool pubMQTT(const char* topic, float payload);
|
|
bool pubMQTT(const char* topic, int payload);
|
|
bool pubMQTT(const char* topic, unsigned int payload);
|
|
bool pubMQTT(const char* topic, long payload);
|
|
bool pubMQTT(const char* topic, double payload);
|
|
bool pubMQTT(String topic, unsigned long payload);
|
|
void checkButton();
|
|
void ESPRestart(byte reason);
|
|
void eraseConfig();
|
|
void readCntParameters(int index);
|
|
void saveConfig();
|
|
void receivingDATA(const char* topicOri, const char* datacallback);
|
|
void updateAndHandleLEDsTask(void* pvParameters);
|
|
void updateAndHandleLEDsTask();
|
|
bool loadConfigFromFlash();
|
|
void setupWiFiManager();
|
|
void setOTA();
|
|
void setupCommonRF();
|
|
void sleep();
|
|
bool checkForUpdates();
|
|
String stateMeasures();
|
|
String stateRFMeasures();
|
|
int getMin();
|
|
void XtoRFset(const char* topicOri, JsonObject& RFdata);
|
|
void MQTTHttpsFWUpdate(const char* topicOri, JsonObject& HttpsFwUpdateData);
|
|
void setup_ethernet_esp32();
|
|
void setupTLS(int index = CNT_DEFAULT_INDEX);
|
|
void XtoSYS(const char* topicOri, JsonObject& SYSdata);
|
|
|
|
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
|
|
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
|
|
|
|
/*
|
|
* 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 {
|
|
THEENGS_LOG_ERROR(F("No origin or topic in JSON filtered" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// Add a document to the queue
|
|
bool enqueueJsonObject(const StaticJsonDocument<JSON_MSG_BUFFER>& jsonDoc, int timeout) {
|
|
receivedMessages++;
|
|
if (jsonDoc.size() == 0) {
|
|
THEENGS_LOG_ERROR(F("Empty JSON, skipping" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
return true;
|
|
}
|
|
if (queueLength >= QueueSize) {
|
|
THEENGS_LOG_WARNING(F("%d Doc(s) in queue, doc blocked" CR), queueLength);
|
|
blockedMessages++;
|
|
return false;
|
|
}
|
|
THEENGS_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) {
|
|
THEENGS_LOG_ERROR(F("xQueueMutex not taken" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
blockedMessages++;
|
|
return false;
|
|
}
|
|
#endif
|
|
jsonQueue.push(jsonString);
|
|
#ifdef ESP32
|
|
xSemaphoreGive(xQueueMutex);
|
|
#endif
|
|
THEENGS_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")) {
|
|
THEENGS_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 {
|
|
THEENGS_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;
|
|
|
|
THEENGS_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;
|
|
}
|
|
THEENGS_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) {
|
|
THEENGS_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) {
|
|
THEENGS_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) {
|
|
THEENGS_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 {
|
|
THEENGS_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
|
|
THEENGS_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) {
|
|
THEENGS_LOG_ERROR(F("xMqttMutex not taken" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
return res;
|
|
}
|
|
#endif
|
|
if (mqtt && mqtt->connected()) {
|
|
THEENGS_LOG_NOTICE(F("[ OMG->MQTT ] topic: %s msg: %s " CR), topic, payload);
|
|
res = mqtt->publish(topic, payload, 0, retainFlag);
|
|
} else {
|
|
THEENGS_LOG_WARNING(F("MQTT not connected, aborting the publication" CR));
|
|
}
|
|
#ifdef ESP32
|
|
xSemaphoreGive(xMqttMutex);
|
|
#endif
|
|
} else {
|
|
THEENGS_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;
|
|
#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);
|
|
#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;
|
|
# 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();
|
|
THEENGS_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) {
|
|
THEENGS_LOG_ERROR(F("SYS config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity());
|
|
gatewayState = GatewayState::ERROR;
|
|
return;
|
|
}
|
|
if (jsonBuffer.isNull()) {
|
|
THEENGS_LOG_WARNING(F("SYS config is null" CR));
|
|
return;
|
|
}
|
|
JsonObject jo = jsonBuffer.as<JsonObject>();
|
|
SYSConfig_fromJson(jo);
|
|
THEENGS_LOG_NOTICE(F("SYS config loaded" CR));
|
|
} else {
|
|
preferences.end();
|
|
THEENGS_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() {
|
|
THEENGS_LOG_TRACE(F("Browsing for MQTT service" CR));
|
|
int n = MDNS.queryService("mqtt", "tcp");
|
|
if (n == 0) {
|
|
THEENGS_LOG_WARNING(F("no services found" CR));
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("%d service(s) found" CR), n);
|
|
for (int i = 0; i < n; ++i) {
|
|
THEENGS_LOG_TRACE(F("Service %d %s found" CR), i, MDNS.hostname(i).c_str());
|
|
# if defined(ESP32)
|
|
THEENGS_LOG_TRACE(F("IP %s Port %d" CR), MDNS.address(i).toString().c_str(), MDNS.port(i));
|
|
# else
|
|
THEENGS_LOG_TRACE(F("IP %s Port %d" CR), MDNS.IP(i).toString().c_str(), MDNS.port(i));
|
|
# endif
|
|
}
|
|
if (n == 1) {
|
|
THEENGS_LOG_TRACE(F("One MQTT server found setting parameters" CR));
|
|
return {MDNS.hostname(0).c_str(), uint16_t(MDNS.port(0))};
|
|
} else {
|
|
THEENGS_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() {
|
|
THEENGS_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() {
|
|
THEENGS_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)
|
|
THEENGS_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
|
|
|
|
THEENGS_LOG_TRACE(F("Mqtt server: %s" CR), broker_host);
|
|
THEENGS_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
|
|
THEENGS_LOG_TRACE(F("MQTT connection count: %d" CR), reconnection_count);
|
|
if (reconnection_count > reconnection_threshold && SYSConfig.mqtt) {
|
|
// Detected instability in MQTT connection
|
|
THEENGS_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");
|
|
THEENGS_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
|
|
THEENGS_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;
|
|
THEENGS_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)
|
|
THEENGS_LOG_WARNING(F("failed, ssl error code=%d" CR), client->lastError(nullptr, 0));
|
|
# elif defined(ESP8266)
|
|
THEENGS_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
|
|
THEENGS_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) {
|
|
THEENGS_LOG_NOTICE(F("Connection %d valid, switching" CR), cnt_index);
|
|
saveConfig();
|
|
break;
|
|
} else {
|
|
THEENGS_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())
|
|
THEENGS_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) {
|
|
THEENGS_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) {
|
|
THEENGS_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) {
|
|
THEENGS_LOG_NOTICE(F("Could not get protocol!" CR));
|
|
}
|
|
if (getprotocol & WIFI_PROTOCOL_11N) {
|
|
THEENGS_LOG_NOTICE(F("WiFi_Protocol_11n" CR));
|
|
}
|
|
if (getprotocol & WIFI_PROTOCOL_11G) {
|
|
THEENGS_LOG_NOTICE(F("WiFi_Protocol_11g" CR));
|
|
}
|
|
if (getprotocol & WIFI_PROTOCOL_11B) {
|
|
THEENGS_LOG_NOTICE(F("WiFi_Protocol_11b" CR));
|
|
}
|
|
|
|
# ifdef WifiPower
|
|
THEENGS_LOG_NOTICE(F("Requested WiFi power level: %i" CR), WifiPower);
|
|
WiFi.setTxPower(WifiPower);
|
|
# endif
|
|
THEENGS_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)) {
|
|
THEENGS_LOG_ERROR(F("Failed to change WifiMode." CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
# endif
|
|
|
|
# if WifiGMode == false
|
|
if (!wifi_set_phy_mode(PHY_MODE_11N)) {
|
|
THEENGS_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) {
|
|
THEENGS_LOG_NOTICE(F("WiFi_Protocol_11n" CR));
|
|
}
|
|
if (getprotocol == PHY_MODE_11G) {
|
|
THEENGS_LOG_NOTICE(F("WiFi_Protocol_11g" CR));
|
|
}
|
|
if (getprotocol == PHY_MODE_11B) {
|
|
THEENGS_LOG_NOTICE(F("WiFi_Protocol_11b" CR));
|
|
}
|
|
|
|
# ifdef WifiPower
|
|
THEENGS_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() {
|
|
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);
|
|
THEENGS_LOG_NOTICE(F(CR "************* WELCOME TO OpenMQTTGateway **************" CR));
|
|
#if defined(CONFIG_IDF_TARGET_ESP32) && !defined(ZgatewayBT) && !defined(USE_BLUFI)
|
|
// Reclaim the Bluetooth controller's reserved DRAM on BT-less ESP32 builds.
|
|
// arduino-esp32 3.x releases only the BLE slice: esp32-hal-bt.c initialises
|
|
// _classicMemReleased = true whenever CONFIG_BT_CLASSIC_ENABLED is unset,
|
|
// which is the case for a BLE-only controller build — so its
|
|
// btMemRelease(BT_MODE_CLASSIC_BT) call short-circuits and ~33 KB of the
|
|
// 0xdb5c reservation is never returned to the heap. ESP32 has Classic BT
|
|
// hardware (CONFIG_SOC_BT_CLASSIC_SUPPORTED) and the DRAM is reserved
|
|
// regardless of the configured controller mode. arduino-esp32 2.x released
|
|
// the whole BTDM region unconditionally here.
|
|
// Safe no-op if the core ever releases it correctly, or if BT is later started.
|
|
{
|
|
uint32_t heapBeforeBtRelease = ESP.getFreeHeap();
|
|
esp_bt_mem_release(ESP_BT_MODE_BTDM);
|
|
THEENGS_LOG_NOTICE(F("BT DRAM reclaimed: %d -> %d" CR), heapBeforeBtRelease, ESP.getFreeHeap());
|
|
}
|
|
#endif
|
|
#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 defined(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
|
|
|
|
THEENGS_LOG_NOTICE(F("OpenMQTTGateway Version: " OMG_VERSION CR));
|
|
|
|
#ifdef ESP32_EXT0_WAKE_PIN
|
|
THEENGS_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) {
|
|
THEENGS_LOG_ERROR(F("Failed to set deep sleep EXT0 Wakeup." CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
#endif
|
|
#ifdef ESP32_EXT1_WAKE_PIN
|
|
THEENGS_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) {
|
|
THEENGS_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)
|
|
extern void setupWiFiFromBuild();
|
|
setupWiFiFromBuild();
|
|
#else
|
|
WiFi.setHostname(gateway_name);
|
|
WiFi.mode(WIFI_STA);
|
|
if (loadConfigFromFlash()) { // Config present
|
|
THEENGS_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
|
|
extern void checkSerial();
|
|
checkSerial();
|
|
# endif
|
|
|
|
// In failSafeMode we don't want to setup wifi manager as it has already been done before,
|
|
// and with Ethernet already connected we don't need it at all: bringing up the WiFi radio
|
|
// would only add 2.4GHz contention with BLE.
|
|
if (!failSafeMode && !ethConnected) {
|
|
THEENGS_LOG_NOTICE(F("No config in flash, launching wifi manager" CR));
|
|
setupWiFiManager();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway mac: %s" CR), WiFi.macAddress().c_str());
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway ip: %s" CR), WiFi.localIP().toString().c_str());
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway index %d" CR), cnt_index);
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway mqtt topic: %s" CR), mqtt_topic);
|
|
#ifdef ZmqttDiscovery
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway mqtt discovery prefix: %s" CR), discovery_prefix);
|
|
#endif
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway gateway name: %s" CR), gateway_name);
|
|
#if !MQTT_BROKER_MODE
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway mqtt server: %s" CR), cnt_parameters_array[cnt_index].mqtt_server);
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway mqtt port: %s" CR), cnt_parameters_array[cnt_index].mqtt_port);
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway mqtt user: %s" CR), cnt_parameters_array[cnt_index].mqtt_user);
|
|
THEENGS_LOG_TRACE(F("OpenMQTTGateway secure connection: %s" CR), cnt_parameters_array[cnt_index].isConnectionSecure ? "true" : "false");
|
|
THEENGS_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);
|
|
#endif
|
|
#ifdef ZgatewayRF2
|
|
modules.add(ZgatewayRF2);
|
|
#endif
|
|
#ifdef ZgatewayPilight
|
|
modules.add(ZgatewayPilight);
|
|
#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
|
|
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
|
|
setupRTL_433();
|
|
modules.add(ZgatewayRTL_433);
|
|
#endif
|
|
THEENGS_LOG_TRACE(F("mqtt_max_payload_size: %d" CR), mqtt_max_payload_size);
|
|
SYSConfig.offline ? THEENGS_LOG_NOTICE(F("Offline enabled" CR)) : THEENGS_LOG_NOTICE(F("Offline disabled" CR));
|
|
char jsonChar[100];
|
|
serializeJson(modules, jsonChar, measureJson(modules) + 1);
|
|
THEENGS_LOG_NOTICE(F("OpenMQTTGateway modules: %s" CR), jsonChar);
|
|
THEENGS_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) {
|
|
THEENGS_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
|
|
THEENGS_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([]() {
|
|
THEENGS_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([]() {
|
|
THEENGS_LOG_TRACE(F("\nOTA done" CR));
|
|
last_ota_activity_millis = 0;
|
|
lpDisplayPrint("OTA done");
|
|
ESPRestart(6);
|
|
});
|
|
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
|
|
THEENGS_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)
|
|
THEENGS_LOG_ERROR(F("Auth Failed" CR));
|
|
else if (error == OTA_BEGIN_ERROR)
|
|
THEENGS_LOG_ERROR(F("Begin Failed" CR));
|
|
else if (error == OTA_CONNECT_ERROR)
|
|
THEENGS_LOG_ERROR(F("Connect Failed" CR));
|
|
else if (error == OTA_RECEIVE_ERROR)
|
|
THEENGS_LOG_ERROR(F("Receive Failed" CR));
|
|
else if (error == OTA_END_ERROR)
|
|
THEENGS_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());
|
|
THEENGS_LOG_NOTICE(F("cnt index used: %d" CR), index);
|
|
if (!cnt_parameters_array[index].isCertValidate) {
|
|
THEENGS_LOG_NOTICE(F("Disabling cert validation" CR));
|
|
sClient->setInsecure();
|
|
} else {
|
|
THEENGS_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());
|
|
THEENGS_LOG_NOTICE(F("Server cert found from cert array" CR));
|
|
} else if (strlen(ss_server_cert) > MIN_CERT_LENGTH) {
|
|
sClient->setCACert(ss_server_cert);
|
|
THEENGS_LOG_NOTICE(F("Server cert found from ss_server_cert" CR));
|
|
} else {
|
|
THEENGS_LOG_ERROR(F("No server cert found" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
|
|
# if AWS_IOT
|
|
if (strcmp(cnt_parameters_array[index].mqtt_port, "443") == 0) {
|
|
THEENGS_LOG_NOTICE(F("Using ALPN" CR));
|
|
static const char* alpnProtocols[] = ALPN_PROTOCOLS;
|
|
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());
|
|
THEENGS_LOG_NOTICE(F("Client cert found from cert array" CR));
|
|
} else if (strlen(ss_client_cert) > MIN_CERT_LENGTH) {
|
|
sClient->setCertificate(ss_client_cert);
|
|
THEENGS_LOG_NOTICE(F("Client cert found from ss_client_cert" CR));
|
|
} else {
|
|
THEENGS_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());
|
|
THEENGS_LOG_NOTICE(F("Client key found from cert array" CR));
|
|
} else if (strlen(ss_client_key) > MIN_CERT_LENGTH) {
|
|
sClient->setPrivateKey(ss_client_key);
|
|
THEENGS_LOG_NOTICE(F("Client key found from ss_client_key" CR));
|
|
} else {
|
|
THEENGS_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) + "\"}";
|
|
THEENGS_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();
|
|
}
|
|
THEENGS_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.setHostname(gateway_name);
|
|
WiFi.mode(WIFI_STA);
|
|
wifiMulti.addAP(wifi_ssid, wifi_password);
|
|
THEENGS_LOG_TRACE(F("Connecting to %s" CR), wifi_ssid);
|
|
# ifdef wifi_ssid1
|
|
wifiMulti.addAP(wifi_ssid1, wifi_password1);
|
|
THEENGS_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)) {
|
|
THEENGS_LOG_ERROR(F("Wifi STA Failed to configure" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
|
|
# endif
|
|
|
|
while (wifiMulti.run() != WL_CONNECTED) {
|
|
delay(500);
|
|
THEENGS_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
|
|
}
|
|
THEENGS_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() {
|
|
THEENGS_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) {
|
|
THEENGS_LOG_TRACE(F("Trigger button Pressed" CR));
|
|
delay(3000); // reset delay hold
|
|
if (digitalRead(TRIGGER_GPIO) == LOW) {
|
|
THEENGS_LOG_NOTICE(F("Button Held" CR));
|
|
// Switching off the relay during reset or failsafe operations
|
|
# ifdef ZactuatorONOFF
|
|
extern void ActuatorTrigger();
|
|
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()) {
|
|
THEENGS_LOG_TRACE(F("mounted file system" CR));
|
|
if (SPIFFS.exists("/config.json")) {
|
|
THEENGS_LOG_NOTICE(F("Erasing ESP Config, restarting" CR));
|
|
eraseConfig();
|
|
}
|
|
}
|
|
delay(30000);
|
|
if (digitalRead(TRIGGER_GPIO) == LOW) {
|
|
THEENGS_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() {
|
|
THEENGS_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
|
|
|
|
THEENGS_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;
|
|
# if BLEDecryptor
|
|
json["ble_aes"] = ble_aes;
|
|
json["ble_aes_keys"] = ble_aes_keys;
|
|
# endif
|
|
|
|
File configFile = SPIFFS.open("/config.json", "w");
|
|
if (!configFile) {
|
|
THEENGS_LOG_ERROR(F("failed to open config file for writing" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
|
|
serializeJson(json, configFile);
|
|
configFile.close();
|
|
}
|
|
|
|
// Length-checked copy used when loading config.json values into fixed-size
|
|
// buffers. The SPIFFS file is implicitly trusted once written, but historical
|
|
// MQTT/HTTP paths could land over-length values there before we hardened them;
|
|
// this prevents one stale config from overflowing globals on every boot.
|
|
static void loadStrField(const char* name, char* dst, size_t dstSize, const char* src) {
|
|
if (!src) return;
|
|
if (strlen(src) >= dstSize) {
|
|
THEENGS_LOG_WARNING(F("Config field %s too long (%u), skipping" CR), name, (unsigned)strlen(src));
|
|
return;
|
|
}
|
|
strcpy(dst, src);
|
|
}
|
|
|
|
bool loadConfigFromFlash() {
|
|
THEENGS_LOG_TRACE(F("mounting FS..." CR));
|
|
bool result = false;
|
|
|
|
if (SPIFFS.begin()) {
|
|
THEENGS_LOG_TRACE(F("mounted file system" CR));
|
|
} else {
|
|
THEENGS_LOG_WARNING(F("failed to mount FS -> formating" CR));
|
|
SPIFFS.format();
|
|
if (SPIFFS.begin())
|
|
THEENGS_LOG_TRACE(F("mounted file system after formating" CR));
|
|
}
|
|
if (SPIFFS.exists("/config.json")) {
|
|
//file exists, reading and loading
|
|
THEENGS_LOG_TRACE(F("reading config file" CR));
|
|
File configFile = SPIFFS.open("/config.json", "r");
|
|
if (configFile) {
|
|
THEENGS_LOG_TRACE(F("opened config file" CR));
|
|
DynamicJsonDocument json(configFile.size() * 2);
|
|
auto error = deserializeJson(json, configFile);
|
|
if (error) {
|
|
THEENGS_LOG_ERROR(F("deserialize config failed: %s, buffer capacity: %u" CR), error.c_str(), json.capacity());
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
if (!json.isNull()) {
|
|
THEENGS_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
|
|
THEENGS_LOG_WARNING(F("Old Github OTA server detected, skipping" CR));
|
|
} else {
|
|
THEENGS_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)) {
|
|
loadStrField(key, cnt_parameters_array[i].mqtt_server, sizeof(cnt_parameters_array[i].mqtt_server), json[key].as<const char*>());
|
|
}
|
|
strcpy(key, "mqtt_port");
|
|
strcat(key, index_suffix);
|
|
if (json.containsKey(key)) {
|
|
loadStrField(key, cnt_parameters_array[i].mqtt_port, sizeof(cnt_parameters_array[i].mqtt_port), json[key].as<const char*>());
|
|
}
|
|
strcpy(key, "mqtt_user");
|
|
strcat(key, index_suffix);
|
|
if (json.containsKey(key)) {
|
|
loadStrField(key, cnt_parameters_array[i].mqtt_user, sizeof(cnt_parameters_array[i].mqtt_user), json[key].as<const char*>());
|
|
}
|
|
strcpy(key, "mqtt_pass");
|
|
strcat(key, index_suffix);
|
|
if (json.containsKey(key)) {
|
|
loadStrField(key, cnt_parameters_array[i].mqtt_pass, sizeof(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
|
|
THEENGS_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"))
|
|
loadStrField("mqtt_topic", mqtt_topic, sizeof(mqtt_topic), json["mqtt_topic"]);
|
|
# ifdef ZmqttDiscovery
|
|
if (json.containsKey("discovery_prefix"))
|
|
// discovery_prefix is extern char[] in this TU; size is parameters_size + 1.
|
|
loadStrField("discovery_prefix", discovery_prefix, parameters_size + 1, json["discovery_prefix"]);
|
|
# endif
|
|
if (json.containsKey("gateway_name"))
|
|
loadStrField("gateway_name", gateway_name, sizeof(gateway_name), json["gateway_name"]);
|
|
if (json.containsKey("ota_pass")) {
|
|
loadStrField("ota_pass", ota_pass, sizeof(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
|
|
}
|
|
# if BLEDecryptor
|
|
if (json.containsKey("ble_aes")) {
|
|
loadStrField("ble_aes", ble_aes, sizeof(ble_aes), json["ble_aes"]);
|
|
THEENGS_LOG_TRACE(F("loaded default BLE AES key %s" CR), ble_aes);
|
|
}
|
|
if (json.containsKey("ble_aes_keys")) {
|
|
ble_aes_keys = json["ble_aes_keys"];
|
|
THEENGS_LOG_TRACE(F("loaded %d custom BLE AES keys" CR), ble_aes_keys.size());
|
|
}
|
|
# endif
|
|
result = true;
|
|
} else {
|
|
THEENGS_LOG_WARNING(F("failed to load json config" CR));
|
|
}
|
|
configFile.close();
|
|
}
|
|
} else {
|
|
THEENGS_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);
|
|
|
|
# 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);
|
|
THEENGS_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
|
|
THEENGS_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()) {
|
|
THEENGS_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
|
|
// Only suppress the portal when we still have saved WiFi credentials. If the
|
|
// credentials were lost (e.g. NVS erased) while config.json remains, keeping
|
|
// the portal disabled makes the gateway impossible to re-onboard and it
|
|
// reboot-loops (autoConnect fails immediately -> ESPRestart, forever).
|
|
if (SPIFFS.exists("/config.json") && WiFi.SSID().length() > 0) {
|
|
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
|
|
{
|
|
THEENGS_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)) {
|
|
THEENGS_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
|
|
// Only persist non-empty values so a partial form submit (for example a
|
|
// programmatic POST of only s/p) cannot wipe compile-time defaults.
|
|
// mqtt_pass keeps its existing sentinel-based guard below.
|
|
if (strlen(custom_mqtt_server.getValue()) > 0) {
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_server, custom_mqtt_server.getValue());
|
|
}
|
|
if (strlen(custom_mqtt_port.getValue()) > 0) {
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_port, custom_mqtt_port.getValue());
|
|
}
|
|
if (strlen(custom_mqtt_user.getValue()) > 0) {
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_user, custom_mqtt_user.getValue());
|
|
}
|
|
// Persist the MQTT password only when the form supplied a non-empty value
|
|
// that also differs from the compile-time MQTT_PASS sentinel. An empty
|
|
// value (partial / programmatic POST) must not wipe the stored password.
|
|
if (strlen(custom_mqtt_pass.getValue()) > 0 &&
|
|
strcmp(custom_mqtt_pass.getValue(), MQTT_PASS) != 0) {
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_pass, custom_mqtt_pass.getValue());
|
|
}
|
|
if (strlen(custom_mqtt_topic.getValue()) > 0) {
|
|
// The WiFiManagerParameter buffer is mqtt_topic_max_size (150) but the
|
|
// destination is sizeof(mqtt_topic). HTML maxlength is client-side only;
|
|
// a raw POST can deliver any length, so length-check before copying.
|
|
const char* src = custom_mqtt_topic.getValue();
|
|
if (strlen(src) >= sizeof(mqtt_topic)) {
|
|
THEENGS_LOG_WARNING(F("mqtt_topic from portal too long, ignoring" CR));
|
|
} else {
|
|
strcpy(mqtt_topic, src);
|
|
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
|
|
if (strlen(custom_gateway_name.getValue()) > 0) {
|
|
strcpy(gateway_name, custom_gateway_name.getValue());
|
|
}
|
|
if (strlen(custom_ota_pass.getValue()) > 0) {
|
|
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);
|
|
# if defined(ETH_PHY_COLD_CYCLE) && defined(ETH_PHY_POWER) && (ETH_PHY_POWER >= 0)
|
|
// Give the PHY a true cold start before begin(): a warm PHY left in a stale
|
|
// state (seen after an NVS erase with PSRAM enabled on Theengs Bridge v1.1)
|
|
// can flap the link until a manual power cycle, and the in-driver reset
|
|
// begin() performs (short assert, short delay) does not clear it. Power the
|
|
// PHY down, then back up, and let its oscillator settle before begin()
|
|
// starts talking to it.
|
|
pinMode(ETH_PHY_POWER, OUTPUT);
|
|
digitalWrite(ETH_PHY_POWER, LOW);
|
|
delay(200); // hold the PHY powered down
|
|
digitalWrite(ETH_PHY_POWER, HIGH);
|
|
delay(200); // let the PHY oscillator settle
|
|
# endif
|
|
# ifdef NetworkAdvancedSetup
|
|
THEENGS_LOG_TRACE(F("Adv eth cfg" CR));
|
|
// ETH.config() has to be called after ETH.begin(): the underlying netif is
|
|
// created by begin(), before that the call returns false and is a no-op.
|
|
ethBeginSuccess = ETH.begin();
|
|
if (ethBeginSuccess) {
|
|
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 (!ETH.config(ip_adress, gateway_adress, subnet_adress, dns_adress)) {
|
|
THEENGS_LOG_ERROR(F("Adv eth cfg failed" CR));
|
|
}
|
|
}
|
|
# else
|
|
THEENGS_LOG_NOTICE(F("Spl eth cfg" CR));
|
|
ethBeginSuccess = ETH.begin();
|
|
# endif
|
|
if (ethBeginSuccess) {
|
|
THEENGS_LOG_NOTICE(F("Ethernet started" CR));
|
|
while (!ethConnected && failure_number_ntwk <= maxConnectionRetryNetwork) {
|
|
delay(500);
|
|
THEENGS_LOG_NOTICE(F("." CR));
|
|
failure_number_ntwk++;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_ERROR(F("Ethernet not started" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
}
|
|
|
|
void WiFiEvent(WiFiEvent_t event) {
|
|
switch (event) {
|
|
case ARDUINO_EVENT_ETH_START:
|
|
THEENGS_LOG_TRACE(F("Ethernet Started" CR));
|
|
ETH.setHostname(gateway_name);
|
|
break;
|
|
case ARDUINO_EVENT_ETH_CONNECTED:
|
|
THEENGS_LOG_NOTICE(F("Ethernet Connected" CR));
|
|
break;
|
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
|
THEENGS_LOG_NOTICE(F("OpenMQTTGateway Ethernet MAC: %s" CR), ETH.macAddress().c_str());
|
|
THEENGS_LOG_NOTICE(F("OpenMQTTGateway Ethernet IP: %s" CR), ETH.localIP().toString().c_str());
|
|
THEENGS_LOG_NOTICE(F("OpenMQTTGateway Ethernet link speed: %d Mbps" CR), ETH.linkSpeed());
|
|
gatewayState = GatewayState::NTWK_CONNECTED;
|
|
ethConnected = true;
|
|
break;
|
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
|
THEENGS_LOG_WARNING(F("Ethernet Disconnected" CR));
|
|
ethConnected = false;
|
|
break;
|
|
case ARDUINO_EVENT_ETH_STOP:
|
|
THEENGS_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;
|
|
THEENGS_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
|
|
THEENGS_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
|
|
THEENGS_LOG_NOTICE(F("Entering deep sleep, EXT0 Wakeup by pin : %l." CR), ESP32_EXT0_WAKE_PIN);
|
|
# endif
|
|
# ifdef ESP32_EXT1_WAKE_PIN
|
|
THEENGS_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) {
|
|
THEENGS_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
|
|
extern bool isSerialReady();
|
|
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
|
|
THEENGS_LOG_WARNING(F("Network disconnected" CR));
|
|
gatewayState = GatewayState::NTWK_DISCONNECTED;
|
|
if (!wifi_reconnect_bypass()) {
|
|
failure_number_ntwk++;
|
|
THEENGS_LOG_WARNING(F("failure_number_ntwk: %d" CR), failure_number_ntwk);
|
|
// Self-heal from a wedged WiFi stack: if reconnection keeps failing, restart the ESP.
|
|
// Disabled on environments where an unexpected reboot is undesirable (e.g. a smart plug).
|
|
if (WifiReconnectWatchDog && failure_number_ntwk > maxRetryWatchDog) {
|
|
ESPRestart(2);
|
|
}
|
|
sleep();
|
|
} else {
|
|
gatewayState = GatewayState::NTWK_CONNECTED;
|
|
}
|
|
}
|
|
|
|
#if defined(ESP32) && defined(USE_BLUFI)
|
|
if (SYSConfig.blufi) {
|
|
if (gatewayState != previousGatewayState) {
|
|
THEENGS_LOG_NOTICE(F("Gateway state changed to: %d" CR), gatewayState);
|
|
previousGatewayState = gatewayState;
|
|
set_blufi_mfg_data();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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
|
|
extern String stateLORAMeasures();
|
|
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
|
|
extern void MeasureGasMQ2();
|
|
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 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())
|
|
THEENGS_LOG_TRACE(F("2GtoMQTT OK" CR));
|
|
#endif
|
|
#ifdef ZgatewayRFM69
|
|
if (RFM69toX())
|
|
THEENGS_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();
|
|
}
|
|
delay(1);
|
|
}
|
|
|
|
/**
|
|
* 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) + "\"}";
|
|
THEENGS_LOG_NOTICE(F("Erasing secondary module config: %s" CR), eraseCmdStr.c_str());
|
|
receivingDATA(subjectMQTTtoSYSsetSecondaryModule, eraseCmdStr.c_str());
|
|
delay(2000);
|
|
#endif
|
|
// SPIFFS.format() has to be called outside of the log macro, THEENGS_LOG_TRACE
|
|
// expands to ((void)0) below LOG_LEVEL_TRACE and discards its arguments with it
|
|
bool formatted = SPIFFS.format();
|
|
THEENGS_LOG_TRACE(F("Formatting requested, result: %d" CR), formatted);
|
|
if (!formatted) {
|
|
THEENGS_LOG_ERROR(F("SPIFFS format failed, config may be retained" CR));
|
|
}
|
|
|
|
#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;
|
|
#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) {
|
|
THEENGS_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;
|
|
if (psramFound()) {
|
|
SYSdata["psram"] = true;
|
|
SYSdata["psramfree"] = ESP.getFreePsram();
|
|
}
|
|
#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
|
|
// Power is initialised by M5.begin() in setupM5(); no explicit begin() needed.
|
|
SYSdata["m5battlevel"] = (int8_t)M5.Power.getBatteryLevel();
|
|
SYSdata["m5ischarging"] = (bool)(M5.Power.isCharging() == m5::Power_Class::is_charging);
|
|
SYSdata["m5ischargefull"] = (bool)(M5.Power.getBatteryLevel() >= 100);
|
|
#endif
|
|
#if defined(ZboardM5STICKC) || defined(ZboardM5STICKCP) || defined(ZboardM5TOUGH)
|
|
// M5Unified exposes a portable battery API across the AXP192/AXP2101 PMICs.
|
|
// getBatteryVoltage() is in mV; convert to volts to preserve the previous
|
|
// field semantics. The AXP192-only Vin/VBus/internal-temperature/power
|
|
// readings have no portable M5Unified equivalent, so those fields are dropped.
|
|
SYSdata["m5batvoltage"] = (float)M5.Power.getBatteryVoltage() / 1000.0f;
|
|
SYSdata["m5batcurrent"] = (float)M5.Power.getBatteryCurrent();
|
|
SYSdata["m5battlevel"] = (int8_t)M5.Power.getBatteryLevel();
|
|
SYSdata["m5ischarging"] = (bool)(M5.Power.isCharging() == m5::Power_Class::is_charging);
|
|
#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);
|
|
THEENGS_LOG_NOTICE(F("SYS json: %s" CR), output.c_str());
|
|
return output;
|
|
}
|
|
|
|
#if defined(ZgatewayRF) || 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();
|
|
THEENGS_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
|
|
THEENGS_LOG_TRACE(F("store code : %u / %u" CR), (unsigned long)receivedSignal[o].value, receivedSignal[o].time);
|
|
THEENGS_LOG_TRACE(F("Col: val/timestamp" CR));
|
|
for (int i = 0; i < struct_size; i++) {
|
|
THEENGS_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) {
|
|
THEENGS_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
|
|
THEENGS_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()) {
|
|
THEENGS_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*>();
|
|
THEENGS_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*>();
|
|
THEENGS_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
|
|
THEENGS_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);
|
|
//THEENGS_LOG_NOTICE(F("[ MQTT->OMG ]: %s" CR), buffer.c_str());
|
|
|
|
#ifdef ZgatewayPilight // ZgatewayPilight is only defined with json publishing due to its numerous parameters
|
|
extern void XtoPilight(const char* topicOri, JsonObject& RFdata);
|
|
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)
|
|
void XtoM5(const char* topicOri, JsonObject& M5data);
|
|
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 <HTTPUpdate.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() {
|
|
THEENGS_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) {
|
|
THEENGS_LOG_NOTICE(F("Using memory cert" CR));
|
|
ota_cert = cnt_parameters_array[cnt_index].ota_server_cert;
|
|
} else
|
|
# endif
|
|
{
|
|
THEENGS_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) {
|
|
THEENGS_LOG_ERROR(F("Deserialize MQTT data failed: %s" CR), error.c_str());
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
THEENGS_LOG_TRACE(F("HttpCode %d" CR), httpCode);
|
|
THEENGS_LOG_TRACE(F("Payload %s" CR), payload.c_str());
|
|
} else {
|
|
THEENGS_LOG_ERROR(F("Error on HTTP request"));
|
|
gatewayState = GatewayState::ERROR;
|
|
}
|
|
http.end(); //Free the resources
|
|
THEENGS_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"] = "";
|
|
// Per-environment version pinning: if the manifest carries an "envs" map with
|
|
// an entry for this build's ENV_NAME, that entry caps the version offered to
|
|
// this board; boards without an entry fall back to the global latest_version.
|
|
// This lets the OTA server hold e.g. theengs-plug at an older release while the
|
|
// rest of the fleet advances, with no board-specific firmware build flags.
|
|
// The "envs" map is expected to list only pinned exceptions so the manifest
|
|
// stays within JSON_MSG_BUFFER (1024 B on ESP32).
|
|
JsonVariant envVersion = jsondata["envs"][ENV_NAME];
|
|
if (!envVersion.isNull()) {
|
|
latestVersion = envVersion.as<String>();
|
|
} else {
|
|
latestVersion = jsondata["latest_version"].as<String>();
|
|
}
|
|
// Publish the resolved value as latest_version so the Home Assistant update
|
|
// entity compares against the (possibly pinned) version, and drop the map so
|
|
// it is not carried in the retained state topic.
|
|
jsondata["latest_version"] = latestVersion;
|
|
jsondata.remove("envs");
|
|
jsondata["origin"] = subjectRLStoMQTT;
|
|
jsondata["retain"] = true;
|
|
enqueueJsonObject(jsondata);
|
|
|
|
THEENGS_LOG_TRACE(F("Update file found on server, offering version %s" CR), latestVersion.c_str());
|
|
return true;
|
|
} else {
|
|
THEENGS_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")) {
|
|
THEENGS_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) {
|
|
THEENGS_LOG_ERROR(F("Invalid OTA password" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_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();
|
|
THEENGS_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();
|
|
THEENGS_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();
|
|
THEENGS_LOG_NOTICE(F("Using system OTA url with defined version %s" CR), url);
|
|
# endif
|
|
} else {
|
|
THEENGS_LOG_ERROR(F("Invalid URL" CR));
|
|
gatewayState = GatewayState::ERROR;
|
|
return;
|
|
}
|
|
# ifdef ESP32
|
|
ProcessLock = true;
|
|
# ifdef ZgatewayBT
|
|
stopProcessing(true);
|
|
# endif
|
|
# endif
|
|
THEENGS_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"] | "");
|
|
THEENGS_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) {
|
|
THEENGS_LOG_NOTICE(F("Using memory cert" CR));
|
|
ota_cert = cnt_parameters_array[cnt_index].ota_server_cert.c_str();
|
|
} else
|
|
# endif
|
|
{
|
|
THEENGS_LOG_NOTICE(F("Using config cert" CR));
|
|
ota_cert = OTAserver_cert;
|
|
}
|
|
}
|
|
|
|
t_httpUpdate_return result = HTTP_UPDATE_FAILED;
|
|
if (strstr(url, "http:")) {
|
|
THEENGS_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
|
|
THEENGS_LOG_ERROR(F("HTTP_UPDATE_FAILED Error (%d): %s\n" CR), httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
|
|
# elif ESP8266
|
|
THEENGS_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:
|
|
THEENGS_LOG_NOTICE(F("HTTP_UPDATE_NO_UPDATES" CR));
|
|
break;
|
|
|
|
case HTTP_UPDATE_OK:
|
|
THEENGS_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) {
|
|
THEENGS_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;
|
|
THEENGS_LOG_TRACE(F("MQTTtoSYS json" CR));
|
|
if (SYSdata.containsKey("cmd")) {
|
|
const char* cmd = SYSdata["cmd"];
|
|
THEENGS_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
|
|
extern void updatePowerIndicator();
|
|
updatePowerIndicator();
|
|
# endif
|
|
THEENGS_LOG_NOTICE(F("RGB brightness: %d" CR), SYSConfig.rgbbrightness);
|
|
publishState = true;
|
|
} else {
|
|
THEENGS_LOG_WARNING(F("RGB brightness value invalid - ignoring command" CR));
|
|
}
|
|
}
|
|
#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);
|
|
|
|
THEENGS_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) {
|
|
THEENGS_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*>())) {
|
|
// Each of the destination buffers is sized at compile time; reject
|
|
// over-length MQTT-supplied values up front so a crafted publish cannot
|
|
// leave the destination non-NUL-terminated (which would corrupt the
|
|
// SPIFFS config on the next saveConfig()).
|
|
if (SYSdata.containsKey("mqtt_topic")) {
|
|
const char* src = SYSdata["mqtt_topic"];
|
|
if (strlen(src) >= sizeof(mqtt_topic)) {
|
|
THEENGS_LOG_WARNING(F("mqtt_topic too long, ignoring" CR));
|
|
} else {
|
|
strcpy(mqtt_topic, src);
|
|
}
|
|
}
|
|
#ifdef ZmqttDiscovery
|
|
if (SYSdata.containsKey("discovery_prefix")) {
|
|
const char* src = SYSdata["discovery_prefix"];
|
|
// discovery_prefix is defined in mqttDiscovery.cpp as char[parameters_size + 1].
|
|
if (strlen(src) > parameters_size) {
|
|
THEENGS_LOG_WARNING(F("discovery_prefix too long, ignoring" CR));
|
|
} else {
|
|
strcpy(discovery_prefix, src);
|
|
}
|
|
}
|
|
#endif
|
|
if (SYSdata.containsKey("gateway_name")) {
|
|
const char* src = SYSdata["gateway_name"];
|
|
if (strlen(src) >= sizeof(gateway_name)) {
|
|
THEENGS_LOG_WARNING(F("gateway_name too long, ignoring" CR));
|
|
} else {
|
|
strcpy(gateway_name, src);
|
|
}
|
|
}
|
|
if (SYSdata.containsKey("gw_pass")) {
|
|
const char* src = SYSdata["gw_pass"];
|
|
if (strlen(src) >= sizeof(ota_pass)) {
|
|
THEENGS_LOG_WARNING(F("gw_pass too long, ignoring" CR));
|
|
} else {
|
|
strcpy(ota_pass, src);
|
|
restartESP = true;
|
|
}
|
|
}
|
|
#ifndef ESPWifiManualSetup
|
|
saveConfig();
|
|
#endif
|
|
mqttSetupPending = true; // trigger reconnect in loop using the new topic/name
|
|
}
|
|
|
|
#if BLEDecryptor
|
|
if (SYSdata.containsKey("ble_aes") || SYSdata.containsKey("ble_aes_keys")) {
|
|
if (SYSdata.containsKey("ble_aes")) {
|
|
const char* src = SYSdata["ble_aes"];
|
|
if (strlen(src) >= sizeof(ble_aes)) {
|
|
THEENGS_LOG_WARNING(F("ble_aes too long, ignoring" CR));
|
|
} else {
|
|
strcpy(ble_aes, src);
|
|
}
|
|
}
|
|
if (SYSdata.containsKey("ble_aes_keys")) {
|
|
THEENGS_LOG_WARNING(F("Contains ble_aes_keys" CR));
|
|
ble_aes_keys = SYSdata["ble_aes_keys"];
|
|
} else {
|
|
// If no keys are passed clear the object.
|
|
StaticJsonDocument<JSON_BLE_AES_CUSTOM_KEYS> jsonBLEBuffer;
|
|
ble_aes_keys = jsonBLEBuffer.to<JsonObject>();
|
|
}
|
|
# ifndef ESPWifiManualSetup
|
|
saveConfig();
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
#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) {
|
|
THEENGS_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];
|
|
|
|
THEENGS_LOG_NOTICE(F("MQTT cnt index %d" CR), cnt_index);
|
|
|
|
// Length-check every MQTT-supplied string before copying into a fixed buffer
|
|
// to prevent heap overflows.
|
|
if (SYSdata.containsKey("mqtt_user") && SYSdata["mqtt_user"].is<const char*>() && SYSdata.containsKey("mqtt_pass") && SYSdata["mqtt_pass"].is<const char*>()) {
|
|
const char* user = SYSdata["mqtt_user"];
|
|
const char* pass = SYSdata["mqtt_pass"];
|
|
if (strlen(user) >= sizeof(cnt_parameters_array[cnt_index].mqtt_user) ||
|
|
strlen(pass) >= sizeof(cnt_parameters_array[cnt_index].mqtt_pass)) {
|
|
THEENGS_LOG_WARNING(F("mqtt_user/mqtt_pass too long, ignoring" CR));
|
|
} else {
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_user, user);
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_pass, pass);
|
|
cnt_parameters_array[cnt_index].validConnection = false;
|
|
}
|
|
}
|
|
|
|
if (SYSdata.containsKey("mqtt_server") && SYSdata["mqtt_server"].is<const char*>()) {
|
|
const char* server = SYSdata["mqtt_server"];
|
|
if (strlen(server) >= sizeof(cnt_parameters_array[cnt_index].mqtt_server)) {
|
|
THEENGS_LOG_WARNING(F("mqtt_server too long, ignoring" CR));
|
|
} else {
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_server, server);
|
|
cnt_parameters_array[cnt_index].validConnection = false;
|
|
}
|
|
}
|
|
|
|
if (SYSdata.containsKey("mqtt_port") && SYSdata["mqtt_port"].is<const char*>()) {
|
|
const char* port = SYSdata["mqtt_port"];
|
|
if (strlen(port) >= sizeof(cnt_parameters_array[cnt_index].mqtt_port)) {
|
|
THEENGS_LOG_WARNING(F("mqtt_port too long, ignoring" CR));
|
|
} else {
|
|
strcpy(cnt_parameters_array[cnt_index].mqtt_port, 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*>());
|
|
THEENGS_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*>());
|
|
THEENGS_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*>());
|
|
THEENGS_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*>());
|
|
THEENGS_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"];
|
|
THEENGS_LOG_NOTICE(F("xtomqtt: %T" CR), SYSConfig.mqtt);
|
|
}
|
|
if (SYSdata.containsKey("serial") && SYSdata["serial"].is<bool>()) {
|
|
SYSConfig.serial = SYSdata["serial"];
|
|
THEENGS_LOG_NOTICE(F("SERIAL: %T" CR), SYSConfig.serial);
|
|
}
|
|
if (SYSdata.containsKey("offline") && SYSdata["offline"].is<bool>()) {
|
|
SYSConfig.offline = SYSdata["offline"];
|
|
THEENGS_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"];
|
|
THEENGS_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;
|
|
THEENGS_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) {
|
|
extern void pubMqttDiscovery();
|
|
pubMqttDiscovery();
|
|
}
|
|
} else {
|
|
THEENGS_LOG_WARNING(F("Discovery command not a boolean" CR));
|
|
}
|
|
THEENGS_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);
|
|
}
|
|
}
|
|
}
|