Tasmota/tasmota/xdrv_02_9_mqtt.ino

1949 lines
80 KiB
Arduino
Raw Normal View History

/*
2021-06-02 17:44:07 +01:00
xdrv_02_9_mqtt.ino - mqtt support for Tasmota
2021-01-01 12:44:04 +00:00
Copyright (C) 2021 Theo Arends
This program 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.
This program 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/>.
*/
2020-11-17 16:15:29 +00:00
#define XDRV_02 2
#define USE_MQTT_NEW_PUBSUBCLIENT
// #define DEBUG_DUMP_TLS // allow dumping of TLS Flash keys
2018-11-20 14:00:24 +00:00
#ifdef USE_MQTT_TLS
2019-06-05 10:44:52 +01:00
#include "WiFiClientSecureLightBearSSL.h"
BearSSL::WiFiClientSecure_light *tlsClient;
2018-11-20 14:00:24 +00:00
#endif
WiFiClient EspClient; // Wifi Client - non-TLS
2018-11-20 14:00:24 +00:00
#ifdef USE_MQTT_AZURE_IOT
#undef MQTT_PORT
#define MQTT_PORT 8883
#if defined(USE_MQTT_AZURE_DPS_SCOPEID) && defined(USE_MQTT_AZURE_DPS_PRESHAREDKEY)
#include <ESP8266HTTPClient.h>
// dedicated tlsHttpsClient for DPS as the 'tlsClient' above causes error '-1' in httpsClient after it is associated with PubSub. It cost ~5K of heap
BearSSL::WiFiClientSecure_light *tlsHttpsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
HTTPClient httpsClient;
int httpsClientReturn;
#endif // USE_MQTT_AZURE_DPS_SCOPEID
2021-05-12 16:10:50 +01:00
#include <base64.hpp>
#include <t_bearssl.h>
#include <JsonParser.h>
#endif // USE_MQTT_AZURE_IOT
const char kMqttCommands[] PROGMEM = "|" // No prefix
2021-01-28 08:04:42 +00:00
// SetOption synonyms
2021-02-28 11:50:02 +00:00
D_SO_MQTTJSONONLY "|"
2021-01-28 08:04:42 +00:00
#ifdef USE_MQTT_TLS
D_SO_MQTTTLS "|" D_SO_MQTTTLS_FINGERPRINT "|"
2021-01-28 08:04:42 +00:00
#endif
D_SO_MQTTNORETAIN "|" D_SO_MQTTDETACHRELAY "|"
// regular commands
#if defined(USE_MQTT_TLS)
D_CMND_MQTTFINGERPRINT "|"
#endif
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_MQTTKEEPALIVE "|" D_CMND_MQTTTIMEOUT "|" D_CMND_MQTTWIFITIMEOUT "|"
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
D_CMND_TLSKEY "|"
#endif
2021-05-12 16:10:50 +01:00
#ifdef USE_MQTT_FILE
D_CMND_FILEUPLOAD "|" D_CMND_FILEDOWNLOAD "|"
#endif // USE_MQTT_FILE
D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTCLIENT "|"
D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|" D_CMND_MQTTLOG "|"
D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|"
2021-05-12 16:10:50 +01:00
D_CMND_SENSORRETAIN "|" D_CMND_INFORETAIN "|" D_CMND_STATERETAIN ;
2021-01-28 08:04:42 +00:00
SO_SYNONYMS(kMqttSynonyms,
90,
#ifdef USE_MQTT_TLS
103, 132,
2021-01-28 08:04:42 +00:00
#endif
104, 114
);
void (* const MqttCommand[])(void) PROGMEM = {
#if defined(USE_MQTT_TLS)
&CmndMqttFingerprint,
#endif
&CmndMqttUser, &CmndMqttPassword, &CmndMqttKeepAlive, &CmndMqttTimeout, &CmndMqttWifiTimeout,
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
&CmndTlsKey,
#endif
2021-05-12 16:10:50 +01:00
#ifdef USE_MQTT_FILE
&CmndFileUpload, &CmndFileDownload,
#endif // USE_MQTT_FILE
&CmndMqttHost, &CmndMqttPort, &CmndMqttRetry, &CmndStateText, &CmndMqttClient,
&CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish, &CmndMqttlog,
&CmndButtonTopic, &CmndSwitchTopic, &CmndButtonRetain, &CmndSwitchRetain, &CmndPowerRetain, &CmndSensorRetain,
2021-05-12 16:10:50 +01:00
&CmndInfoRetain, &CmndStateRetain };
struct MQTT {
uint16_t connect_count = 0; // MQTT re-connect count
uint16_t retry_counter = 1; // MQTT connection retry counter
2020-11-22 11:26:05 +00:00
uint16_t retry_counter_delay = 1; // MQTT retry counter multiplier
uint8_t initial_connection_state = 2; // MQTT connection messages state
bool connected = false; // MQTT virtual connection status
bool allowed = false; // MQTT enabled and parameters valid
2020-09-14 20:48:32 +01:00
bool mqtt_tls = false; // MQTT TLS is enabled
2021-05-21 15:19:40 +01:00
bool disable_logging = false; // Temporarly disable logging on some commands
} Mqtt;
#ifdef USE_MQTT_TLS
2019-06-05 10:44:52 +01:00
// This part of code is necessary to store Private Key and Cert in Flash
#ifdef USE_MQTT_AWS_IOT
2021-05-12 16:10:50 +01:00
#include <base64.hpp>
const br_ec_private_key *AWS_IoT_Private_Key = nullptr;
const br_x509_certificate *AWS_IoT_Client_Certificate = nullptr;
class tls_entry_t {
public:
uint32_t name; // simple 4 letters name. Currently 'skey', 'crt ', 'crt1', 'crt2'
uint16_t start; // start offset
uint16_t len; // len of object
}; // 8 bytes
const static uint32_t TLS_NAME_SKEY = 0x2079656B; // 'key ' little endian
const static uint32_t TLS_NAME_CRT = 0x20747263; // 'crt ' little endian
class tls_dir_t {
public:
tls_entry_t entry[4]; // 4 entries max, only 4 used today, for future use
}; // 4*8 = 64 bytes
tls_dir_t tls_dir; // memory copy of tls_dir from flash
#endif // USE_MQTT_AWS_IOT
2019-06-05 10:44:52 +01:00
// check whether the fingerprint is filled with a single value
// Filled with 0x00 = accept any fingerprint and learn it for next time
// Filled with 0xFF = accept any fingerpring forever
bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value) {
for (uint32_t i = 0; i<20; i++) {
if (finger[i] != value) {
return false;
}
}
return true;
}
#endif // USE_MQTT_TLS
2019-06-05 10:44:52 +01:00
void MakeValidMqtt(uint32_t option, char* str) {
// option 0 = replace by underscore
// option 1 = delete character
uint32_t i = 0;
while (str[i] > 0) {
// if ((str[i] == '/') || (str[i] == '+') || (str[i] == '#') || (str[i] == ' ')) {
if ((str[i] == '+') || (str[i] == '#') || (str[i] == ' ')) {
if (option) {
uint32_t j = i;
while (str[j] > 0) {
str[j] = str[j +1];
j++;
}
i--;
} else {
str[i] = '_';
}
}
i++;
}
}
2021-05-21 15:19:40 +01:00
void MqttDisableLogging(bool state) {
// Disable logging only on repeating MQTT messages
Mqtt.disable_logging = state;
TasmotaGlobal.masterlog_level = (Mqtt.disable_logging) ? LOG_LEVEL_DEBUG_MORE : LOG_LEVEL_NONE;
}
/*********************************************************************************************\
* MQTT driver specific code need to provide the following functions:
*
* bool MqttIsConnected()
* void MqttDisconnect()
* void MqttSubscribeLib(char *topic)
2021-05-19 16:21:50 +01:00
* bool MqttPublishLib(const char* topic, const uint8_t* payload, unsigned int plength, bool retained)
2021-06-10 17:06:11 +01:00
*
2021-06-09 16:24:28 +01:00
* Change/Verify PubSubClient.h defines:
* #define MQTT_MAX_PACKET_SIZE 1200 // Tasmota v8.1.0.8
\*********************************************************************************************/
#include <PubSubClient.h>
2019-06-05 10:44:52 +01:00
PubSubClient MqttClient;
void MqttInit(void) {
#ifdef USE_MQTT_AZURE_IOT
2021-06-11 17:14:12 +01:00
Settings->mqtt_port = 8883;
#endif //USE_MQTT_AZURE_IOT
#ifdef USE_MQTT_TLS
2021-08-16 13:20:10 +01:00
if ((8883 == Settings->mqtt_port) || (8884 == Settings->mqtt_port) || (443 == Settings->mqtt_port)) {
// Turn on TLS for port 8883 (TLS), 8884 (TLS, client certificate), 443 (TLS, user/password)
2021-06-11 17:14:12 +01:00
Settings->flag4.mqtt_tls = true;
}
2021-06-11 17:14:12 +01:00
Mqtt.mqtt_tls = Settings->flag4.mqtt_tls; // this flag should not change even if we change the SetOption (until reboot)
// Detect AWS IoT and set default parameters
String host = String(SettingsText(SET_MQTT_HOST));
2021-01-18 20:48:04 +00:00
if (host.indexOf(F(".iot.")) && host.endsWith(F(".amazonaws.com"))) { // look for ".iot." and ".amazonaws.com" in the domain name
2021-06-11 17:14:12 +01:00
Settings->flag4.mqtt_no_retain = true;
}
2020-09-14 20:48:32 +01:00
if (Mqtt.mqtt_tls) {
2021-03-12 07:18:16 +00:00
#ifdef ESP32
tlsClient = new BearSSL::WiFiClientSecure_light(2048,2048);
#else // ESP32 - ESP8266
tlsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
2021-03-12 07:18:16 +00:00
#endif
2021-11-10 17:45:50 +00:00
#ifdef USE_MQTT_AWS_IOT_LIGHT
if (443 == Settings->mqtt_port) {
static const char * alpn_mqtt = "mqtt"; // needs to be static
tlsClient->setALPN(&alpn_mqtt, 1); // need to set alpn to 'mqtt' for AWS IoT
}
#endif
2019-06-05 10:44:52 +01:00
#ifdef USE_MQTT_AWS_IOT
loadTlsDir(); // load key and certificate data from Flash
2020-09-14 20:48:32 +01:00
if ((nullptr != AWS_IoT_Private_Key) && (nullptr != AWS_IoT_Client_Certificate)) {
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
AWS_IoT_Private_Key,
0xFFFF /* all usages, don't care */, 0);
}
#endif
2019-06-05 10:44:52 +01:00
if (!Settings->flag5.tls_use_fingerprint) {
tlsClient->setTrustAnchor(Tasmota_TA, nitems(Tasmota_TA));
}
MqttClient.setClient(*tlsClient);
} else {
MqttClient.setClient(EspClient); // non-TLS
}
#else // USE_MQTT_TLS
MqttClient.setClient(EspClient);
#endif // USE_MQTT_TLS
2021-06-11 17:14:12 +01:00
MqttClient.setKeepAlive(Settings->mqtt_keepalive);
MqttClient.setSocketTimeout(Settings->mqtt_socket_timeout);
2019-06-05 10:44:52 +01:00
}
#ifdef USE_MQTT_AZURE_IOT
String Sha256Sign(String dataToSign, String preSharedKey){
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "sha256 dataToSign is '%s'"), String(dataToSign).c_str());
char dataToSignChar[dataToSign.length() + 1];
dataToSign.toCharArray(dataToSignChar, dataToSign.length() + 1);
unsigned char decodedPSK[32];
unsigned char encryptedSignature[100];
unsigned char encodedSignature[100];
br_sha256_context sha256_context;
br_hmac_key_context hmac_key_context;
br_hmac_context hmac_context;
// need to base64 decode the Preshared key and the length
int base64_decoded_device_length = decode_base64((unsigned char*)preSharedKey.c_str(), decodedPSK);
// create the sha256 hmac and hash the data
br_sha256_init(&sha256_context);
br_hmac_key_init(&hmac_key_context, sha256_context.vtable, decodedPSK, base64_decoded_device_length);
br_hmac_init(&hmac_context, &hmac_key_context, 32);
br_hmac_update(&hmac_context, dataToSignChar, sizeof(dataToSignChar)-1);
br_hmac_out(&hmac_context, encryptedSignature);
// base64 decode the HMAC to a char
encode_base64(encryptedSignature, br_hmac_size(&hmac_context), encodedSignature);
// creating the real SAS Token
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "sha256 signature is '%s'"), String((char*)encodedSignature).c_str());
return String((char*)encodedSignature);
}
String urlEncodeBase64(String stringToEncode){
// correctly URL encoding the 64 characters of Base64 and the '=' sign
stringToEncode.replace("+", "%2B");
stringToEncode.replace("=", "%3D");
stringToEncode.replace("/", "%2F");
return stringToEncode;
}
String AzurePSKtoToken(char *iotHubFQDN, const char *deviceId, const char *preSharedKey, int sasTTL = 86400){
int ttl = time(NULL) + sasTTL;
String dataToSignString = urlEncodeBase64(String(iotHubFQDN) + "/devices/" + String(deviceId)) + "\n" + String(ttl);
String signedData = Sha256Sign(dataToSignString, String(preSharedKey));
// creating the real SAS Token
String realSASToken = "SharedAccessSignature sr=" + urlEncodeBase64(String(iotHubFQDN) + "/devices/" + String(deviceId));
realSASToken += "&sig=" + urlEncodeBase64(signedData) + "&se=" + String(ttl);
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Azure IoT Hub SAS Token is '%s'"), realSASToken.c_str());
return realSASToken;
}
#if defined(USE_MQTT_AZURE_DPS_SCOPEID) && defined(USE_MQTT_AZURE_DPS_PRESHAREDKEY)
String AzureDSPPSKtoToken(String scopeId, String deviceId, const char *preSharedKey, int sasTTL = 3600){
int ttl = time(NULL) + sasTTL;
String dataToSignString = urlEncodeBase64(scopeId + "/registrations/" + deviceId) + "\n" + String(ttl);
String signedData = Sha256Sign(dataToSignString, String(preSharedKey));
// creating the real SAS Token
String realSASToken = "SharedAccessSignature sr=" + urlEncodeBase64(scopeId + "/registrations/" + deviceId);
realSASToken += "&sig=" + urlEncodeBase64(signedData) + "&skn=registration" + "&se=" + String(ttl);
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Azure DPS SAS Token is '%s'"), realSASToken.c_str());
return realSASToken;
}
void ProvisionAzureDPS(){
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Starting Azure DPS registration..."));
// Scope and Key are derived from user_config_override.h, USE_MQTT_AZURE_DPS_SCOPE_ENDPOINT is optional
String dPSScopeId = USE_MQTT_AZURE_DPS_SCOPEID;
String dPSPreSharedKey = USE_MQTT_AZURE_DPS_PRESHAREDKEY;
#if defined(USE_MQTT_AZURE_DPS_SCOPE_ENDPOINT)
String endpoint=USE_MQTT_AZURE_DPS_SCOPE_ENDPOINT;
#else
String endpoint="https://global.azure-devices-provisioning.net/";
#endif //USE_MQTT_AZURE_DPS_SCOPE_ENDPOINT
String MACAddress = WiFi.macAddress();
MACAddress.replace(":", "");
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "DPS register for %s, scope %s to %s."), MACAddress.c_str(), dPSScopeId.c_str(), endpoint.c_str());
// derive our PSK from the DPS and set the device ID
String devicePresharedKey = Sha256Sign(MACAddress, dPSPreSharedKey);
char devicePresharedKeyChar[devicePresharedKey.length() + 1];
devicePresharedKey.toCharArray(devicePresharedKeyChar, devicePresharedKey.length() + 1);
// generate a SAS Token with this new derived key
String dPSSASToken = AzureDSPPSKtoToken(dPSScopeId, MACAddress, devicePresharedKey.c_str());
// REST to DPS to start the assigning process
String dPSURL = endpoint + dPSScopeId + "/registrations/" + MACAddress + "/register?api-version=2019-03-31";
String dPSPutContent = "{\"registrationId\": \"" + MACAddress + "\"}";
httpsClient.setReuse(true);
httpsClient.begin(*tlsHttpsClient, dPSURL);
httpsClient.addHeader("User-Agent", "Tasmota");
httpsClient.addHeader("Content-Type", "application/json");
httpsClient.addHeader("Content-Encoding", "utf-8");
httpsClient.addHeader("Authorization", dPSSASToken);
httpsClientReturn = httpsClient.PUT(dPSPutContent);
String dPSAssigningResponseJSON;
if (httpsClientReturn == HTTP_CODE_ACCEPTED){
dPSAssigningResponseJSON = httpsClient.getString();
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "DPS Assigning response '%s'"), dPSAssigningResponseJSON.c_str());
} else {
dPSAssigningResponseJSON = httpsClient.getString();
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "DPS Assigning response '%s'"), dPSAssigningResponseJSON.c_str());
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_MQTT "Azure DPS REST assignment connection failed with code '%d'. Restarting."), httpsClientReturn);
WebRestart(1);
}
if (dPSAssigningResponseJSON.indexOf("\"assigning\"") == -1){
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_MQTT "Azure DPS assignment failed with response '%s'. Restarting."), dPSAssigningResponseJSON.c_str());
WebRestart(1);
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Azure DPS assignment response '%s'."), dPSAssigningResponseJSON.c_str());
}
httpsClient.end();
JsonParser dPSAssigningResponseParser((char*) dPSAssigningResponseJSON.c_str());
JsonParserObject dPSAssigningResponseRoot = dPSAssigningResponseParser.getRootObject();
String dPSAssigningOperationId = dPSAssigningResponseRoot.getStr("operationId");
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "DPS operationId is '%s'."), dPSAssigningOperationId.c_str());
bool assigned = false;
int assignedCounter = 1;
String dPSAssignedResponseJSON;
dPSURL = endpoint + dPSScopeId + "/registrations/" + MACAddress + "/operations/" + dPSAssigningOperationId + "?api-version=2019-03-31";
while (!assigned && assignedCounter < 5){
httpsClient.begin(*tlsHttpsClient, dPSURL);
httpsClient.addHeader("User-Agent", "Tasmota");
httpsClient.addHeader("Content-Type", "application/json");
httpsClient.addHeader("Content-Encoding", "utf-8");
httpsClient.addHeader("Authorization", dPSSASToken);
httpsClientReturn = httpsClient.GET();
if (httpsClientReturn == HTTP_CODE_OK){
dPSAssignedResponseJSON = httpsClient.getString();
} else if (httpsClientReturn != HTTP_CODE_ACCEPTED){
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_MQTT "Azure DPS REST check connection failed with code '%d'."), httpsClientReturn);
}
if (dPSAssignedResponseJSON.indexOf("\"status\":\"assigned\"") > 0){
assigned = true;
} else if (httpsClientReturn != HTTP_CODE_ACCEPTED) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "DPS try %d, response '%s'."), assignedCounter, dPSAssignedResponseJSON.c_str());
}
delay(1000 * assignedCounter);
assignedCounter+=1;
}
httpsClient.end();
if (assigned){
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Azure DPS registration response '%s'."), dPSAssignedResponseJSON.c_str());
JsonParser parser((char*) dPSAssignedResponseJSON.c_str());
JsonParserObject stateObject = parser.getRootObject()[PSTR("registrationState")].getObject();
String deviceId = stateObject["deviceId"].getStr();
String iotHub = stateObject["assignedHub"].getStr();
bool newProvision = false;
if (String(SettingsText(SET_MQTT_PWD)) != devicePresharedKey ||
String(SettingsText(SET_MQTT_HOST)) != iotHub ||
String(SettingsText(SET_MQTT_CLIENT)) != deviceId ||
String(SettingsText(SET_MQTT_USER)) != deviceId) {
newProvision = true;
SettingsUpdateText(SET_MQTT_PWD, devicePresharedKey.c_str());
SettingsUpdateText(SET_MQTT_HOST, iotHub.c_str());
SettingsUpdateText(SET_MQTT_CLIENT, deviceId.c_str());
SettingsUpdateText(SET_MQTT_USER, deviceId.c_str());
}
if (newProvision){ // because this is the first time we have been provisioned must reboot
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Azure DPS registration success, changed in DPS registration, restarting."));
WebRestart(1);
} else {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Azure DPS registration success, no changes."));
}
} else {
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_MQTT "Azure DPS registration response failed with response '%s'."), dPSAssignedResponseJSON.c_str());
}
}
#endif // USE_MQTT_AZURE_DPS_SCOPEID
#endif // USE_MQTT_AZURE_IOT
bool MqttIsConnected(void) {
return MqttClient.connected();
}
void MqttDisconnect(void) {
MqttClient.disconnect();
}
void MqttSubscribeLib(const char *topic) {
#ifdef USE_MQTT_AZURE_IOT
// Azure IoT Hub currently does not support custom topics: https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-mqtt-support
String realTopicString = "devices/" + String(SettingsText(SET_MQTT_CLIENT));
realTopicString += "/messages/devicebound/#";
MqttClient.subscribe(realTopicString.c_str());
SettingsUpdateText(SET_MQTT_FULLTOPIC, SettingsText(SET_MQTT_CLIENT));
SettingsUpdateText(SET_MQTT_TOPIC, SettingsText(SET_MQTT_CLIENT));
#else
MqttClient.subscribe(topic);
#endif // USE_MQTT_AZURE_IOT
MqttClient.loop(); // Solve LmacRxBlk:1 messages
}
void MqttUnsubscribeLib(const char *topic) {
Rules: Trigger Event with MQTT Subscriptions Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic. You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter. For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this. Two new commands in Rules: 1. Subscribe Subscribe a MQTT topic (with or without key) and assign an event name to it. Command format: Subscribe [<event_name>, <topic> [, <key>]] This command will subscribe a <topic> and give it an event name <event_name>. The optional parameter <key> is for parse the specified key/value from MQTT message payload with JSON format. In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section. Subscribe command without any parameter will list all topics currently subscribed. 2. Unsubscribe Unsubscribe specified MQTT event. Command format: Unsubscribe [<event_name>] Unsubscribe a topic subscribed by specify the event name. If no event specified, Unsubscribe all topics subscribed. Examples: 1. Subscribe BkLight, Tasmota/BackyardLight/stat/POWER And define a rule like: Rule1 on event#BkLight=ON do ruletimer4 60 endon 2. Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}} Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
MqttClient.unsubscribe(topic);
MqttClient.loop(); // Solve LmacRxBlk:1 messages
}
2021-05-19 16:21:50 +01:00
bool MqttPublishLib(const char* topic, const uint8_t* payload, unsigned int plength, bool retained) {
2020-01-18 14:34:01 +00:00
// If Prefix1 equals Prefix2 disable next MQTT subscription to prevent loop
if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
char *str = strstr(topic, SettingsText(SET_MQTTPREFIX1));
if (str == topic) {
2020-10-29 12:58:50 +00:00
TasmotaGlobal.mqtt_cmnd_blocked_reset = 4; // Allow up to four seconds before resetting residual cmnd blocks
TasmotaGlobal.mqtt_cmnd_blocked++;
2020-01-18 14:34:01 +00:00
}
}
2021-06-22 13:12:05 +01:00
#ifdef USE_TASMESH
if (MESHrouteMQTTtoMESH(topic, (char*)payload, retained)) { // If we are a node, send this via ESP-Now
yield();
return true;
}
#endif // USE_TASMESH
#ifdef USE_MQTT_AZURE_IOT
String sourceTopicString = urlEncodeBase64(String(topic));
String topicString = "devices/" + String(SettingsText(SET_MQTT_CLIENT));
2021-05-19 16:21:50 +01:00
topicString += "/messages/events/topic=" + sourceTopicString;
2021-05-19 16:21:50 +01:00
JsonParser mqtt_message((char*) String((const char*)payload).c_str());
JsonParserObject message_object = mqtt_message.getRootObject();
2021-05-19 16:21:50 +01:00
if (!message_object.isValid()) { // only sending valid JSON, yet this is optional
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Invalid JSON for topic '%s', not sending to Azure IoT Hub"), topic);
return true;
}
2021-05-19 16:21:50 +01:00
topic = topicString.c_str();
#endif // USE_MQTT_AZURE_IOT
2021-05-19 16:21:50 +01:00
if (!MqttClient.beginPublish(topic, plength, retained)) {
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Connection lost or message too large"));
return false;
}
uint32_t written = MqttClient.write(payload, plength);
if (written != plength) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Message too large"));
return false;
}
MqttClient.endPublish();
yield(); // #3313
return true;
}
void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len) {
2021-06-25 16:09:53 +01:00
SHOW_FREE_MEM(PSTR("MqttDataHandler"));
// Do not allow more data than would be feasable within stack space
if (data_len >= MQTT_MAX_PACKET_SIZE) { return; }
// Do not execute multiple times if Prefix1 equals Prefix2
if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
char *str = strstr(mqtt_topic, SettingsText(SET_MQTTPREFIX1));
2020-10-29 12:58:50 +00:00
if ((str == mqtt_topic) && TasmotaGlobal.mqtt_cmnd_blocked) {
TasmotaGlobal.mqtt_cmnd_blocked--;
return;
}
}
2021-05-15 10:53:37 +01:00
#ifdef USE_MQTT_FILE
FMqtt.topic_size = strlen(mqtt_topic);
#endif // USE_MQTT_FILE
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "BufferSize %d, Topic |%s|, Length %d, data_len %d"), MqttClient.getBufferSize(), mqtt_topic, strlen(mqtt_topic), data_len);
char topic[TOPSZ];
#ifdef USE_MQTT_AZURE_IOT
// for Azure, we read the topic from the property of the message
String fullTopicString = String(mqtt_topic);
String toppicUpper = fullTopicString;
toppicUpper.toUpperCase();
int startOfTopic = toppicUpper.indexOf("TOPIC=");
if (startOfTopic == -1){
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_MQTT "Azure IoT message without the property topic."));
return;
}
String newTopic = fullTopicString.substring(startOfTopic + 6);
newTopic.replace("%2F", "/");
if (newTopic.indexOf("/") == -1){
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_MQTT "Invalid Topic %s"), newTopic.c_str());
return;
}
strlcpy(topic, newTopic.c_str(), sizeof(topic));
#else
strlcpy(topic, mqtt_topic, sizeof(topic));
#endif // USE_MQTT_AZURE_IOT
mqtt_data[data_len] = 0;
2021-05-21 15:19:40 +01:00
if (Mqtt.disable_logging) {
TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide logging
}
2021-06-22 13:12:05 +01:00
#ifdef USE_TASMESH
#ifdef ESP32
if (MESHinterceptMQTTonBroker(topic, (uint8_t*)mqtt_data, data_len +1)) {
return; // Check if this is a message for a node
}
#endif // ESP32
#endif // USE_TASMESH
// MQTT pre-processing
XdrvMailbox.index = strlen(topic);
XdrvMailbox.data_len = data_len;
XdrvMailbox.topic = topic;
2021-05-24 15:52:59 +01:00
XdrvMailbox.data = (char*)mqtt_data;
if (XdrvCall(FUNC_MQTT_DATA)) { return; }
ShowSource(SRC_MQTT);
2021-05-24 15:52:59 +01:00
CommandHandler(topic, (char*)mqtt_data, data_len);
2021-05-21 15:19:40 +01:00
if (Mqtt.disable_logging) {
TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging
}
}
/*********************************************************************************************/
void MqttRetryCounter(uint8_t value) {
Mqtt.retry_counter = value;
}
void MqttSubscribe(const char *topic) {
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT D_SUBSCRIBE_TO " %s"), topic);
MqttSubscribeLib(topic);
}
void MqttUnsubscribe(const char *topic) {
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT D_UNSUBSCRIBE_FROM " %s"), topic);
Rules: Trigger Event with MQTT Subscriptions Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic. You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter. For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this. Two new commands in Rules: 1. Subscribe Subscribe a MQTT topic (with or without key) and assign an event name to it. Command format: Subscribe [<event_name>, <topic> [, <key>]] This command will subscribe a <topic> and give it an event name <event_name>. The optional parameter <key> is for parse the specified key/value from MQTT message payload with JSON format. In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section. Subscribe command without any parameter will list all topics currently subscribed. 2. Unsubscribe Unsubscribe specified MQTT event. Command format: Unsubscribe [<event_name>] Unsubscribe a topic subscribed by specify the event name. If no event specified, Unsubscribe all topics subscribed. Examples: 1. Subscribe BkLight, Tasmota/BackyardLight/stat/POWER And define a rule like: Rule1 on event#BkLight=ON do ruletimer4 60 endon 2. Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}} Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
MqttUnsubscribeLib(topic);
}
2020-12-22 14:26:07 +00:00
void MqttPublishLoggingAsync(bool refresh) {
2020-12-19 16:29:14 +00:00
static uint32_t index = 1;
2021-06-11 17:14:12 +01:00
if (!Settings->mqttlog_level || !Settings->flag.mqtt_enabled || !Mqtt.connected) { return; } // SetOption3 - Enable MQTT
if (refresh && !NeedLogRefresh(Settings->mqttlog_level, index)) { return; }
2020-12-19 16:29:14 +00:00
char* line;
size_t len;
2021-06-11 17:14:12 +01:00
while (GetLog(Settings->mqttlog_level, &index, &line, &len)) {
2020-12-19 16:29:14 +00:00
char stopic[TOPSZ];
GetTopic_P(stopic, STAT, TasmotaGlobal.mqtt_topic, PSTR("LOGGING"));
2021-05-23 13:42:27 +01:00
MqttPublishLib(stopic, (const uint8_t*)line, len -1, false);
2020-12-19 16:29:14 +00:00
}
}
2021-05-19 16:21:50 +01:00
void MqttPublishPayload(const char* topic, const char* payload, uint32_t binary_length, bool retained) {
// Publish <topic> payload string or binary when binary_length set with optional retained
2021-06-25 16:09:53 +01:00
SHOW_FREE_MEM(PSTR("MqttPublish"));
2020-01-18 14:34:01 +00:00
2021-05-19 16:21:50 +01:00
bool binary_data = (binary_length > 0);
if (!binary_data) {
binary_length = strlen(payload);
}
2021-06-11 17:14:12 +01:00
if (Settings->flag4.mqtt_no_retain) { // SetOption104 - Disable all MQTT retained messages, some brokers don't support it: AWS IoT, Losant
2021-04-21 09:38:20 +01:00
retained = false; // Some brokers don't support retained, they will disconnect if received
}
2021-06-10 17:06:11 +01:00
// To lower heap usage the payload is not copied to the heap but used directly
String log_data_topic; // 20210420 Moved to heap to solve tight stack resulting in exception 2
2021-06-11 17:14:12 +01:00
if (Settings->flag.mqtt_enabled && MqttPublishLib(topic, (const uint8_t*)payload, binary_length, retained)) { // SetOption3 - Enable MQTT
2021-06-22 13:12:05 +01:00
#ifdef USE_TASMESH
log_data_topic = (MESHroleNode()) ? F("MSH: ") : F(D_LOG_MQTT); // MSH: or MQT:
#else
2021-06-10 17:06:11 +01:00
log_data_topic = F(D_LOG_MQTT); // MQT:
2021-06-25 16:09:53 +01:00
#endif // USE_TASMESH
2021-06-10 17:06:11 +01:00
log_data_topic += topic; // stat/tasmota/STATUS2
2021-04-21 08:52:05 +01:00
} else {
2021-06-10 17:06:11 +01:00
log_data_topic = F(D_LOG_RESULT); // RSL:
char *command = strrchr(topic, '/'); // If last part of topic it is always the command
log_data_topic += (command == nullptr) ? topic : command +1; // STATUS2
2021-04-21 09:38:20 +01:00
retained = false; // Without MQTT enabled there is no retained message
}
2021-06-10 17:06:11 +01:00
log_data_topic += F(" = "); // =
char* log_data_payload = (char*)payload;
String log_data_payload_b;
if (binary_data) {
log_data_payload_b = HexToString((uint8_t*)payload, binary_length);
log_data_payload = (char*)log_data_payload_b.c_str();
}
char* log_data_retained = nullptr;
String log_data_retained_b;
if (retained) {
log_data_retained_b = F(" (" D_RETAINED ")"); // (retained)
log_data_retained = (char*)log_data_retained_b.c_str();
}
AddLogData(LOG_LEVEL_INFO, log_data_topic.c_str(), log_data_payload, log_data_retained); // MQT: stat/tasmota/STATUS2 = {"StatusFWR":{"Version":...
2021-06-11 17:14:12 +01:00
if (Settings->ledstate &0x04) {
2020-10-29 11:39:44 +00:00
TasmotaGlobal.blinks++;
}
}
2021-05-19 16:21:50 +01:00
void MqttPublishPayload(const char* topic, const char* payload) {
// Publish <topic> payload string no retained
MqttPublishPayload(topic, payload, 0, false);
}
void MqttPublish(const char* topic, bool retained) {
// Publish <topic> default ResponseData string with optional retained
MqttPublishPayload(topic, ResponseData(), 0, retained);
2021-05-19 16:21:50 +01:00
}
void MqttPublish(const char* topic) {
// Publish <topic> default ResponseData string no retained
MqttPublish(topic, false);
}
2021-05-19 16:21:50 +01:00
void MqttPublishPayloadPrefixTopic_P(uint32_t prefix, const char* subtopic, const char* payload, uint32_t binary_length, bool retained) {
/*
Publish <prefix>/<device>/<RESULT or <subtopic>> payload string or binary when binary_length set with optional retained
prefix 0 = cmnd using subtopic
prefix 1 = stat using subtopic
prefix 2 = tele using subtopic
prefix 4 = cmnd using subtopic or RESULT
prefix 5 = stat using subtopic or RESULT
prefix 6 = tele using subtopic or RESULT
*/
char romram[64];
2021-06-11 17:14:12 +01:00
snprintf_P(romram, sizeof(romram), ((prefix > 3) && !Settings->flag.mqtt_response) ? S_RSLT_RESULT : subtopic); // SetOption4 - Switch between MQTT RESULT or COMMAND
2020-11-03 15:24:49 +00:00
UpperCase(romram, romram);
prefix &= 3;
2020-11-03 15:24:49 +00:00
char stopic[TOPSZ];
2020-10-30 11:29:48 +00:00
GetTopic_P(stopic, prefix, TasmotaGlobal.mqtt_topic, romram);
2021-05-19 16:21:50 +01:00
MqttPublishPayload(stopic, payload, binary_length, retained);
#if defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_AWS_IOT_LIGHT)
2021-06-11 17:14:12 +01:00
if ((prefix > 0) && (Settings->flag4.awsiot_shadow) && (Mqtt.connected)) { // placeholder for SetOptionXX
// compute the target topic
char *topic = SettingsText(SET_MQTT_TOPIC);
char topic2[strlen(topic)+1]; // save buffer onto stack
strcpy(topic2, topic);
// replace any '/' with '_'
char *s = topic2;
while (*s) {
if ('/' == *s) {
*s = '_';
}
s++;
}
// update topic is "$aws/things/<topic>/shadow/update"
snprintf_P(romram, sizeof(romram), PSTR("$aws/things/%s/shadow/update"), topic2);
2020-01-27 16:38:32 +00:00
// copy buffer
2021-05-19 16:21:50 +01:00
String aws_payload = F("{\"state\":{\"reported\":%s}}");
aws_payload += payload;
MqttClient.publish(romram, aws_payload.c_str(), false);
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram);
yield(); // #3313
}
#endif // USE_MQTT_AWS_IOT
}
2021-05-19 16:21:50 +01:00
void MqttPublishPayloadPrefixTopic_P(uint32_t prefix, const char* subtopic, const char* payload, uint32_t binary_length) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> payload string or binary when binary_length set no retained
MqttPublishPayloadPrefixTopic_P(prefix, subtopic, payload, binary_length, false);
}
void MqttPublishPayloadPrefixTopic_P(uint32_t prefix, const char* subtopic, const char* payload) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> payload string no retained
MqttPublishPayloadPrefixTopic_P(prefix, subtopic, payload, 0, false);
}
2021-06-02 16:56:44 +01:00
void MqttPublishPayloadPrefixTopicRulesProcess_P(uint32_t prefix, const char* subtopic, const char* payload, bool retained) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> payload string with optional retained
// then process rules
MqttPublishPayloadPrefixTopic_P(prefix, subtopic, payload, 0, retained);
XdrvRulesProcess(0, payload);
}
void MqttPublishPayloadPrefixTopicRulesProcess_P(uint32_t prefix, const char* subtopic, const char* payload) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> default ResponseData string no retained
2021-06-02 16:56:44 +01:00
// then process rules
MqttPublishPayloadPrefixTopicRulesProcess_P(prefix, subtopic, payload, false);
}
2021-05-19 16:21:50 +01:00
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retained) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> default ResponseData string with optional retained
MqttPublishPayloadPrefixTopic_P(prefix, subtopic, ResponseData(), 0, retained);
2021-05-19 16:21:50 +01:00
}
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> default ResponseData string no retained
MqttPublishPrefixTopic_P(prefix, subtopic, false);
}
void MqttPublishPrefixTopicRulesProcess_P(uint32_t prefix, const char* subtopic, bool retained) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> default ResponseData string with optional retained
2021-05-19 16:21:50 +01:00
// then process rules
2020-07-20 16:24:51 +01:00
MqttPublishPrefixTopic_P(prefix, subtopic, retained);
XdrvRulesProcess(0);
2019-11-10 16:02:02 +00:00
}
void MqttPublishPrefixTopicRulesProcess_P(uint32_t prefix, const char* subtopic) {
// Publish <prefix>/<device>/<RESULT or <subtopic>> default ResponseData string no retained
2021-05-19 16:21:50 +01:00
// then process rules
2020-07-20 16:24:51 +01:00
MqttPublishPrefixTopicRulesProcess_P(prefix, subtopic, false);
}
void MqttPublishTeleSensor(void) {
// Publish tele/<device>/SENSOR default ResponseData string with optional retained
2021-05-19 16:21:50 +01:00
// then process rules
2021-06-11 17:14:12 +01:00
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
2020-07-20 16:24:51 +01:00
}
void MqttPublishPowerState(uint32_t device) {
char stopic[TOPSZ];
char scommand[33];
2020-10-30 11:29:48 +00:00
if ((device < 1) || (device > TasmotaGlobal.devices_present)) { device = 1; }
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan() && (device > 1)) {
if (GetFanspeed() < MaxFanspeed()) { // 4 occurs when fanspeed is 3 and RC button 2 is pressed
#ifdef USE_DOMOTICZ
DomoticzUpdateFanState(); // RC Button feedback
#endif // USE_DOMOTICZ
snprintf_P(scommand, sizeof(scommand), PSTR(D_CMND_FANSPEED));
2021-06-11 17:14:12 +01:00
GetTopic_P(stopic, STAT, TasmotaGlobal.mqtt_topic, (Settings->flag.mqtt_response) ? scommand : S_RSLT_RESULT); // SetOption4 - Switch between MQTT RESULT or COMMAND
Response_P(S_JSON_COMMAND_NVALUE, scommand, GetFanspeed());
MqttPublish(stopic);
}
} else {
#endif // USE_SONOFF_IFAN
2021-06-11 17:14:12 +01:00
GetPowerDevice(scommand, device, sizeof(scommand), Settings->flag.device_index_enable); // SetOption26 - Switch between POWER or POWER1
GetTopic_P(stopic, STAT, TasmotaGlobal.mqtt_topic, (Settings->flag.mqtt_response) ? scommand : S_RSLT_RESULT); // SetOption4 - Switch between MQTT RESULT or COMMAND
2020-10-28 18:03:39 +00:00
Response_P(S_JSON_COMMAND_SVALUE, scommand, GetStateText(bitRead(TasmotaGlobal.power, device -1)));
MqttPublish(stopic);
2021-06-11 17:14:12 +01:00
if (!Settings->flag4.only_json_message) { // SetOption90 - Disable non-json MQTT response
2020-10-30 11:29:48 +00:00
GetTopic_P(stopic, STAT, TasmotaGlobal.mqtt_topic, scommand);
2020-10-28 18:03:39 +00:00
Response_P(GetStateText(bitRead(TasmotaGlobal.power, device -1)));
2021-06-11 17:14:12 +01:00
MqttPublish(stopic, Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN
}
2021-08-13 16:46:06 +01:00
#ifdef USE_INFLUXDB
InfluxDbPublishPowerState(device);
#endif
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
}
void MqttPublishAllPowerState(void) {
2020-10-30 11:29:48 +00:00
for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) {
MqttPublishPowerState(i);
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) { break; } // Report status of light relay only
#endif // USE_SONOFF_IFAN
}
}
void MqttPublishPowerBlinkState(uint32_t device) {
char scommand[33];
2020-10-30 11:29:48 +00:00
if ((device < 1) || (device > TasmotaGlobal.devices_present)) {
device = 1;
}
Response_P(PSTR("{\"%s\":\"" D_JSON_BLINK " %s\"}"),
2021-06-11 17:14:12 +01:00
GetPowerDevice(scommand, device, sizeof(scommand), Settings->flag.device_index_enable), GetStateText(bitRead(TasmotaGlobal.blink_mask, device -1))); // SetOption26 - Switch between POWER or POWER1
2021-04-07 14:07:05 +01:00
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, S_RSLT_POWER);
}
/*********************************************************************************************/
uint16_t MqttConnectCount(void) {
return Mqtt.connect_count;
}
void MqttDisconnected(int state) {
Mqtt.connected = false;
2021-06-11 17:14:12 +01:00
Mqtt.retry_counter = Settings->mqtt_retry * Mqtt.retry_counter_delay;
if ((Settings->mqtt_retry * Mqtt.retry_counter_delay) < 120) {
Mqtt.retry_counter_delay++;
}
MqttClient.disconnect();
// Check if this solves intermittent MQTT re-connection failures when broker is restarted
EspClient.stop();
2021-09-01 14:41:49 +01:00
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND),
SettingsText(SET_MQTT_HOST), Settings->mqtt_port, state, Mqtt.retry_counter);
2020-10-29 12:58:50 +00:00
TasmotaGlobal.rules_flag.mqtt_disconnected = 1;
}
void MqttConnected(void) {
char stopic[TOPSZ];
if (Mqtt.allowed) {
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECTED));
Mqtt.connected = true;
Mqtt.retry_counter = 0;
2020-11-22 11:26:05 +00:00
Mqtt.retry_counter_delay = 1;
Mqtt.connect_count++;
2020-10-30 11:29:48 +00:00
GetTopic_P(stopic, TELE, TasmotaGlobal.mqtt_topic, S_LWT);
Response_P(PSTR(MQTT_LWT_ONLINE));
MqttPublish(stopic, true);
2021-06-11 17:14:12 +01:00
if (!Settings->flag4.only_json_message) { // SetOption90 - Disable non-json MQTT response
// Satisfy iobroker (#299)
2020-10-30 11:29:48 +00:00
ResponseClear();
MqttPublishPrefixTopic_P(CMND, S_RSLT_POWER);
}
2020-10-30 11:29:48 +00:00
GetTopic_P(stopic, CMND, TasmotaGlobal.mqtt_topic, PSTR("#"));
MqttSubscribe(stopic);
if (strstr_P(SettingsText(SET_MQTT_FULLTOPIC), MQTT_TOKEN_TOPIC) != nullptr) {
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (strlen(SettingsText(real_index +i))) {
GetGroupTopic_P(stopic, PSTR("#"), real_index +i); // SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing/<grouptopic>/# or SetOption75 1: cmnd/<grouptopic>
MqttSubscribe(stopic);
}
}
GetFallbackTopic_P(stopic, PSTR("#"));
MqttSubscribe(stopic);
}
XdrvCall(FUNC_MQTT_SUBSCRIBE);
}
if (Mqtt.initial_connection_state) {
if (ResetReason() != REASON_DEEP_SLEEP_AWAKE) {
char stopic2[TOPSZ];
2021-04-07 14:22:07 +01:00
Response_P(PSTR("{\"Info1\":{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}}"),
2020-10-30 11:29:48 +00:00
ModuleName().c_str(), TasmotaGlobal.version, TasmotaGlobal.image_name, GetFallbackTopic_P(stopic, ""), GetGroupTopic_P(stopic2, "", SET_MQTT_GRP_TOPIC));
2021-06-11 17:14:12 +01:00
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_INFO "1"), Settings->flag5.mqtt_info_retain);
#ifdef USE_WEBSERVER
2021-06-11 17:14:12 +01:00
if (Settings->webserver) {
#if LWIP_IPV6
2021-04-07 14:22:07 +01:00
Response_P(PSTR("{\"Info2\":{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"IPv6Address\":\"%s\"}}"),
2021-06-11 17:14:12 +01:00
(2 == Settings->webserver) ? PSTR(D_ADMIN) : PSTR(D_USER), NetworkHostname(), NetworkAddress().toString().c_str(), WifiGetIPv6().c_str(), Settings->flag5.mqtt_info_retain);
#else
2021-04-07 14:22:07 +01:00
Response_P(PSTR("{\"Info2\":{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}}"),
2021-06-11 17:14:12 +01:00
(2 == Settings->webserver) ? PSTR(D_ADMIN) : PSTR(D_USER), NetworkHostname(), NetworkAddress().toString().c_str(), Settings->flag5.mqtt_info_retain);
#endif // LWIP_IPV6 = 1
2021-06-11 17:14:12 +01:00
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_INFO "2"), Settings->flag5.mqtt_info_retain);
}
#endif // USE_WEBSERVER
2021-04-07 14:22:07 +01:00
Response_P(PSTR("{\"Info3\":{\"" D_JSON_RESTARTREASON "\":"));
if (CrashFlag()) {
CrashDump();
} else {
ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str());
}
2021-04-07 14:22:07 +01:00
ResponseJsonEndEnd();
2021-06-11 17:14:12 +01:00
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_INFO "3"), Settings->flag5.mqtt_info_retain);
}
MqttPublishAllPowerState();
2021-06-11 17:14:12 +01:00
if (Settings->tele_period) {
TasmotaGlobal.tele_period = Settings->tele_period -5; // Enable TelePeriod in 5 seconds
}
2020-10-29 12:58:50 +00:00
TasmotaGlobal.rules_flag.system_boot = 1;
XdrvCall(FUNC_MQTT_INIT);
}
Mqtt.initial_connection_state = 0;
2020-10-30 11:29:48 +00:00
TasmotaGlobal.global_state.mqtt_down = 0;
2021-06-11 17:14:12 +01:00
if (Settings->flag.mqtt_enabled) { // SetOption3 - Enable MQTT
2020-10-29 12:58:50 +00:00
TasmotaGlobal.rules_flag.mqtt_connected = 1;
}
}
void MqttReconnect(void) {
char stopic[TOPSZ];
Mqtt.allowed = Settings->flag.mqtt_enabled && (TasmotaGlobal.restart_flag == 0); // SetOption3 - Enable MQTT, and don't connect if restart in process
if (Mqtt.allowed) {
#if defined(USE_MQTT_AZURE_DPS_SCOPEID) && defined(USE_MQTT_AZURE_DPS_PRESHAREDKEY)
ProvisionAzureDPS();
#endif
#ifdef USE_DISCOVERY
#ifdef MQTT_HOST_DISCOVERY
MqttDiscoverServer();
#endif // MQTT_HOST_DISCOVERY
#endif // USE_DISCOVERY
2021-06-11 17:14:12 +01:00
if (!strlen(SettingsText(SET_MQTT_HOST)) || !Settings->mqtt_port) {
Mqtt.allowed = false;
}
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
// don't enable MQTT for AWS IoT if Private Key or Certificate are not set
2020-09-14 20:48:32 +01:00
if (Mqtt.mqtt_tls) {
if (0 == strlen(SettingsText(SET_MQTT_PWD))) { // we anticipate that an empty password does not make sense with TLS. This avoids failed connections
Mqtt.allowed = false;
}
}
#endif
}
if (!Mqtt.allowed) {
MqttConnected();
return;
}
#ifdef USE_EMULATION
UdpDisconnect();
#endif // USE_EMULATION
Mqtt.connected = false;
2021-06-11 17:14:12 +01:00
Mqtt.retry_counter = Settings->mqtt_retry * Mqtt.retry_counter_delay;
2020-10-30 11:29:48 +00:00
TasmotaGlobal.global_state.mqtt_down = 1;
#ifdef FIRMWARE_MINIMAL
#ifndef USE_MQTT_TLS
// Don't try to connect if MQTT requires TLS but TLS is not supported
2021-06-11 17:14:12 +01:00
if (Settings->flag4.mqtt_tls) {
return;
}
#endif
#endif
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_ATTEMPTING_CONNECTION));
char *mqtt_user = nullptr;
char *mqtt_pwd = nullptr;
if (strlen(SettingsText(SET_MQTT_USER))) {
mqtt_user = SettingsText(SET_MQTT_USER);
}
if (strlen(SettingsText(SET_MQTT_PWD))) {
mqtt_pwd = SettingsText(SET_MQTT_PWD);
}
2020-10-30 11:29:48 +00:00
GetTopic_P(stopic, TELE, TasmotaGlobal.mqtt_topic, S_LWT);
Response_P(S_LWT_OFFLINE);
if (MqttClient.connected()) { MqttClient.disconnect(); }
EspClient.setTimeout(Settings->mqtt_wifi_timeout * 100);
#ifdef USE_MQTT_TLS
2020-09-14 20:48:32 +01:00
if (Mqtt.mqtt_tls) {
tlsClient->stop();
} else {
// EspClient = WiFiClient(); // Wifi Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
MqttClient.setClient(EspClient);
}
#else
// EspClient = WiFiClient(); // Wifi Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
MqttClient.setClient(EspClient);
#endif
if (2 == Mqtt.initial_connection_state) { // Executed once just after power on and wifi is connected
Mqtt.initial_connection_state = 1;
}
MqttClient.setCallback(MqttDataHandler);
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
// re-assign private keys in case it was updated in between
2020-09-14 20:48:32 +01:00
if (Mqtt.mqtt_tls) {
if ((nullptr != AWS_IoT_Private_Key) && (nullptr != AWS_IoT_Client_Certificate)) {
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
AWS_IoT_Private_Key,
0xFFFF /* all usages, don't care */, 0);
}
}
2019-06-05 10:44:52 +01:00
#endif
2021-06-11 17:14:12 +01:00
MqttClient.setServer(SettingsText(SET_MQTT_HOST), Settings->mqtt_port);
uint32_t mqtt_connect_time = millis();
#if defined(USE_MQTT_TLS)
bool allow_all_fingerprints = false;
bool learn_fingerprint1 = false;
bool learn_fingerprint2 = false;
if (Mqtt.mqtt_tls && Settings->flag5.tls_use_fingerprint) {
allow_all_fingerprints = false;
2021-06-11 17:14:12 +01:00
learn_fingerprint1 = is_fingerprint_mono_value(Settings->mqtt_fingerprint[0], 0x00);
learn_fingerprint2 = is_fingerprint_mono_value(Settings->mqtt_fingerprint[1], 0x00);
allow_all_fingerprints |= is_fingerprint_mono_value(Settings->mqtt_fingerprint[0], 0xff);
allow_all_fingerprints |= is_fingerprint_mono_value(Settings->mqtt_fingerprint[1], 0xff);
allow_all_fingerprints |= learn_fingerprint1;
allow_all_fingerprints |= learn_fingerprint2;
2021-06-11 17:14:12 +01:00
tlsClient->setPubKeyFingerprint(Settings->mqtt_fingerprint[0], Settings->mqtt_fingerprint[1], allow_all_fingerprints);
}
#endif
2021-06-11 17:14:12 +01:00
bool lwt_retain = Settings->flag4.mqtt_no_retain ? false : true; // no retained last will if "no_retain"
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
2020-09-14 20:48:32 +01:00
if (Mqtt.mqtt_tls) {
if ((nullptr != AWS_IoT_Private_Key) && (nullptr != AWS_IoT_Client_Certificate)) {
// if private key is there, we remove user/pwd
mqtt_user = nullptr;
mqtt_pwd = nullptr;
}
}
2019-06-05 10:44:52 +01:00
#endif
#ifdef USE_MQTT_AZURE_IOT
String azureMqtt_password = SettingsText(SET_MQTT_PWD);
if (azureMqtt_password.indexOf("SharedAccessSignature") == -1) {
// assuming a PreSharedKey was provided, calculating a SAS Token into azureMqtt_password
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Authenticating with an Azure IoT Hub Token"));
azureMqtt_password = AzurePSKtoToken(SettingsText(SET_MQTT_HOST), SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_PWD));
}
String azureMqtt_userString = String(SettingsText(SET_MQTT_HOST)) + "/" + String(SettingsText(SET_MQTT_CLIENT)); + "/?api-version=2018-06-30";
if (MqttClient.connect(TasmotaGlobal.mqtt_client, azureMqtt_userString.c_str(), azureMqtt_password.c_str(), stopic, 1, lwt_retain, ResponseData(), MQTT_CLEAN_SESSION)) {
#else
if (MqttClient.connect(TasmotaGlobal.mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, lwt_retain, ResponseData(), MQTT_CLEAN_SESSION)) {
#endif // USE_MQTT_AZURE_IOT
#ifdef USE_MQTT_TLS
2020-09-14 20:48:32 +01:00
if (Mqtt.mqtt_tls) {
#ifdef ESP8266
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connected in %d ms, max ThunkStack used %d"),
millis() - mqtt_connect_time, tlsClient->getMaxThunkStackUse());
#elif defined(ESP32)
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connected in %d ms, stack low mark %d"),
millis() - mqtt_connect_time, uxTaskGetStackHighWaterMark(nullptr));
#endif
if (!tlsClient->getMFLNStatus()) {
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "MFLN not supported by TLS server"));
}
2020-07-16 15:46:30 +01:00
if (Settings->flag5.tls_use_fingerprint) { // CA validation
const uint8_t *recv_fingerprint = tlsClient->getRecvPubKeyFingerprint();
// create a printable version of the fingerprint received
char buf_fingerprint[64];
ToHex_P(recv_fingerprint, 20, buf_fingerprint, sizeof(buf_fingerprint), ' ');
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Server fingerprint: %s"), buf_fingerprint);
bool learned = false;
// If the fingerprint slot is marked for update, we'll do so.
// Otherwise, if the fingerprint slot had the magic trust-on-first-use
// value, we will save the current fingerprint there, but only if the other fingerprint slot
// *didn't* match it.
if (recv_fingerprint[20] & 0x1 || (learn_fingerprint1 && 0 != memcmp(recv_fingerprint, Settings->mqtt_fingerprint[1], 20))) {
memcpy(Settings->mqtt_fingerprint[0], recv_fingerprint, 20);
learned = true;
}
// As above, but for the other slot.
if (recv_fingerprint[20] & 0x2 || (learn_fingerprint2 && 0 != memcmp(recv_fingerprint, Settings->mqtt_fingerprint[0], 20))) {
memcpy(Settings->mqtt_fingerprint[1], recv_fingerprint, 20);
learned = true;
}
if (learned) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Fingerprint learned: %s"), buf_fingerprint);
2020-07-16 15:46:30 +01:00
SettingsSaveAll(); // save settings
}
}
}
#endif // USE_MQTT_TLS
MqttConnected();
} else {
#ifdef USE_MQTT_TLS
2020-09-14 20:48:32 +01:00
if (Mqtt.mqtt_tls) {
2021-09-01 14:41:49 +01:00
/*
getLastError codes as documented in lib\lib_ssl\bearssl-esp8266\src\t_bearssl_ssl.h
SSL-level error codes
| Receive Fatal Alert
| | Send Fatal Alert
| | |
0 : 256 : 512 : BR_ERR_OK
1 : 257 : 513 : BR_ERR_BAD_PARAM - caller-provided parameter is incorrect
2 : 258 : 514 : BR_ERR_BAD_STATE - operation requested by the caller cannot be applied with the current context state (e.g. reading data while outgoing data is waiting to be sent)
3 : 259 : 515 : BR_ERR_UNSUPPORTED_VERSION - incoming protocol or record version is unsupported
4 : 260 : 516 : BR_ERR_BAD_VERSION - incoming record version does not match the expected version
5 : 261 : 517 : BR_ERR_BAD_LENGTH - incoming record length is invalid
6 : 262 : 518 : BR_ERR_TOO_LARGE - incoming record is too large to be processed, or buffer is too small for the handshake message to send
7 : 263 : 519 : BR_ERR_BAD_MAC - decryption found an invalid padding, or the record MAC is not correct
8 : 264 : 520 : BR_ERR_NO_RANDOM - no initial entropy was provided, and none can be obtained from the OS
9 : 265 : 521 : BR_ERR_UNKNOWN_TYPE - incoming record type is unknown
10 : 266 : 522 : BR_ERR_UNEXPECTED - incoming record or message has wrong type with regards to the current engine state
12 : 268 : 524 : BR_ERR_BAD_CCS - ChangeCipherSpec message from the peer has invalid contents
13 : 269 : 525 : BR_ERR_BAD_ALERT - alert message from the peer has invalid contents (odd length)
14 : 270 : 526 : BR_ERR_BAD_HANDSHAKE - incoming handshake message decoding failed
15 : 271 : 527 : BR_ERR_OVERSIZED_ID - ServerHello contains a session ID which is larger than 32 bytes
16 : 272 : 528 : BR_ERR_BAD_CIPHER_SUITE - server wants to use a cipher suite that we did not claim to support. This is also reported if we tried to advertise a cipher suite that we do not support
17 : 273 : 529 : BR_ERR_BAD_COMPRESSION - server wants to use a compression that we did not claim to support
18 : 274 : 530 : BR_ERR_BAD_FRAGLEN - server's max fragment length does not match client's
19 : 275 : 531 : BR_ERR_BAD_SECRENEG - secure renegotiation failed
20 : 276 : 532 : BR_ERR_EXTRA_EXTENSION - server sent an extension type that we did not announce, or used the same extension type several times in a single ServerHello
21 : 277 : 533 : BR_ERR_BAD_SNI - invalid Server Name Indication contents (when used by the server, this extension shall be empty)
22 : 278 : 534 : BR_ERR_BAD_HELLO_DONE - invalid ServerHelloDone from the server (length is not 0)
23 : 279 : 535 : BR_ERR_LIMIT_EXCEEDED - internal limit exceeded (e.g. server's public key is too large)
24 : 280 : 536 : BR_ERR_BAD_FINISHED - Finished message from peer does not match the expected value
25 : 281 : 537 : BR_ERR_RESUME_MISMATCH - session resumption attempt with distinct version or cipher suite
26 : 282 : 538 : BR_ERR_INVALID_ALGORITHM - unsupported or invalid algorithm (ECDHE curve, signature algorithm, hash function)
27 : 283 : 539 : BR_ERR_BAD_SIGNATURE - invalid signature (on ServerKeyExchange from server, or in CertificateVerify from client)
28 : 284 : 540 : BR_ERR_WRONG_KEY_USAGE - peer's public key does not have the proper type or is not allowed for requested operation
29 : 285 : 541 : BR_ERR_NO_CLIENT_AUTH - client did not send a certificate upon request, or the client certificate could not be validated
31 : 287 : 543 : BR_ERR_IO - I/O error or premature close on underlying transport stream. This error code is set only by the simplified I/O API ("br_sslio_*")
getLastError codes as documented in lib\lib_ssl\bearssl-esp8266\src\t_bearssl_x509.h
32 : BR_ERR_X509_OK - validation was successful; this is not actually an error
33 : BR_ERR_X509_INVALID_VALUE - invalid value in an ASN.1 structure
34 : BR_ERR_X509_TRUNCATED - truncated certificate
35 : BR_ERR_X509_EMPTY_CHAIN - empty certificate chain (no certificate at all)
36 : BR_ERR_X509_INNER_TRUNC - decoding error: inner element extends beyond outer element size
37 : BR_ERR_X509_BAD_TAG_CLASS - decoding error: unsupported tag class (application or private)
38 : BR_ERR_X509_BAD_TAG_VALUE - decoding error: unsupported tag value
39 : BR_ERR_X509_INDEFINITE_LENGTH - decoding error: indefinite length
40 : BR_ERR_X509_EXTRA_ELEMENT - decoding error: extraneous element
41 : BR_ERR_X509_UNEXPECTED - decoding error: unexpected element
42 : BR_ERR_X509_NOT_CONSTRUCTED - decoding error: expected constructed element, but is primitive
43 : BR_ERR_X509_NOT_PRIMITIVE - decoding error: expected primitive element, but is constructed
44 : BR_ERR_X509_PARTIAL_BYTE - decoding error: BIT STRING length is not multiple of 8
45 : BR_ERR_X509_BAD_BOOLEAN - decoding error: BOOLEAN value has invalid length
46 : BR_ERR_X509_OVERFLOW - decoding error: value is off-limits
47 : BR_ERR_X509_BAD_DN - invalid distinguished name
48 : BR_ERR_X509_BAD_TIME - invalid date/time representation
49 : BR_ERR_X509_UNSUPPORTED - certificate contains unsupported features that cannot be ignored
50 : BR_ERR_X509_LIMIT_EXCEEDED - key or signature size exceeds internal limits
51 : BR_ERR_X509_WRONG_KEY_TYPE - key type does not match that which was expected
52 : BR_ERR_X509_BAD_SIGNATURE - signature is invalid
53 : BR_ERR_X509_TIME_UNKNOWN - validation time is unknown
54 : BR_ERR_X509_EXPIRED - certificate is expired or not yet valid
55 : BR_ERR_X509_DN_MISMATCH - issuer/subject DN mismatch in the chain
56 : BR_ERR_X509_BAD_SERVER_NAME - expected server name was not found in the chain
57 : BR_ERR_X509_CRITICAL_EXTENSION - unknown critical extension in certificate
58 : BR_ERR_X509_NOT_CA - not a CA, or path length constraint violation
59 : BR_ERR_X509_FORBIDDEN_KEY_USAGE - Key Usage extension prohibits intended usage
60 : BR_ERR_X509_WEAK_PUBLIC_KEY - public key found in certificate is too small
62 : BR_ERR_X509_NOT_TRUSTED - chain could not be linked to a trust anchor
getLastError codes as documented in lib\lib_ssl\bearssl-esp8266\src\t_bearssl_ssl.h
10 : 266 : BR_ALERT_UNEXPECTED_MESSAGE
20 : 276 : BR_ALERT_BAD_RECORD_MAC
22 : 278 : BR_ALERT_RECORD_OVERFLOW
30 : 286 : BR_ALERT_DECOMPRESSION_FAILURE
40 : 296 : BR_ALERT_HANDSHAKE_FAILURE
42 : 298 : BR_ALERT_BAD_CERTIFICATE
43 : 299 : BR_ALERT_UNSUPPORTED_CERTIFICATE
44 : 300 : BR_ALERT_CERTIFICATE_REVOKED
45 : 301 : BR_ALERT_CERTIFICATE_EXPIRED
46 : 302 : BR_ALERT_CERTIFICATE_UNKNOWN
47 : 303 : BR_ALERT_ILLEGAL_PARAMETER
48 : 304 : BR_ALERT_UNKNOWN_CA
49 : 305 : BR_ALERT_ACCESS_DENIED
50 : 306 : BR_ALERT_DECODE_ERROR
51 : 307 : BR_ALERT_DECRYPT_ERROR
70 : 326 : BR_ALERT_PROTOCOL_VERSION
71 : 327 : BR_ALERT_INSUFFICIENT_SECURITY
80 : 336 : BR_ALERT_INTERNAL_ERROR
90 : 346 : BR_ALERT_USER_CANCELED
100 : 356 : BR_ALERT_NO_RENEGOTIATION
110 : 366 : BR_ALERT_UNSUPPORTED_EXTENSION
120 : 376 : BR_ALERT_NO_APPLICATION_PROTOCOL
*/
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connection error: %d"), tlsClient->getLastError());
}
2019-06-05 10:44:52 +01:00
#endif
2021-09-01 14:41:49 +01:00
/*
State codes as documented here http://pubsubclient.knolleary.net/api.html#state
-4 : MQTT_CONNECTION_TIMEOUT - the server didn't respond within the keepalive time
-3 : MQTT_CONNECTION_LOST - the network connection was broken
-2 : MQTT_CONNECT_FAILED - the network connection failed
-1 : MQTT_DISCONNECTED - the client is disconnected cleanly
0 : MQTT_CONNECTED - the client is connected
1 : MQTT_CONNECT_BAD_PROTOCOL - the server doesn't support the requested version of MQTT
2 : MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier
3 : MQTT_CONNECT_UNAVAILABLE - the server was unable to accept the connection
4 : MQTT_CONNECT_BAD_CREDENTIALS - the username/password were rejected
5 : MQTT_CONNECT_UNAUTHORIZED - the client was not authorized to connect
*/
MqttDisconnected(MqttClient.state());
}
}
void MqttCheck(void) {
2021-06-11 17:14:12 +01:00
if (Settings->flag.mqtt_enabled) { // SetOption3 - Enable MQTT
if (!MqttIsConnected()) {
2020-10-30 11:29:48 +00:00
TasmotaGlobal.global_state.mqtt_down = 1;
if (!Mqtt.retry_counter) {
MqttReconnect();
} else {
Mqtt.retry_counter--;
}
} else {
2020-10-30 11:29:48 +00:00
TasmotaGlobal.global_state.mqtt_down = 0;
}
} else {
2020-10-30 11:29:48 +00:00
TasmotaGlobal.global_state.mqtt_down = 0;
if (Mqtt.initial_connection_state) {
MqttReconnect();
}
}
}
bool KeyTopicActive(uint32_t key) {
// key = 0 - Button topic
// key = 1 - Switch topic
key &= 1;
char key_topic[TOPSZ];
Format(key_topic, SettingsText(SET_MQTT_BUTTON_TOPIC + key), sizeof(key_topic));
return ((strlen(key_topic) != 0) && strcmp(key_topic, "0"));
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
#if defined(USE_MQTT_TLS)
void CmndMqttFingerprint(void) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
char fingerprint[60];
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(fingerprint))) {
2020-10-21 10:19:59 +01:00
if (SC_DEFAULT == Shortcut()) {
2021-06-11 17:14:12 +01:00
memcpy_P(Settings->mqtt_fingerprint[XdrvMailbox.index -1], (1 == XdrvMailbox.index) ? default_fingerprint1 : default_fingerprint2, sizeof(default_fingerprint1));
2020-10-21 10:19:59 +01:00
} else {
strlcpy(fingerprint, (SC_CLEAR == Shortcut()) ? "" : XdrvMailbox.data, sizeof(fingerprint));
char *p = fingerprint;
for (uint32_t i = 0; i < 20; i++) {
2021-06-11 17:14:12 +01:00
Settings->mqtt_fingerprint[XdrvMailbox.index -1][i] = strtol(p, &p, 16);
2020-10-21 10:19:59 +01:00
}
}
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
2021-06-11 17:14:12 +01:00
ResponseCmndIdxChar(ToHex_P((unsigned char *)Settings->mqtt_fingerprint[XdrvMailbox.index -1], 20, fingerprint, sizeof(fingerprint), ' '));
}
}
#endif
void CmndMqttUser(void) {
if (XdrvMailbox.data_len > 0) {
2021-01-18 20:48:04 +00:00
SettingsUpdateText(SET_MQTT_USER, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? PSTR(MQTT_USER) : XdrvMailbox.data);
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_USER));
}
void CmndMqttPassword(void) {
2021-05-03 11:19:44 +01:00
bool show_asterisk = (2 == XdrvMailbox.index);
if (XdrvMailbox.data_len > 0) {
2021-01-18 20:48:04 +00:00
SettingsUpdateText(SET_MQTT_PWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? PSTR(MQTT_PASS) : XdrvMailbox.data);
2021-05-03 11:19:44 +01:00
if (!show_asterisk) {
ResponseCmndChar(SettingsText(SET_MQTT_PWD));
}
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
} else {
2021-05-03 11:19:44 +01:00
show_asterisk = true;
}
if (show_asterisk) {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
}
void CmndMqttKeepAlive(void) {
if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 100)) {
2021-06-11 17:14:12 +01:00
Settings->mqtt_keepalive = XdrvMailbox.payload;
#ifdef USE_MQTT_NEW_PUBSUBCLIENT
2021-06-11 17:14:12 +01:00
MqttClient.setKeepAlive(Settings->mqtt_keepalive);
#endif
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber(Settings->mqtt_keepalive);
}
void CmndMqttTimeout(void) {
// Set timeout between 1 and 100 seconds
if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 100)) {
2021-06-11 17:14:12 +01:00
Settings->mqtt_socket_timeout = XdrvMailbox.payload;
#ifdef USE_MQTT_NEW_PUBSUBCLIENT
2021-06-11 17:14:12 +01:00
MqttClient.setSocketTimeout(Settings->mqtt_socket_timeout);
#endif
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber(Settings->mqtt_socket_timeout);
}
void CmndMqttWifiTimeout(void) {
// Set timeout between 100 and 20000 mSec
if ((XdrvMailbox.payload >= 100) && (XdrvMailbox.payload <= 20000)) {
Settings->mqtt_wifi_timeout = XdrvMailbox.payload / 100;
EspClient.setTimeout(Settings->mqtt_wifi_timeout * 100);
}
ResponseCmndNumber(Settings->mqtt_wifi_timeout * 100);
}
void CmndMqttlog(void) {
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) {
2021-06-11 17:14:12 +01:00
Settings->mqttlog_level = XdrvMailbox.payload;
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber(Settings->mqttlog_level);
}
void CmndMqttHost(void) {
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_MQTT_HOST, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data);
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_HOST));
}
void CmndMqttPort(void) {
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) {
2021-06-11 17:14:12 +01:00
Settings->mqtt_port = (1 == XdrvMailbox.payload) ? MQTT_PORT : XdrvMailbox.payload;
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber(Settings->mqtt_port);
}
void CmndMqttRetry(void) {
if ((XdrvMailbox.payload >= MQTT_RETRY_SECS) && (XdrvMailbox.payload < 32001)) {
2021-06-11 17:14:12 +01:00
Settings->mqtt_retry = XdrvMailbox.payload;
Mqtt.retry_counter = Settings->mqtt_retry;
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber(Settings->mqtt_retry);
}
void CmndStateText(void) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_STATE_TEXT)) {
if (!XdrvMailbox.usridx) {
ResponseCmndAll(SET_STATE_TXT1, MAX_STATE_TEXT);
} else {
if (XdrvMailbox.data_len > 0) {
for (uint32_t i = 0; i <= XdrvMailbox.data_len; i++) {
if (XdrvMailbox.data[i] == ' ') XdrvMailbox.data[i] = '_';
}
SettingsUpdateText(SET_STATE_TXT1 + XdrvMailbox.index -1, XdrvMailbox.data);
}
ResponseCmndIdxChar(GetStateText(XdrvMailbox.index -1));
}
}
}
void CmndMqttClient(void) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
2021-01-18 20:48:04 +00:00
SettingsUpdateText(SET_MQTT_CLIENT, (SC_DEFAULT == Shortcut()) ? PSTR(MQTT_CLIENT_ID) : XdrvMailbox.data);
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_CLIENT));
}
void CmndFullTopic(void) {
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(1, XdrvMailbox.data);
2020-10-30 11:29:48 +00:00
if (!strcmp(XdrvMailbox.data, TasmotaGlobal.mqtt_client)) { SetShortcutDefault(); }
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_FULLTOPIC : XdrvMailbox.data, sizeof(stemp1));
if (strcmp(stemp1, SettingsText(SET_MQTT_FULLTOPIC))) {
2021-06-11 17:14:12 +01:00
Response_P((Settings->flag.mqtt_offline) ? S_LWT_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, S_LWT, true); // Offline or remove previous retained topic
SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp1);
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
}
ResponseCmndChar(SettingsText(SET_MQTT_FULLTOPIC));
}
void CmndPrefix(void) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_MQTT_PREFIXES)) {
if (!XdrvMailbox.usridx) {
ResponseCmndAll(SET_MQTTPREFIX1, MAX_MQTT_PREFIXES);
} else {
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
SettingsUpdateText(SET_MQTTPREFIX1 + XdrvMailbox.index -1,
2021-01-18 20:48:04 +00:00
(SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index) ? PSTR(SUB_PREFIX) : (2==XdrvMailbox.index) ? PSTR(PUB_PREFIX) : PSTR(PUB_PREFIX2) : XdrvMailbox.data);
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndIdxChar(SettingsText(SET_MQTTPREFIX1 + XdrvMailbox.index -1));
}
}
}
void CmndPublish(void) {
// Allow wildcard character "#" as space replacement in topic (#10258)
// publish cmnd/theo#arends/power 2 ==> publish cmnd/theo arends/power 2
if (XdrvMailbox.data_len > 0) {
2020-01-27 16:38:32 +00:00
char *payload_part;
char *mqtt_part = strtok_r(XdrvMailbox.data, " ", &payload_part);
if (mqtt_part) {
char stemp1[TOPSZ];
strlcpy(stemp1, mqtt_part, sizeof(stemp1));
ReplaceChar(stemp1, '#', ' ');
2020-01-27 16:38:32 +00:00
if ((payload_part != nullptr) && strlen(payload_part)) {
2021-05-23 15:50:17 +01:00
Response_P(payload_part);
} else {
2020-10-30 11:29:48 +00:00
ResponseClear();
}
2020-01-18 14:34:01 +00:00
MqttPublish(stemp1, (XdrvMailbox.index == 2));
2020-10-30 11:29:48 +00:00
ResponseClear();
}
}
}
void CmndGroupTopic(void) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_GROUP_TOPICS)) {
if (XdrvMailbox.data_len > 0) {
uint32_t settings_text_index = (1 == XdrvMailbox.index) ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + XdrvMailbox.index - 2;
MakeValidMqtt(0, XdrvMailbox.data);
2020-10-30 11:29:48 +00:00
if (!strcmp(XdrvMailbox.data, TasmotaGlobal.mqtt_client)) { SetShortcutDefault(); }
2021-01-18 20:48:04 +00:00
SettingsUpdateText(settings_text_index, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? PSTR(MQTT_GRPTOPIC) : XdrvMailbox.data);
// Eliminate duplicates, have at least one and fill from index 1
char stemp[MAX_GROUP_TOPICS][TOPSZ];
uint32_t read_index = 0;
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (strlen(SettingsText(real_index +i))) {
bool not_equal = true;
for (uint32_t j = 0; j < read_index; j++) {
if (!strcmp(SettingsText(real_index +i), stemp[j])) { // Topics are case-sensitive
not_equal = false;
}
}
if (not_equal) {
strncpy(stemp[read_index], SettingsText(real_index +i), sizeof(stemp[read_index]));
read_index++;
}
}
}
if (0 == read_index) {
2021-01-18 20:48:04 +00:00
SettingsUpdateText(SET_MQTT_GRP_TOPIC, PSTR(MQTT_GRPTOPIC));
} else {
uint32_t write_index = 0;
uint32_t real_index = SET_MQTT_GRP_TOPIC;
for (uint32_t i = 0; i < MAX_GROUP_TOPICS; i++) {
if (1 == i) { real_index = SET_MQTT_GRP_TOPIC2 -1; }
if (write_index < read_index) {
SettingsUpdateText(real_index +i, stemp[write_index]);
write_index++;
} else {
SettingsUpdateText(real_index +i, "");
}
}
}
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndAll(SET_MQTT_GRP_TOPIC, MAX_GROUP_TOPICS);
}
}
void CmndTopic(void) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
2020-10-30 11:29:48 +00:00
if (!strcmp(XdrvMailbox.data, TasmotaGlobal.mqtt_client)) { SetShortcutDefault(); }
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_TOPIC : XdrvMailbox.data, sizeof(stemp1));
if (strcmp(stemp1, SettingsText(SET_MQTT_TOPIC))) {
2021-06-11 17:14:12 +01:00
Response_P((Settings->flag.mqtt_offline) ? S_LWT_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, S_LWT, true); // Offline or remove previous retained topic
SettingsUpdateText(SET_MQTT_TOPIC, stemp1);
2020-10-29 11:21:24 +00:00
TasmotaGlobal.restart_flag = 2;
}
}
ResponseCmndChar(SettingsText(SET_MQTT_TOPIC));
}
void CmndButtonTopic(void) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
2020-10-30 11:29:48 +00:00
if (!strcmp(XdrvMailbox.data, TasmotaGlobal.mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
case SC_CLEAR: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, ""); break;
2020-10-30 11:29:48 +00:00
case SC_DEFAULT: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, TasmotaGlobal.mqtt_topic); break;
case SC_USER: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC); break;
default: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, XdrvMailbox.data);
}
}
ResponseCmndChar(SettingsText(SET_MQTT_BUTTON_TOPIC));
}
void CmndSwitchTopic(void) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
2020-10-30 11:29:48 +00:00
if (!strcmp(XdrvMailbox.data, TasmotaGlobal.mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
case SC_CLEAR: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, ""); break;
2020-10-30 11:29:48 +00:00
case SC_DEFAULT: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, TasmotaGlobal.mqtt_topic); break;
2021-01-18 20:48:04 +00:00
case SC_USER: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, PSTR(MQTT_SWITCH_TOPIC)); break;
default: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, XdrvMailbox.data);
}
}
ResponseCmndChar(SettingsText(SET_MQTT_SWITCH_TOPIC));
}
void CmndButtonRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
for (uint32_t i = 1; i <= MAX_KEYS; i++) {
SendKey(KEY_BUTTON, i, CLEAR_RETAIN); // Clear MQTT retain in broker
}
}
2021-06-11 17:14:12 +01:00
Settings->flag.mqtt_button_retain = XdrvMailbox.payload; // CMND_BUTTONRETAIN
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText(Settings->flag.mqtt_button_retain); // CMND_BUTTONRETAIN
}
void CmndSwitchRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
for (uint32_t i = 1; i <= MAX_SWITCHES; i++) {
SendKey(KEY_SWITCH, i, CLEAR_RETAIN); // Clear MQTT retain in broker
}
}
2021-06-11 17:14:12 +01:00
Settings->flag.mqtt_switch_retain = XdrvMailbox.payload; // CMND_SWITCHRETAIN
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText(Settings->flag.mqtt_switch_retain); // CMND_SWITCHRETAIN
}
void CmndPowerRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
char stemp1[TOPSZ];
char scommand[CMDSZ];
2020-10-30 11:29:48 +00:00
for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) { // Clear MQTT retain in broker
2021-06-11 17:14:12 +01:00
GetTopic_P(stemp1, STAT, TasmotaGlobal.mqtt_topic, GetPowerDevice(scommand, i, sizeof(scommand), Settings->flag.device_index_enable)); // SetOption26 - Switch between POWER or POWER1
2020-10-30 11:29:48 +00:00
ResponseClear();
2021-06-11 17:14:12 +01:00
MqttPublish(stemp1, Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN
}
}
2021-06-11 17:14:12 +01:00
Settings->flag.mqtt_power_retain = XdrvMailbox.payload; // CMND_POWERRETAIN
if (Settings->flag.mqtt_power_retain) {
Settings->flag4.only_json_message = 0; // SetOption90 - Disable non-json MQTT response
}
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText(Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN
}
void CmndSensorRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
2020-10-30 11:29:48 +00:00
ResponseClear();
2021-06-11 17:14:12 +01:00
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
}
2021-06-11 17:14:12 +01:00
Settings->flag.mqtt_sensor_retain = XdrvMailbox.payload; // CMND_SENSORRETAIN
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText(Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
}
void CmndInfoRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
2021-06-11 17:14:12 +01:00
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO), Settings->flag5.mqtt_info_retain); // CMND_INFORETAIN
}
2021-06-11 17:14:12 +01:00
Settings->flag5.mqtt_info_retain = XdrvMailbox.payload; // CMND_INFORETAIN
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText(Settings->flag5.mqtt_info_retain); // CMND_INFORETAIN
}
void CmndStateRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
2021-06-11 17:14:12 +01:00
MqttPublishPrefixTopic_P(STAT, PSTR(D_RSLT_STATE), Settings->flag5.mqtt_state_retain); // CMND_STATERETAIN
}
2021-06-11 17:14:12 +01:00
Settings->flag5.mqtt_state_retain = XdrvMailbox.payload; // CMND_STATERETAIN
}
2021-06-11 17:14:12 +01:00
ResponseCmndStateText(Settings->flag5.mqtt_state_retain); // CMND_STATERETAIN
}
/*********************************************************************************************\
* TLS private key and certificate - store into Flash
\*********************************************************************************************/
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
2021-03-30 18:17:03 +01:00
#ifdef ESP32
static uint8_t * tls_spi_start = nullptr;
const static size_t tls_spi_len = 0x0400; // 1kb blocs
const static size_t tls_block_offset = 0x0000; // don't need offset in FS
#else
2021-01-22 10:54:15 +00:00
// const static uint16_t tls_spi_start_sector = EEPROM_LOCATION + 4; // 0xXXFF
// const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000); // 0x40XFF000
const static uint16_t tls_spi_start_sector = 0xFF; // Force last bank of first MB
const static uint8_t* tls_spi_start = (uint8_t*) 0x402FF000; // 0x402FF000
const static size_t tls_spi_len = 0x1000; // 4kb blocs
const static size_t tls_block_offset = 0x0400;
2021-03-30 18:17:03 +01:00
#endif
const static size_t tls_block_len = 0x0400; // 1kb
const static size_t tls_obj_store_offset = tls_block_offset + sizeof(tls_dir_t);
inline void TlsEraseBuffer(uint8_t *buffer) {
memset(buffer + tls_block_offset, 0xFF, tls_block_len);
}
// static data structures for Private Key and Certificate, only the pointer
// to binary data will change to a region in SPI Flash
static br_ec_private_key EC = {
23,
nullptr, 0
};
static br_x509_certificate CHAIN[] = {
{ nullptr, 0 }
};
// load a copy of the tls_dir from flash into ram
// and calculate the appropriate data structures for AWS_IoT_Private_Key and AWS_IoT_Client_Certificate
void loadTlsDir(void) {
2021-03-30 18:17:03 +01:00
#ifdef ESP32
// We load the file in RAM and use it as if it was in Flash. The buffer is never deallocated once we loaded TLS keys
AWS_IoT_Private_Key = nullptr;
AWS_IoT_Client_Certificate = nullptr;
if (TfsFileExists(TASM_FILE_TLSKEY)) {
if (tls_spi_start == nullptr){
tls_spi_start = (uint8_t*) malloc(tls_block_len);
if (tls_spi_start == nullptr) {
return;
}
}
TfsLoadFile(TASM_FILE_TLSKEY, tls_spi_start, tls_block_len);
} else {
return; // file does not exist, do nothing
}
#endif
memcpy_P(&tls_dir, tls_spi_start + tls_block_offset, sizeof(tls_dir));
// calculate the addresses for Key and Cert in Flash
if ((TLS_NAME_SKEY == tls_dir.entry[0].name) && (tls_dir.entry[0].len > 0)) {
EC.x = (unsigned char *)(tls_spi_start + tls_obj_store_offset + tls_dir.entry[0].start);
EC.xlen = tls_dir.entry[0].len;
AWS_IoT_Private_Key = &EC;
} else {
AWS_IoT_Private_Key = nullptr;
}
if ((TLS_NAME_CRT == tls_dir.entry[1].name) && (tls_dir.entry[1].len > 0)) {
CHAIN[0].data = (unsigned char *) (tls_spi_start + tls_obj_store_offset + tls_dir.entry[1].start);
CHAIN[0].data_len = tls_dir.entry[1].len;
AWS_IoT_Client_Certificate = CHAIN;
} else {
AWS_IoT_Client_Certificate = nullptr;
}
//Serial.printf("AWS_IoT_Private_Key = %x, AWS_IoT_Client_Certificate = %x\n", AWS_IoT_Private_Key, AWS_IoT_Client_Certificate);
}
const char ALLOCATE_ERROR[] PROGMEM = "TLSKey " D_JSON_ERROR ": cannot allocate buffer.";
void CmndTlsKey(void) {
#ifdef DEBUG_DUMP_TLS
if (0 == XdrvMailbox.index){
CmndTlsDump();
}
#endif // DEBUG_DUMP_TLS
if ((XdrvMailbox.index >= 1) && (XdrvMailbox.index <= 2)) {
tls_dir_t *tls_dir_write;
if (XdrvMailbox.data_len > 0) { // write new value
// first copy SPI buffer into ram
uint8_t *spi_buffer = (uint8_t*) malloc(tls_spi_len);
if (!spi_buffer) {
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_ERROR, ALLOCATE_ERROR);
return;
}
2021-03-30 18:17:03 +01:00
if (tls_spi_start != nullptr) { // safeguard for ESP32
memcpy_P(spi_buffer, tls_spi_start, tls_spi_len);
} else {
memset(spi_buffer, 0, tls_spi_len); // safeguard for ESP32, removed by compiler for ESP8266
}
2019-12-07 14:01:39 +00:00
// remove any white space from the base64
2020-10-10 16:00:37 +01:00
RemoveSpace(XdrvMailbox.data);
2019-12-07 14:01:39 +00:00
// allocate buffer for decoded base64
uint32_t bin_len = decode_base64_length((unsigned char*)XdrvMailbox.data);
uint8_t *bin_buf = nullptr;
if (bin_len > 0) {
bin_buf = (uint8_t*) malloc(bin_len + 4);
if (!bin_buf) {
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_ERROR, ALLOCATE_ERROR);
free(spi_buffer);
return;
}
}
// decode base64
if (bin_len > 0) {
decode_base64((unsigned char*)XdrvMailbox.data, bin_buf);
}
// address of writable tls_dir in buffer
tls_dir_write = (tls_dir_t*) (spi_buffer + tls_block_offset);
2021-03-30 18:17:03 +01:00
bool save_file = false; // for ESP32, do we need to write file
if (1 == XdrvMailbox.index) {
// Try to write Private key
// Start by erasing all
2021-03-30 18:17:03 +01:00
#ifdef ESP32
if (TfsFileExists(TASM_FILE_TLSKEY)) {
TfsDeleteFile(TASM_FILE_TLSKEY); // delete file
}
#else
TlsEraseBuffer(spi_buffer); // Erase any previously stored data
2021-03-30 18:17:03 +01:00
#endif
if (bin_len > 0) {
if (bin_len != 32) {
// no private key was previously stored, abort
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TLSKey: Certificate must be 32 bytes: %d."), bin_len);
free(spi_buffer);
free(bin_buf);
return;
}
tls_entry_t *entry = &tls_dir_write->entry[0];
entry->name = TLS_NAME_SKEY;
entry->start = 0;
entry->len = bin_len;
memcpy(spi_buffer + tls_obj_store_offset + entry->start, bin_buf, entry->len);
2021-03-30 18:17:03 +01:00
save_file = true;
} else {
// if lenght is zero, simply erase this SPI flash area
}
} else if (2 == XdrvMailbox.index) {
// Try to write Certificate
if (TLS_NAME_SKEY != tls_dir.entry[0].name) {
// no private key was previously stored, abort
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TLSKey: cannot store Cert if no Key previously stored."));
free(spi_buffer);
free(bin_buf);
return;
}
if (bin_len <= 256) {
// Certificate lenght too short
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TLSKey: Certificate length too short: %d."), bin_len);
free(spi_buffer);
free(bin_buf);
return;
}
tls_entry_t *entry = &tls_dir_write->entry[1];
entry->name = TLS_NAME_CRT;
entry->start = (tls_dir_write->entry[0].start + tls_dir_write->entry[0].len + 3) & ~0x03; // align to 4 bytes boundary
entry->len = bin_len;
memcpy(spi_buffer + tls_obj_store_offset + entry->start, bin_buf, entry->len);
2021-03-30 18:17:03 +01:00
save_file = true;
}
2021-03-30 18:17:03 +01:00
#ifdef ESP32
if (save_file) {
TfsSaveFile(TASM_FILE_TLSKEY, spi_buffer, tls_spi_len);
}
#else
if (ESP.flashEraseSector(tls_spi_start_sector)) {
ESP.flashWrite(tls_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE);
}
2021-03-30 18:17:03 +01:00
#endif
free(spi_buffer);
free(bin_buf);
}
loadTlsDir(); // reload into memory any potential change
Response_P(PSTR("{\"%s1\":%d,\"%s2\":%d}"),
XdrvMailbox.command, AWS_IoT_Private_Key ? tls_dir.entry[0].len : -1,
XdrvMailbox.command, AWS_IoT_Client_Certificate ? tls_dir.entry[1].len : -1);
}
}
#ifdef DEBUG_DUMP_TLS
// Dump TLS Flash data - don't activate in production to protect your private keys
uint32_t bswap32(uint32_t x) {
return ((x << 24) & 0xff000000 ) |
((x << 8) & 0x00ff0000 ) |
((x >> 8) & 0x0000ff00 ) |
((x >> 24) & 0x000000ff );
}
void CmndTlsDump(void) {
2021-03-30 18:17:03 +01:00
if (tls_spi_start == nullptr) { return; } // safeguard for ESP32, removed by compiler for ESP8266
uint32_t start = (uint32_t)tls_spi_start + tls_block_offset;
uint32_t end = start + tls_block_len -1;
for (uint32_t pos = start; pos < end; pos += 0x10) {
2020-01-26 13:16:44 +00:00
uint32_t* values = (uint32_t*)(pos);
Serial.printf_P(PSTR("%08x: %08x %08x %08x %08x\n"), pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3]));
}
}
#endif // DEBUG_DUMP_TLS
#endif
/*********************************************************************************************\
* Presentation
\*********************************************************************************************/
#ifdef USE_WEBSERVER
#define WEB_HANDLE_MQTT "mq"
const char S_CONFIGURE_MQTT[] PROGMEM = D_CONFIGURE_MQTT;
const char HTTP_BTN_MENU_MQTT[] PROGMEM =
2019-02-13 15:05:25 +00:00
"<p><form action='" WEB_HANDLE_MQTT "' method='get'><button>" D_CONFIGURE_MQTT "</button></form></p>";
const char HTTP_FORM_MQTT1[] PROGMEM =
2019-02-13 15:05:25 +00:00
"<fieldset><legend><b>&nbsp;" D_MQTT_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='" WEB_HANDLE_MQTT "'>"
"<p><b>" D_HOST "</b> (" MQTT_HOST ")<br><input id='mh' placeholder=\"" MQTT_HOST "\" value=\"%s\"></p>"
"<p><b>" D_PORT "</b> (" STR(MQTT_PORT) ")<br><input id='ml' placeholder='" STR(MQTT_PORT) "' value='%d'></p>"
#ifdef USE_MQTT_TLS
"<p><label><input id='b3' type='checkbox'%s><b>" D_MQTT_TLS_ENABLE "</b></label><br>"
#endif // USE_MQTT_TLS
"<p><b>" D_CLIENT "</b> (%s)<br><input id='mc' placeholder=\"%s\" value=\"%s\"></p>";
const char HTTP_FORM_MQTT2[] PROGMEM =
"<p><b>" D_USER "</b> (" MQTT_USER ")<br><input id='mu' placeholder=\"" MQTT_USER "\" value=\"%s\"></p>"
"<p><label><b>" D_PASSWORD "</b><input type='checkbox' onclick='sp(\"mp\")'></label><br><input id='mp' type='password' minlength='5' placeholder=\"" D_PASSWORD "\" value=\"" D_ASTERISK_PWD "\"></p>"
"<p><b>" D_TOPIC "</b> = %%topic%% (%s)<br><input id='mt' placeholder=\"%s\" value=\"%s\"></p>"
"<p><b>" D_FULL_TOPIC "</b> (%s)<br><input id='mf' placeholder=\"%s\" value=\"%s\"></p>";
2018-11-14 13:32:09 +00:00
void HandleMqttConfiguration(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_MQTT));
2021-01-18 20:48:04 +00:00
if (Webserver->hasArg(F("save"))) {
MqttSaveSettings();
WebRestart(1);
return;
}
char str[TOPSZ];
WSContentStart_P(PSTR(D_CONFIGURE_MQTT));
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_MQTT1,
SettingsText(SET_MQTT_HOST),
2021-06-11 17:14:12 +01:00
Settings->mqtt_port,
#ifdef USE_MQTT_TLS
2021-10-23 13:08:40 +01:00
Mqtt.mqtt_tls ? PSTR(" checked") : "", // SetOption103 - Enable MQTT TLS
#endif // USE_MQTT_TLS
2021-01-18 20:48:04 +00:00
Format(str, PSTR(MQTT_CLIENT_ID), sizeof(str)), PSTR(MQTT_CLIENT_ID), SettingsText(SET_MQTT_CLIENT));
WSContentSend_P(HTTP_FORM_MQTT2,
(!strlen(SettingsText(SET_MQTT_USER))) ? "0" : SettingsText(SET_MQTT_USER),
2021-01-18 20:48:04 +00:00
Format(str, PSTR(MQTT_TOPIC), sizeof(str)), PSTR(MQTT_TOPIC), SettingsText(SET_MQTT_TOPIC),
PSTR(MQTT_FULLTOPIC), PSTR(MQTT_FULLTOPIC), SettingsText(SET_MQTT_FULLTOPIC));
WSContentSend_P(HTTP_FORM_END);
2019-03-11 09:38:41 +00:00
WSContentSpaceButton(BUTTON_CONFIGURATION);
2019-03-16 15:23:41 +00:00
WSContentStop();
}
2021-05-03 11:19:44 +01:00
void MqttSaveSettings(void) {
String cmnd = F(D_CMND_BACKLOG "0 ");
cmnd += AddWebCommand(PSTR(D_CMND_MQTTHOST), PSTR("mh"), PSTR("1"));
cmnd += AddWebCommand(PSTR(D_CMND_MQTTPORT), PSTR("ml"), PSTR("1"));
cmnd += AddWebCommand(PSTR(D_CMND_MQTTCLIENT), PSTR("mc"), PSTR("1"));
cmnd += AddWebCommand(PSTR(D_CMND_MQTTUSER), PSTR("mu"), PSTR("1"));
cmnd += AddWebCommand(PSTR(D_CMND_MQTTPASSWORD "2"), PSTR("mp"), PSTR("\""));
cmnd += AddWebCommand(PSTR(D_CMND_TOPIC), PSTR("mt"), PSTR("1"));
cmnd += AddWebCommand(PSTR(D_CMND_FULLTOPIC), PSTR("mf"), PSTR("1"));
#ifdef USE_MQTT_TLS
2021-10-23 13:08:40 +01:00
cmnd += F(";" D_CMND_SO "103 ");
cmnd += Webserver->hasArg(F("b3")); // SetOption103 - Enable MQTT TLS
#endif
2021-05-03 11:19:44 +01:00
ExecuteWebCommand((char*)cmnd.c_str());
}
#endif // USE_WEBSERVER
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv02(uint8_t function)
{
bool result = false;
2021-06-11 17:14:12 +01:00
if (Settings->flag.mqtt_enabled) { // SetOption3 - Enable MQTT
switch (function) {
case FUNC_EVERY_50_MSECOND: // https://github.com/knolleary/pubsubclient/issues/556
MqttClient.loop();
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_ADD_BUTTON:
WSContentSend_P(HTTP_BTN_MENU_MQTT);
break;
case FUNC_WEB_ADD_HANDLER:
2020-10-20 17:56:18 +01:00
WebServer_on(PSTR("/" WEB_HANDLE_MQTT), HandleMqttConfiguration);
break;
#endif // USE_WEBSERVER
case FUNC_COMMAND:
2021-01-28 08:04:42 +00:00
result = DecodeCommand(kMqttCommands, MqttCommand, kMqttSynonyms);
break;
2021-05-12 16:10:50 +01:00
case FUNC_PRE_INIT:
MqttInit();
break;
}
}
return result;
}