(EthernetLocalIP()) != 0) {
ResponseAppend_P(PSTR(",\"Ethernet\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%_I\"}"),
EthernetHostname(), (uint32_t)EthernetLocalIP());
}
#endif // USE_ETHERNET
ResponseJsonEndEnd();
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_INFO "2"), Settings->flag5.mqtt_info_retain);
}
#endif // USE_WEBSERVER
Response_P(PSTR("{\"Info3\":{\"" D_JSON_RESTARTREASON "\":"));
#ifndef FIRMWARE_MINIMAL
if (CrashFlag()) {
CrashDump();
} else
#endif // FIRMWARE_MINIMAL
{
ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str());
}
ResponseAppend_P(PSTR(",\"" D_JSON_BOOTCOUNT "\":%d}}"), Settings->bootcount +1);
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_INFO "3"), Settings->flag5.mqtt_info_retain);
}
MqttPublishAllPowerState();
if (Settings->tele_period) {
TasmotaGlobal.tele_period = Settings->tele_period -5; // Enable TelePeriod in 5 seconds
}
TasmotaGlobal.rules_flag.system_boot = 1;
XdrvCall(FUNC_MQTT_INIT);
}
Mqtt.initial_connection_state = 0;
TasmotaGlobal.global_state.mqtt_down = 0;
if (Settings->flag.mqtt_enabled) { // SetOption3 - Enable MQTT
TasmotaGlobal.rules_flag.mqtt_connected = 1;
}
}
void MqttReconnect(void) {
if (!strlen(TasmotaGlobal.mqtt_client)) { // Do it here as it needs the MAC address from a possible hosted MCU available after WiFi connection
Format(TasmotaGlobal.mqtt_client, SettingsText(SET_MQTT_CLIENT), sizeof(TasmotaGlobal.mqtt_client));
}
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
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
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;
Mqtt.retry_counter = Settings->mqtt_retry * Mqtt.retry_counter_delay;
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
if (Settings->flag4.mqtt_tls) {
return;
}
#endif
#endif
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_ATTEMPTING_CONNECTION));
MqttDisconnect();
MqttSetClientTimeout();
MqttClient.setCallback(MqttDataHandler);
// Keep using hostname to solve rc -4 issues
IPAddress ip;
if (!WifiHostByName(SettingsText(SET_MQTT_HOST), ip)) {
MqttDisconnected(-5); // MQTT_DNS_DISCONNECTED
return;
}
MqttClient.setServer(ip, Settings->mqtt_port);
if (2 == Mqtt.initial_connection_state) { // Executed once just after power on and wifi is connected
Mqtt.initial_connection_state = 1;
}
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);
}
#ifdef USE_MQTT_TLS
uint32_t mqtt_connect_time = millis();
if (Mqtt.mqtt_tls) {
tlsClient->stop();
tlsClient->setDomainName(SettingsText(SET_MQTT_HOST)); // set domain name for TLS SNI (selection of certificate based on domain name)
} else {
MqttClient.setClient(EspClient);
MqttNonTLSWarning();
}
#if defined(USE_MQTT_CLIENT_CERT)
// re-assign private key in case it was updated in between
if (Mqtt.mqtt_tls) {
if ((nullptr != AWS_IoT_Private_Key) && (nullptr != AWS_IoT_Client_Certificate)) {
#ifdef USE_MQTT_AWS_IOT
// if private key is there, we remove user/pwd
mqtt_user = nullptr;
mqtt_pwd = nullptr;
#endif
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
AWS_IoT_Private_Key,
0xFFFF /* all usages, don't care */, 0);
}
}
#endif // USE_MQTT_CLIENT_CERT
#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";
mqtt_user = (char*)azureMqtt_userString.c_str();
mqtt_pwd = (char*)azureMqtt_password.c_str();
#endif // USE_MQTT_AZURE_IOT
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;
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;
tlsClient->setPubKeyFingerprint(Settings->mqtt_fingerprint[0], Settings->mqtt_fingerprint[1], allow_all_fingerprints);
}
#else // No USE_MQTT_TLS
MqttClient.setClient(EspClient);
MqttNonTLSWarning();
#endif // USE_MQTT_TLS
char stopic[TOPSZ];
GetTopic_P(stopic, TELE, TasmotaGlobal.mqtt_topic, S_LWT);
Response_P(S_LWT_OFFLINE);
if (MqttClient.connect(TasmotaGlobal.mqtt_client,
mqtt_user,
mqtt_pwd,
stopic, // Will topic
1, // Will QoS
Settings->flag4.mqtt_no_retain ? false : true, // No retained last will if "no_retain",
ResponseData(), // Will message
Settings->flag5.mqtt_persistent ? 0 : 1)) { // Clean Session
#ifdef USE_MQTT_TLS
if (Mqtt.mqtt_tls) {
#ifdef ESP8266
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)
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()) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "MFLN not supported by TLS server"));
}
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);
SettingsSaveAll(); // save settings
}
}
}
#endif // USE_MQTT_TLS
MqttConnected();
} else {
#ifdef USE_MQTT_TLS
if (Mqtt.mqtt_tls) {
/*
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
*/
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connection error: %d"), tlsClient->getLastError());
#if defined(ESP32) || (defined(ESP8266) && defined(USE_MQTT_TLS_ECDSA))
if (tlsClient->getLastError() == 296) {
// in this special case of cipher mismatch, we force enable ECDSA
// this would be the case for newer letsencrypt certificates now defaulting
// to EC certificates requiring ECDSA instead of RSA
Settings->flag6.tls_use_ecdsa = true;
tlsClient->setECDSA(Settings->flag6.tls_use_ecdsa);
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS now enabling ECDSA 'SetOption165 1'"), tlsClient->getLastError());
}
#endif // defined(ESP32) || (defined(ESP8266) && defined(USE_MQTT_TLS_ECDSA))
}
#endif
/*
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());
}
#ifdef USE_MQTT_TLS
if (Mqtt.mqtt_tls) {
int32_t cipher_suite = tlsClient->getLastCipherSuite();
if (BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 == cipher_suite) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "TLS cipher suite: %s"), PSTR("ECDHE_RSA_AES_128_GCM_SHA256"));
} else if (BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 == cipher_suite) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "TLS cipher suite: %s"), PSTR("ECDHE_ECDSA_AES_128_GCM_SHA256"));
} else if (0 != cipher_suite) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "TLS cipher suite: 0x%04X"), cipher_suite);
}
}
#endif // USE_MQTT_TLS
}
void MqttCheck(void) {
if (Settings->flag.mqtt_enabled) { // SetOption3 - Enable MQTT
if (!MqttIsConnected()) {
TasmotaGlobal.global_state.mqtt_down = 1;
if (!Mqtt.retry_counter) {
MqttReconnect();
} else {
Mqtt.retry_counter--;
}
} else {
TasmotaGlobal.global_state.mqtt_down = 0;
}
} else {
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))) {
if (SC_DEFAULT == Shortcut()) {
memcpy_P(Settings->mqtt_fingerprint[XdrvMailbox.index -1], (1 == XdrvMailbox.index) ? default_fingerprint1 : default_fingerprint2, sizeof(default_fingerprint1));
} else {
strlcpy(fingerprint, (SC_CLEAR == Shortcut()) ? "" : XdrvMailbox.data, sizeof(fingerprint));
char *p = fingerprint;
for (uint32_t i = 0; i < 20; i++) {
Settings->mqtt_fingerprint[XdrvMailbox.index -1][i] = strtol(p, &p, 16);
}
}
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndIdxChar(ToHex_P((unsigned char *)Settings->mqtt_fingerprint[XdrvMailbox.index -1], 20, fingerprint, sizeof(fingerprint), ' '));
}
}
#endif
void CmndMqttUser(void) {
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_MQTT_USER, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? PSTR(MQTT_USER) : XdrvMailbox.data);
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_USER));
}
void CmndMqttPassword(void) {
bool show_asterisk = (2 == XdrvMailbox.index);
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_MQTT_PWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? PSTR(MQTT_PASS) : XdrvMailbox.data);
if (!show_asterisk) {
ResponseCmndChar(SettingsText(SET_MQTT_PWD));
}
TasmotaGlobal.restart_flag = 2;
} else {
show_asterisk = true;
}
if (show_asterisk) {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
}
void CmndMqttKeepAlive(void) {
if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 100)) {
Settings->mqtt_keepalive = XdrvMailbox.payload;
#ifdef USE_MQTT_NEW_PUBSUBCLIENT
MqttClient.setKeepAlive(Settings->mqtt_keepalive);
#endif
}
ResponseCmndNumber(Settings->mqtt_keepalive);
}
void CmndMqttTimeout(void) {
// Set timeout between 1 and 100 seconds
if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 100)) {
Settings->mqtt_socket_timeout = XdrvMailbox.payload;
#ifdef USE_MQTT_NEW_PUBSUBCLIENT
MqttClient.setSocketTimeout(Settings->mqtt_socket_timeout);
#endif
}
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;
MqttSetClientTimeout();
}
ResponseCmndNumber(Settings->mqtt_wifi_timeout * 100);
}
void CmndMqttlog(void) {
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) {
Settings->mqttlog_level = XdrvMailbox.payload;
}
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);
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_HOST));
}
void CmndMqttPort(void) {
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) {
Settings->mqtt_port = (1 == XdrvMailbox.payload) ? MQTT_PORT : XdrvMailbox.payload;
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndNumber(Settings->mqtt_port);
}
void CmndMqttRetry(void) {
if ((XdrvMailbox.payload >= MQTT_RETRY_SECS) && (XdrvMailbox.payload < 32001)) {
Settings->mqtt_retry = XdrvMailbox.payload;
Mqtt.retry_counter = Settings->mqtt_retry;
}
ResponseCmndNumber(Settings->mqtt_retry);
}
void CmndMqttClient(void) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
SettingsUpdateText(SET_MQTT_CLIENT, (SC_DEFAULT == Shortcut()) ? PSTR(MQTT_CLIENT_ID) : XdrvMailbox.data);
TasmotaGlobal.restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_CLIENT));
}
void CmndFullTopic(void) {
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(1, XdrvMailbox.data);
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_FULLTOPIC : XdrvMailbox.data, sizeof(stemp1));
if (strcmp(stemp1, SettingsText(SET_MQTT_FULLTOPIC))) {
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);
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,
(SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index) ? PSTR(SUB_PREFIX) : (2==XdrvMailbox.index) ? PSTR(PUB_PREFIX) : PSTR(PUB_PREFIX2) : XdrvMailbox.data);
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) {
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, '#', ' ');
if ((payload_part != nullptr) && strlen(payload_part)) {
Response_P(payload_part);
} else {
ResponseClear();
}
#ifndef FIRMWARE_MINIMAL
// Publish3 binary is not enabled in MINIMAL
MqttPublishBinary(stemp1, (XdrvMailbox.index == 2), (XdrvMailbox.index == 3));
#else
MqttPublish(stemp1, (XdrvMailbox.index == 2));
#endif
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);
if (!strcmp(XdrvMailbox.data, TasmotaGlobal.mqtt_topic)) {
AddLog(LOG_LEVEL_INFO, PSTR("MQT: Error: GroupTopic must differ from Topic"));
SetShortcutDefault();
}
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) {
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, "");
}
}
}
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);
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_TOPIC : XdrvMailbox.data, sizeof(stemp1));
if (strcmp(stemp1, SettingsText(SET_MQTT_TOPIC))) {
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);
TasmotaGlobal.restart_flag = 2;
}
}
ResponseCmndChar(SettingsText(SET_MQTT_TOPIC));
}
void CmndButtonTopic(void) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
switch (Shortcut()) {
case SC_CLEAR: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, ""); break;
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);
switch (Shortcut()) {
case SC_CLEAR: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, ""); break;
case SC_DEFAULT: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, TasmotaGlobal.mqtt_topic); break;
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
}
}
Settings->flag.mqtt_button_retain = XdrvMailbox.payload; // CMND_BUTTONRETAIN
}
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
}
}
Settings->flag.mqtt_switch_retain = XdrvMailbox.payload; // CMND_SWITCHRETAIN
}
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];
for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) { // Clear MQTT retain in broker
GetTopic_P(stemp1, STAT, TasmotaGlobal.mqtt_topic, GetPowerDevice(scommand, i, sizeof(scommand), Settings->flag.device_index_enable)); // SetOption26 - Switch between POWER or POWER1
ResponseClear();
MqttPublish(stemp1, true);
}
}
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
}
}
ResponseCmndStateText(Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN
}
void CmndSensorRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), true); // Remove retained SENSOR
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), true); // Remove retained ENERGY
}
Settings->flag.mqtt_sensor_retain = XdrvMailbox.payload; // CMND_SENSORRETAIN
}
ResponseCmndStateText(Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
}
void CmndInfoRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
char stemp1[10];
for (uint32_t i = 1; i <= 3; i++) { // Remove retained INFO1, INFO2 and INFO3
snprintf_P(stemp1, sizeof(stemp1), PSTR(D_RSLT_INFO "%d"), i);
MqttPublishPrefixTopic_P(TELE, stemp1, true);
}
}
Settings->flag5.mqtt_info_retain = XdrvMailbox.payload; // CMND_INFORETAIN
}
ResponseCmndStateText(Settings->flag5.mqtt_info_retain); // CMND_INFORETAIN
}
void CmndStateRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
MqttPublishPrefixTopic_P(STAT, PSTR(D_RSLT_STATE), true); // Remove retained STATE
}
Settings->flag5.mqtt_state_retain = XdrvMailbox.payload; // CMND_STATERETAIN
}
ResponseCmndStateText(Settings->flag5.mqtt_state_retain); // CMND_STATERETAIN
}
void CmndStatusRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
MqttPublishPrefixTopic_P(STAT, PSTR(D_CMND_STATUS), true); // Remove retained STATUS
char stemp1[10];
for (uint32_t i = 0; i <= MAX_STATUS; i++) { // Remove retained STATUS0, STATUS1 .. STATUS13
snprintf_P(stemp1, sizeof(stemp1), PSTR(D_CMND_STATUS "%d"), i);
MqttPublishPrefixTopic_P(STAT, stemp1, true);
}
}
Settings->flag5.mqtt_status_retain = XdrvMailbox.payload; // CMND_STATUSRETAIN
}
ResponseCmndStateText(Settings->flag5.mqtt_status_retain); // CMND_STATUSRETAIN
}
/*********************************************************************************************\
* TLS private key and certificate - store into Flash
\*********************************************************************************************/
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_CLIENT_CERT)
#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
// 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;
#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) {
#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) {
AddLog(LOG_LEVEL_ERROR, ALLOCATE_ERROR);
return;
}
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
}
// remove any white space from the base64
RemoveSpace(XdrvMailbox.data);
// 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) {
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);
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
#ifdef ESP32
if (TfsFileExists(TASM_FILE_TLSKEY)) {
TfsDeleteFile(TASM_FILE_TLSKEY); // delete file
}
#else
TlsEraseBuffer(spi_buffer); // Erase any previously stored data
#endif
if (bin_len > 0) {
if (bin_len != 32) {
// no private key was previously stored, abort
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);
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
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
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);
save_file = true;
}
#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);
}
#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) {
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) {
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 HTTP_FORM_MQTT1[] PROGMEM =
"" D_HOST " (" MQTT_HOST ")
"
"" D_PORT " (" STR(MQTT_PORT) ")
"
#ifdef USE_MQTT_TLS
"
"
#endif // USE_MQTT_TLS
"
" D_CLIENT " (%s)
";
const char HTTP_FORM_MQTT2[] PROGMEM =
"" D_USER " (" MQTT_USER ")
"
"
"
"" D_TOPIC " = %%topic%% (%s)
"
"" D_FULL_TOPIC " (%s)
";
void HandleMqttConfiguration(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_MQTT));
if (Webserver->hasArg(F("save"))) {
MqttSaveSettings();
WebRestart(1);
return;
}
char str[TOPSZ];
WSContentStart_P(PSTR(D_CONFIGURE_MQTT));
WSContentSendStyle();
WSContentSend_P(HTTP_FIELDSET_LEGEND, PSTR(D_MQTT_PARAMETERS));
WSContentSend_P(HTTP_FORM_GET_ACTION, PSTR(WEB_HANDLE_MQTT));
WSContentSend_P(HTTP_FORM_MQTT1,
SettingsTextEscaped(SET_MQTT_HOST).c_str(),
Settings->mqtt_port,
#ifdef USE_MQTT_TLS
Mqtt.mqtt_tls ? PSTR(" checked") : "", // SetOption103 - Enable MQTT TLS
#endif // USE_MQTT_TLS
Format(str, PSTR(MQTT_CLIENT_ID), sizeof(str)), PSTR(MQTT_CLIENT_ID), SettingsTextEscaped(SET_MQTT_CLIENT).c_str());
WSContentSend_P(HTTP_FORM_MQTT2,
(!strlen(SettingsText(SET_MQTT_USER))) ? "0" : SettingsTextEscaped(SET_MQTT_USER).c_str(),
Format(str, PSTR(MQTT_TOPIC), sizeof(str)), PSTR(MQTT_TOPIC), SettingsTextEscaped(SET_MQTT_TOPIC).c_str(),
PSTR(MQTT_FULLTOPIC), PSTR(MQTT_FULLTOPIC), SettingsTextEscaped(SET_MQTT_FULLTOPIC).c_str());
WSContentSend_P(HTTP_FORM_END);
WSContentSpaceButton(BUTTON_CONFIGURATION);
WSContentStop();
}
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
cmnd += F(";" D_CMND_SO "103 ");
cmnd += Webserver->hasArg(F("b3")); // SetOption103 - Enable MQTT TLS
#endif
ExecuteWebCommand((char*)cmnd.c_str());
}
#endif // USE_WEBSERVER
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv02(uint32_t function)
{
bool result = false;
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
#ifndef FIRMWARE_MINIMAL // not needed in minimal/safeboot because of disabled feature and Settings are not saved anyways
case FUNC_WEB_ADD_BUTTON:
WSContentSend_P(HTTP_FORM_BUTTON, PSTR(WEB_HANDLE_MQTT), PSTR(D_CONFIGURE_MQTT));
break;
case FUNC_WEB_ADD_HANDLER:
WebServer_on(PSTR("/" WEB_HANDLE_MQTT), HandleMqttConfiguration);
break;
#ifdef USE_WEB_STATUS_LINE
case FUNC_WEB_STATUS_RIGHT:
if (MqttIsConnected()) {
if (MqttTLSEnabled()) {
WSContentStatusSticker(PSTR(D_MQTT_TLS_ENABLE));
} else {
WSContentStatusSticker(PSTR(D_MQTT));
}
}
break;
#endif // USE_WEB_STATUS_LINE
#endif // not FIRMWARE_MINIMAL
#endif // USE_WEBSERVER
case FUNC_COMMAND:
result = DecodeCommand(kMqttCommands, MqttCommand, kMqttSynonyms);
break;
case FUNC_PRE_INIT:
MqttInit();
break;
case FUNC_ACTIVE:
result = true;
break;
}
}
return result;
}