2018-10-10 21:21:44 +01:00
/*
2019-10-27 10:13:24 +00:00
xdrv_02_mqtt . ino - mqtt support for Tasmota
2018-10-10 21:21:44 +01:00
2019-12-31 13:23:34 +00:00
Copyright ( C ) 2020 Theo Arends
2018-10-10 21:21:44 +01:00
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/>.
*/
2018-11-07 09:30:03 +00:00
# define XDRV_02 2
2018-10-10 21:21:44 +01:00
2019-08-04 18:42:21 +01:00
// #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"
2019-06-10 11:06:03 +01:00
BearSSL : : WiFiClientSecure_light * tlsClient ;
2018-11-20 14:00:24 +00:00
# else
WiFiClient EspClient ; // Wifi Client
# endif
2019-08-11 17:12:18 +01:00
const char kMqttCommands [ ] PROGMEM = " | " // No prefix
2019-07-28 16:14:20 +01:00
# if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
D_CMND_MQTTFINGERPRINT " | "
# endif
D_CMND_MQTTUSER " | " D_CMND_MQTTPASSWORD " | "
2019-08-04 18:42:21 +01:00
# if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
D_CMND_TLSKEY " | "
2019-07-28 16:14:20 +01:00
# endif
D_CMND_MQTTHOST " | " D_CMND_MQTTPORT " | " D_CMND_MQTTRETRY " | " D_CMND_STATETEXT " | " D_CMND_MQTTCLIENT " | "
2019-09-27 17:13:00 +01:00
D_CMND_FULLTOPIC " | " D_CMND_PREFIX " | " D_CMND_GROUPTOPIC " | " D_CMND_TOPIC " | " D_CMND_PUBLISH " | " D_CMND_MQTTLOG " | "
2018-10-10 21:21:44 +01:00
D_CMND_BUTTONTOPIC " | " D_CMND_SWITCHTOPIC " | " D_CMND_BUTTONRETAIN " | " D_CMND_SWITCHRETAIN " | " D_CMND_POWERRETAIN " | " D_CMND_SENSORRETAIN ;
2019-08-01 14:46:12 +01:00
2019-07-28 16:14:20 +01:00
void ( * const MqttCommand [ ] ) ( void ) PROGMEM = {
# if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
& CmndMqttFingerprint ,
# endif
& CmndMqttUser , & CmndMqttPassword ,
2019-08-04 18:42:21 +01:00
# if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
& CmndTlsKey ,
2019-07-28 16:14:20 +01:00
# endif
& CmndMqttHost , & CmndMqttPort , & CmndMqttRetry , & CmndStateText , & CmndMqttClient ,
2019-09-27 17:13:00 +01:00
& CmndFullTopic , & CmndPrefix , & CmndGroupTopic , & CmndTopic , & CmndPublish , & CmndMqttlog ,
2019-07-28 16:14:20 +01:00
& CmndButtonTopic , & CmndSwitchTopic , & CmndButtonRetain , & CmndSwitchRetain , & CmndPowerRetain , & CmndSensorRetain } ;
2018-10-10 21:21:44 +01:00
2019-08-15 12:50:28 +01:00
struct MQTT {
uint16_t connect_count = 0 ; // MQTT re-connect count
uint16_t retry_counter = 1 ; // MQTT connection retry counter
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
} Mqtt ;
2018-10-10 21:21:44 +01:00
2019-06-10 11:06:03 +01:00
# ifdef USE_MQTT_TLS
2019-06-05 10:44:52 +01:00
2019-08-10 12:31:05 +01:00
# ifdef USE_MQTT_AWS_IOT
2019-08-04 18:42:21 +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
2019-08-10 12:31:05 +01:00
# 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 ;
}
2019-06-10 11:06:03 +01:00
# endif // USE_MQTT_TLS
2019-06-05 10:44:52 +01:00
2019-07-28 16:14:20 +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 + + ;
}
}
# ifdef USE_DISCOVERY
# ifdef MQTT_HOST_DISCOVERY
void MqttDiscoverServer ( void )
{
2019-08-17 16:13:09 +01:00
if ( ! Wifi . mdns_begun ) { return ; }
2019-07-28 16:14:20 +01:00
int n = MDNS . queryService ( " mqtt " , " tcp " ) ; // Search for mqtt service
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MDNS D_QUERY_DONE " %d " ) , n ) ;
if ( n > 0 ) {
uint32_t i = 0 ; // If the hostname isn't set, use the first record found.
# ifdef MDNS_HOSTNAME
for ( i = n ; i > 0 ; i - - ) { // Search from last to first and use first if not found
if ( ! strcmp ( MDNS . hostname ( i ) . c_str ( ) , MDNS_HOSTNAME ) ) {
break ; // Stop at matching record
}
}
# endif // MDNS_HOSTNAME
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_HOST , MDNS . IP ( i ) . toString ( ) . c_str ( ) ) ;
2019-07-28 16:14:20 +01:00
Settings . mqtt_port = MDNS . port ( i ) ;
2019-12-16 14:13:57 +00:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MDNS D_MQTT_SERVICE_FOUND " %s, " D_IP_ADDRESS " %s, " D_PORT " %d " ) , MDNS . hostname ( i ) . c_str ( ) , SettingsText ( SET_MQTT_HOST ) , Settings . mqtt_port ) ;
2019-07-28 16:14:20 +01:00
}
}
# endif // MQTT_HOST_DISCOVERY
# endif // USE_DISCOVERY
2018-10-10 21:21:44 +01:00
/*********************************************************************************************\
* MQTT driver specific code need to provide the following functions :
*
* bool MqttIsConnected ( )
* void MqttDisconnect ( )
* void MqttSubscribeLib ( char * topic )
2019-01-28 13:08:33 +00:00
* bool MqttPublishLib ( const char * topic , bool retained )
2018-10-10 21:21:44 +01:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <PubSubClient.h>
// Max message size calculated by PubSubClient is (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength)
# if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MIN_MESSZ // If the max message size is too small, throw an error at compile time. See PubSubClient.cpp line 359
# error "MQTT_MAX_PACKET_SIZE is too small in libraries / PubSubClient / src / PubSubClient.h, increase it to at least 1000"
# endif
2019-06-10 11:06:03 +01:00
# ifdef USE_MQTT_TLS
2019-06-05 10:44:52 +01:00
PubSubClient MqttClient ;
# else
2018-10-10 21:21:44 +01:00
PubSubClient MqttClient ( EspClient ) ;
2019-06-05 10:44:52 +01:00
# endif
2019-07-28 16:14:20 +01:00
void MqttInit ( void )
{
2019-06-10 11:06:03 +01:00
# ifdef USE_MQTT_TLS
tlsClient = new BearSSL : : WiFiClientSecure_light ( 1024 , 1024 ) ;
2019-06-05 10:44:52 +01:00
# ifdef USE_MQTT_AWS_IOT
2019-08-04 18:42:21 +01:00
loadTlsDir ( ) ; // load key and certificate data from Flash
tlsClient - > setClientECCert ( AWS_IoT_Client_Certificate ,
AWS_IoT_Private_Key ,
2019-06-05 10:44:52 +01:00
0xFFFF /* all usages, don't care */ , 0 ) ;
2019-06-10 11:06:03 +01:00
# endif
2019-06-05 10:44:52 +01:00
2019-06-10 18:58:57 +01:00
# ifdef USE_MQTT_TLS_CA_CERT
# ifdef USE_MQTT_AWS_IOT
tlsClient - > setTrustAnchor ( & AmazonRootCA1_TA ) ;
# else
2019-06-10 19:04:34 +01:00
tlsClient - > setTrustAnchor ( & LetsEncryptX3CrossSigned_TA ) ;
2019-06-10 18:58:57 +01:00
# endif // USE_MQTT_AWS_IOT
# endif // USE_MQTT_TLS_CA_CERT
2019-06-10 11:06:03 +01:00
MqttClient . setClient ( * tlsClient ) ;
2019-06-30 17:50:42 +01:00
# endif // USE_MQTT_TLS
2019-06-05 10:44:52 +01:00
}
2018-11-14 13:32:09 +00:00
bool MqttIsConnected ( void )
2018-10-10 21:21:44 +01:00
{
return MqttClient . connected ( ) ;
}
2018-11-14 13:32:09 +00:00
void MqttDisconnect ( void )
2018-10-10 21:21:44 +01:00
{
MqttClient . disconnect ( ) ;
}
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
void MqttSubscribeLib ( const char * topic )
2018-10-10 21:21:44 +01:00
{
MqttClient . subscribe ( topic ) ;
MqttClient . loop ( ) ; // Solve LmacRxBlk:1 messages
}
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
void MqttUnsubscribeLib ( const char * topic )
{
MqttClient . unsubscribe ( topic ) ;
MqttClient . loop ( ) ; // Solve LmacRxBlk:1 messages
}
2019-01-28 13:08:33 +00:00
bool MqttPublishLib ( const char * topic , bool retained )
2018-10-10 21:21:44 +01:00
{
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-01-18 15:57:48 +00:00
mqtt_cmnd_blocked_reset = 4 ; // Allow up to four seconds before resetting residual cmnd blocks
mqtt_cmnd_blocked + + ;
2020-01-18 14:34:01 +00:00
}
}
2018-10-10 21:21:44 +01:00
bool result = MqttClient . publish ( topic , mqtt_data , retained ) ;
yield ( ) ; // #3313
return result ;
}
2019-10-02 15:45:53 +01:00
void MqttDataHandler ( char * mqtt_topic , uint8_t * mqtt_data , unsigned int data_len )
2019-07-26 08:52:14 +01:00
{
# ifdef USE_DEBUG_DRIVER
ShowFreeMem ( PSTR ( " MqttDataHandler " ) ) ;
# endif
// 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
2019-12-16 14:13:57 +00:00
if ( ! strcmp ( SettingsText ( SET_MQTTPREFIX1 ) , SettingsText ( SET_MQTTPREFIX2 ) ) ) {
char * str = strstr ( mqtt_topic , SettingsText ( SET_MQTTPREFIX1 ) ) ;
2020-01-18 15:57:48 +00:00
if ( ( str = = mqtt_topic ) & & mqtt_cmnd_blocked ) {
mqtt_cmnd_blocked - - ;
2019-07-26 08:52:14 +01:00
return ;
}
}
2019-10-02 15:45:53 +01:00
// Save MQTT data ASAP as it's data is discarded by PubSubClient with next publish as used in MQTTlog
char topic [ TOPSZ ] ;
strlcpy ( topic , mqtt_topic , sizeof ( topic ) ) ;
mqtt_data [ data_len ] = 0 ;
char data [ data_len + 1 ] ;
memcpy ( data , mqtt_data , sizeof ( data ) ) ;
2019-07-26 08:52:14 +01:00
2019-10-02 15:45:53 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( D_LOG_MQTT D_RECEIVED_TOPIC " \" %s \" , " D_DATA_SIZE " %d, " D_DATA " \" %s \" " ) , topic , data_len , data ) ;
// if (LOG_LEVEL_DEBUG_MORE <= seriallog_level) { Serial.println(data); }
2019-07-26 08:52:14 +01:00
// MQTT pre-processing
2019-10-02 11:51:37 +01:00
XdrvMailbox . index = strlen ( topic ) ;
XdrvMailbox . data_len = data_len ;
XdrvMailbox . topic = topic ;
XdrvMailbox . data = ( char * ) data ;
if ( XdrvCall ( FUNC_MQTT_DATA ) ) { return ; }
2019-07-26 08:52:14 +01:00
ShowSource ( SRC_MQTT ) ;
CommandHandler ( topic , data , data_len ) ;
}
2018-10-10 21:21:44 +01:00
/*********************************************************************************************/
void MqttRetryCounter ( uint8_t value )
{
2019-08-15 12:50:28 +01:00
Mqtt . retry_counter = value ;
2018-10-10 21:21:44 +01:00
}
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
void MqttSubscribe ( const char * topic )
2018-10-10 21:21:44 +01:00
{
2019-03-08 14:15:42 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_MQTT D_SUBSCRIBE_TO " %s " ) , topic ) ;
2018-10-10 21:21:44 +01:00
MqttSubscribeLib ( 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
void MqttUnsubscribe ( const char * topic )
{
2019-03-08 14:15:42 +00:00
AddLog_P2 ( 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 ) ;
}
2019-09-27 17:13:00 +01:00
void MqttPublishLogging ( const char * mxtime )
{
2020-01-19 13:12:25 +00:00
char saved_mqtt_data [ strlen ( mqtt_data ) + 1 ] ;
memcpy ( saved_mqtt_data , mqtt_data , sizeof ( saved_mqtt_data ) ) ;
2019-09-27 17:13:00 +01:00
2020-01-18 14:34:01 +00:00
// ResponseTime_P(PSTR(",\"Log\":{\"%s\"}}"), log_data); // Will fail as some messages contain JSON
2020-01-19 13:12:25 +00:00
Response_P ( PSTR ( " %s%s " ) , mxtime , log_data ) ; // No JSON and ugly!!
char stopic [ TOPSZ ] ;
GetTopic_P ( stopic , STAT , mqtt_topic , PSTR ( " LOGGING " ) ) ;
MqttPublishLib ( stopic , false ) ;
2020-01-18 14:34:01 +00:00
2020-01-19 13:12:25 +00:00
memcpy ( mqtt_data , saved_mqtt_data , sizeof ( saved_mqtt_data ) ) ;
2019-09-27 17:13:00 +01:00
}
2020-01-18 14:34:01 +00:00
void MqttPublish ( const char * topic , bool retained )
2018-10-10 21:21:44 +01:00
{
2019-03-11 09:38:41 +00:00
# ifdef USE_DEBUG_DRIVER
2020-01-18 14:34:01 +00:00
ShowFreeMem ( PSTR ( " MqttPublish " ) ) ;
# endif
# if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
2020-01-19 13:12:25 +00:00
// if (retained) {
2020-01-18 14:34:01 +00:00
// AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("Retained are not supported by AWS IoT, using retained = false."));
2020-01-19 13:12:25 +00:00
// }
retained = false ; // AWS IoT does not support retained, it will disconnect if received
2019-03-11 09:38:41 +00:00
# endif
2018-10-10 21:21:44 +01:00
2020-01-18 14:34:01 +00:00
char sretained [ CMDSZ ] ;
2018-10-10 21:21:44 +01:00
sretained [ 0 ] = ' \0 ' ;
2020-01-18 14:34:01 +00:00
char slog_type [ 20 ] ;
2018-10-10 21:21:44 +01:00
snprintf_P ( slog_type , sizeof ( slog_type ) , PSTR ( D_LOG_RESULT ) ) ;
2019-11-03 11:33:36 +00:00
if ( Settings . flag . mqtt_enabled ) { // SetOption3 - Enable MQTT
2020-01-19 13:12:25 +00:00
if ( MqttPublishLib ( topic , retained ) ) {
snprintf_P ( slog_type , sizeof ( slog_type ) , PSTR ( D_LOG_MQTT ) ) ;
if ( retained ) {
snprintf_P ( sretained , sizeof ( sretained ) , PSTR ( " ( " D_RETAINED " ) " ) ) ;
2018-10-10 21:21:44 +01:00
}
}
}
2019-11-03 11:33:36 +00:00
snprintf_P ( log_data , sizeof ( log_data ) , PSTR ( " %s%s = %s " ) , slog_type , ( Settings . flag . mqtt_enabled ) ? topic : strrchr ( topic , ' / ' ) + 1 , mqtt_data ) ; // SetOption3 - Enable MQTT
2018-10-10 21:21:44 +01:00
if ( strlen ( log_data ) > = ( sizeof ( log_data ) - strlen ( sretained ) - 1 ) ) {
log_data [ sizeof ( log_data ) - strlen ( sretained ) - 5 ] = ' \0 ' ;
snprintf_P ( log_data , sizeof ( log_data ) , PSTR ( " %s ... " ) , log_data ) ;
}
snprintf_P ( log_data , sizeof ( log_data ) , PSTR ( " %s%s " ) , log_data , sretained ) ;
AddLog ( LOG_LEVEL_INFO ) ;
2019-03-08 14:15:42 +00:00
2018-10-10 21:21:44 +01:00
if ( Settings . ledstate & 0x04 ) {
blinks + + ;
}
}
void MqttPublish ( const char * topic )
{
MqttPublish ( topic , false ) ;
}
2019-07-28 16:14:20 +01:00
void MqttPublishPrefixTopic_P ( uint32_t prefix , const char * subtopic , bool retained )
2018-10-10 21:21:44 +01:00
{
/* 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 [ 33 ] ;
char stopic [ TOPSZ ] ;
2019-11-03 11:33:36 +00:00
snprintf_P ( romram , sizeof ( romram ) , ( ( prefix > 3 ) & & ! Settings . flag . mqtt_response ) ? S_RSLT_RESULT : subtopic ) ; // SetOption4 - Switch between MQTT RESULT or COMMAND
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < strlen ( romram ) ; i + + ) {
2018-10-10 21:21:44 +01:00
romram [ i ] = toupper ( romram [ i ] ) ;
}
prefix & = 3 ;
GetTopic_P ( stopic , prefix , mqtt_topic , romram ) ;
MqttPublish ( stopic , retained ) ;
}
2019-07-28 16:14:20 +01:00
void MqttPublishPrefixTopic_P ( uint32_t prefix , const char * subtopic )
2018-10-10 21:21:44 +01:00
{
MqttPublishPrefixTopic_P ( prefix , subtopic , false ) ;
}
2019-11-10 16:02:02 +00:00
void MqttPublishTeleSensor ( void )
{
MqttPublishPrefixTopic_P ( TELE , PSTR ( D_RSLT_SENSOR ) , Settings . flag . mqtt_sensor_retain ) ; // CMND_SENSORRETAIN
2019-11-10 16:40:37 +00:00
XdrvRulesProcess ( ) ;
2019-11-10 16:02:02 +00:00
}
2019-07-28 16:14:20 +01:00
void MqttPublishPowerState ( uint32_t device )
2018-10-10 21:21:44 +01:00
{
char stopic [ TOPSZ ] ;
char scommand [ 33 ] ;
if ( ( device < 1 ) | | ( device > devices_present ) ) { device = 1 ; }
2019-07-14 21:08:19 +01:00
# ifdef USE_SONOFF_IFAN
2019-07-14 14:23:02 +01:00
if ( IsModuleIfan ( ) & & ( device > 1 ) ) {
if ( GetFanspeed ( ) < MaxFanspeed ( ) ) { // 4 occurs when fanspeed is 3 and RC button 2 is pressed
2018-12-06 14:03:42 +00:00
# ifdef USE_DOMOTICZ
DomoticzUpdateFanState ( ) ; // RC Button feedback
# endif // USE_DOMOTICZ
2018-10-10 21:21:44 +01:00
snprintf_P ( scommand , sizeof ( scommand ) , PSTR ( D_CMND_FANSPEED ) ) ;
2019-11-03 11:33:36 +00:00
GetTopic_P ( stopic , STAT , mqtt_topic , ( Settings . flag . mqtt_response ) ? scommand : S_RSLT_RESULT ) ; // SetOption4 - Switch between MQTT RESULT or COMMAND
2019-03-23 16:00:59 +00:00
Response_P ( S_JSON_COMMAND_NVALUE , scommand , GetFanspeed ( ) ) ;
2018-10-10 21:21:44 +01:00
MqttPublish ( stopic ) ;
}
} else {
2019-07-14 21:08:19 +01:00
# endif // USE_SONOFF_IFAN
2019-11-03 11:33:36 +00:00
GetPowerDevice ( scommand , device , sizeof ( scommand ) , Settings . flag . device_index_enable ) ; // SetOption26 - Switch between POWER or POWER1
GetTopic_P ( stopic , STAT , mqtt_topic , ( Settings . flag . mqtt_response ) ? scommand : S_RSLT_RESULT ) ; // SetOption4 - Switch between MQTT RESULT or COMMAND
2019-03-23 16:00:59 +00:00
Response_P ( S_JSON_COMMAND_SVALUE , scommand , GetStateText ( bitRead ( power , device - 1 ) ) ) ;
2018-10-10 21:21:44 +01:00
MqttPublish ( stopic ) ;
GetTopic_P ( stopic , STAT , mqtt_topic , scommand ) ;
2019-03-23 16:00:59 +00:00
Response_P ( GetStateText ( bitRead ( power , device - 1 ) ) ) ;
2019-11-03 11:33:36 +00:00
MqttPublish ( stopic , Settings . flag . mqtt_power_retain ) ; // CMND_POWERRETAIN
2019-07-14 21:08:19 +01:00
# ifdef USE_SONOFF_IFAN
2018-10-10 21:21:44 +01:00
}
2019-07-14 21:08:19 +01:00
# endif // USE_SONOFF_IFAN
2018-10-10 21:21:44 +01:00
}
2019-11-20 19:53:12 +00:00
void MqttPublishAllPowerState ( void )
2019-04-15 17:12:42 +01:00
{
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 1 ; i < = devices_present ; i + + ) {
2019-04-15 17:12:42 +01:00
MqttPublishPowerState ( i ) ;
2019-07-14 21:08:19 +01:00
# ifdef USE_SONOFF_IFAN
2019-07-14 14:23:02 +01:00
if ( IsModuleIfan ( ) ) { break ; } // Report status of light relay only
2019-07-14 21:08:19 +01:00
# endif // USE_SONOFF_IFAN
2019-04-15 17:12:42 +01:00
}
}
2019-07-28 16:14:20 +01:00
void MqttPublishPowerBlinkState ( uint32_t device )
2018-10-10 21:21:44 +01:00
{
char scommand [ 33 ] ;
if ( ( device < 1 ) | | ( device > devices_present ) ) {
device = 1 ;
}
2019-03-23 16:00:59 +00:00
Response_P ( PSTR ( " { \" %s \" : \" " D_JSON_BLINK " %s \" } " ) ,
2019-11-03 11:33:36 +00:00
GetPowerDevice ( scommand , device , sizeof ( scommand ) , Settings . flag . device_index_enable ) , GetStateText ( bitRead ( blink_mask , device - 1 ) ) ) ; // SetOption26 - Switch between POWER or POWER1
2018-10-10 21:21:44 +01:00
MqttPublishPrefixTopic_P ( RESULT_OR_STAT , S_RSLT_POWER ) ;
}
/*********************************************************************************************/
2019-11-20 19:53:12 +00:00
uint16_t MqttConnectCount ( void )
2019-02-18 16:05:25 +00:00
{
2019-08-15 12:50:28 +01:00
return Mqtt . connect_count ;
2019-02-18 16:05:25 +00:00
}
2018-10-10 21:21:44 +01:00
void MqttDisconnected ( int state )
{
2019-08-15 12:50:28 +01:00
Mqtt . connected = false ;
Mqtt . retry_counter = Settings . mqtt_retry ;
2018-10-10 21:21:44 +01:00
2019-06-07 17:03:34 +01:00
MqttClient . disconnect ( ) ;
2019-12-16 14:13:57 +00:00
AddLog_P2 ( 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 ) ;
2018-10-10 21:21:44 +01:00
rules_flag . mqtt_disconnected = 1 ;
}
2018-11-14 13:32:09 +00:00
void MqttConnected ( void )
2018-10-10 21:21:44 +01:00
{
char stopic [ TOPSZ ] ;
2019-08-15 12:50:28 +01:00
if ( Mqtt . allowed ) {
2018-10-10 21:21:44 +01:00
AddLog_P ( LOG_LEVEL_INFO , S_LOG_MQTT , PSTR ( D_CONNECTED ) ) ;
2019-08-15 12:50:28 +01:00
Mqtt . connected = true ;
Mqtt . retry_counter = 0 ;
Mqtt . connect_count + + ;
2018-10-10 21:21:44 +01:00
GetTopic_P ( stopic , TELE , mqtt_topic , S_LWT ) ;
2019-03-23 16:00:59 +00:00
Response_P ( PSTR ( D_ONLINE ) ) ;
2018-10-10 21:21:44 +01:00
MqttPublish ( stopic , true ) ;
// Satisfy iobroker (#299)
mqtt_data [ 0 ] = ' \0 ' ;
MqttPublishPrefixTopic_P ( CMND , S_RSLT_POWER ) ;
GetTopic_P ( stopic , CMND , mqtt_topic , PSTR ( " # " ) ) ;
MqttSubscribe ( stopic ) ;
2019-12-16 14:13:57 +00:00
if ( strstr_P ( SettingsText ( SET_MQTT_FULLTOPIC ) , MQTT_TOKEN_TOPIC ) ! = nullptr ) {
2019-10-31 16:24:06 +00:00
GetGroupTopic_P ( stopic , PSTR ( " # " ) ) ; // SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing/<grouptopic>/# or SetOption75 1: cmnd/<grouptopic>
2018-10-10 21:21:44 +01:00
MqttSubscribe ( stopic ) ;
2019-10-31 16:24:06 +00:00
GetFallbackTopic_P ( stopic , PSTR ( " # " ) ) ;
2018-10-10 21:21:44 +01:00
MqttSubscribe ( stopic ) ;
}
XdrvCall ( FUNC_MQTT_SUBSCRIBE ) ;
}
2019-08-15 12:50:28 +01:00
if ( Mqtt . initial_connection_state ) {
2019-10-31 16:24:06 +00:00
char stopic2 [ TOPSZ ] ;
2019-03-23 16:00:59 +00:00
Response_P ( PSTR ( " { \" " D_CMND_MODULE " \" : \" %s \" , \" " D_JSON_VERSION " \" : \" %s%s \" , \" " D_JSON_FALLBACKTOPIC " \" : \" %s \" , \" " D_CMND_GROUPTOPIC " \" : \" %s \" } " ) ,
2019-10-31 16:24:06 +00:00
ModuleName ( ) . c_str ( ) , my_version , my_image , GetFallbackTopic_P ( stopic , " " ) , GetGroupTopic_P ( stopic2 , " " ) ) ;
2018-10-10 21:21:44 +01:00
MqttPublishPrefixTopic_P ( TELE , PSTR ( D_RSLT_INFO " 1 " ) ) ;
# ifdef USE_WEBSERVER
if ( Settings . webserver ) {
2019-12-11 14:53:19 +00:00
# if LWIP_IPV6
Response_P ( PSTR ( " { \" " D_JSON_WEBSERVER_MODE " \" : \" %s \" , \" " D_CMND_HOSTNAME " \" : \" %s \" , \" " D_CMND_IPADDRESS " \" : \" %s \" , \" IPv6Address \" : \" %s \" } " ) ,
( 2 = = Settings . webserver ) ? D_ADMIN : D_USER , my_hostname , WiFi . localIP ( ) . toString ( ) . c_str ( ) , WifiGetIPv6 ( ) . c_str ( ) ) ;
# else
2019-03-23 16:00:59 +00:00
Response_P ( PSTR ( " { \" " D_JSON_WEBSERVER_MODE " \" : \" %s \" , \" " D_CMND_HOSTNAME " \" : \" %s \" , \" " D_CMND_IPADDRESS " \" : \" %s \" } " ) ,
2018-10-10 21:21:44 +01:00
( 2 = = Settings . webserver ) ? D_ADMIN : D_USER , my_hostname , WiFi . localIP ( ) . toString ( ) . c_str ( ) ) ;
2019-12-11 14:53:19 +00:00
# endif // LWIP_IPV6 = 1
2018-10-10 21:21:44 +01:00
MqttPublishPrefixTopic_P ( TELE , PSTR ( D_RSLT_INFO " 2 " ) ) ;
}
# endif // USE_WEBSERVER
2019-12-09 14:29:22 +00:00
Response_P ( PSTR ( " { \" " D_JSON_RESTARTREASON " \" : " ) ) ;
2019-12-28 13:54:26 +00:00
if ( CrashFlag ( ) ) {
2019-12-09 14:29:22 +00:00
CrashDump ( ) ;
} else {
ResponseAppend_P ( PSTR ( " \" %s \" " ) , GetResetReason ( ) . c_str ( ) ) ;
}
ResponseJsonEnd ( ) ;
2018-10-10 21:21:44 +01:00
MqttPublishPrefixTopic_P ( TELE , PSTR ( D_RSLT_INFO " 3 " ) ) ;
2019-04-15 17:12:42 +01:00
MqttPublishAllPowerState ( ) ;
2019-11-12 21:30:44 +00:00
if ( Settings . tele_period ) {
tele_period = Settings . tele_period - 5 ; // Enable TelePeriod in 5 seconds
}
2018-10-10 21:21:44 +01:00
rules_flag . system_boot = 1 ;
XdrvCall ( FUNC_MQTT_INIT ) ;
}
2019-08-15 12:50:28 +01:00
Mqtt . initial_connection_state = 0 ;
2018-10-25 13:16:46 +01:00
2018-10-10 21:21:44 +01:00
global_state . mqtt_down = 0 ;
2019-11-03 11:33:36 +00:00
if ( Settings . flag . mqtt_enabled ) { // SetOption3 - Enable MQTT
2018-10-25 13:16:46 +01:00
rules_flag . mqtt_connected = 1 ;
}
2018-10-10 21:21:44 +01:00
}
2018-11-14 13:32:09 +00:00
void MqttReconnect ( void )
2018-10-10 21:21:44 +01:00
{
char stopic [ TOPSZ ] ;
2019-11-03 11:33:36 +00:00
Mqtt . allowed = Settings . flag . mqtt_enabled ; // SetOption3 - Enable MQTT
2019-08-15 12:50:28 +01:00
if ( Mqtt . allowed ) {
2019-01-20 15:57:07 +00:00
# ifdef USE_DISCOVERY
# ifdef MQTT_HOST_DISCOVERY
MqttDiscoverServer ( ) ;
# endif // MQTT_HOST_DISCOVERY
# endif // USE_DISCOVERY
2019-12-16 14:13:57 +00:00
if ( ! strlen ( SettingsText ( SET_MQTT_HOST ) ) | | ! Settings . mqtt_port ) {
2019-08-15 12:50:28 +01:00
Mqtt . allowed = false ;
2019-01-20 15:57:07 +00:00
}
2019-08-04 18:42:21 +01:00
# 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 ( ! AWS_IoT_Private_Key | | ! AWS_IoT_Client_Certificate ) {
2019-08-15 12:50:28 +01:00
Mqtt . allowed = false ;
2019-08-04 18:42:21 +01:00
}
# endif
2019-01-20 15:57:07 +00:00
}
2019-08-15 12:50:28 +01:00
if ( ! Mqtt . allowed ) {
2018-10-10 21:21:44 +01:00
MqttConnected ( ) ;
return ;
}
2019-05-20 14:09:42 +01:00
# ifdef USE_EMULATION
2018-10-10 21:21:44 +01:00
UdpDisconnect ( ) ;
# endif // USE_EMULATION
2019-03-30 15:32:22 +00:00
AddLog_P ( LOG_LEVEL_INFO , S_LOG_MQTT , PSTR ( D_ATTEMPTING_CONNECTION ) ) ;
2018-10-10 21:21:44 +01:00
2019-08-15 12:50:28 +01:00
Mqtt . connected = false ;
Mqtt . retry_counter = Settings . mqtt_retry ;
2018-10-10 21:21:44 +01:00
global_state . mqtt_down = 1 ;
2019-03-26 17:26:50 +00:00
char * mqtt_user = nullptr ;
char * mqtt_pwd = nullptr ;
2019-12-16 14:13:57 +00:00
if ( strlen ( SettingsText ( SET_MQTT_USER ) ) ) {
mqtt_user = SettingsText ( SET_MQTT_USER ) ;
}
if ( strlen ( SettingsText ( SET_MQTT_PWD ) ) ) {
mqtt_pwd = SettingsText ( SET_MQTT_PWD ) ;
}
2018-10-10 21:21:44 +01:00
GetTopic_P ( stopic , TELE , mqtt_topic , S_LWT ) ;
2019-03-23 16:00:59 +00:00
Response_P ( S_OFFLINE ) ;
2018-10-10 21:21:44 +01:00
2019-06-07 17:03:34 +01:00
if ( MqttClient . connected ( ) ) { MqttClient . disconnect ( ) ; }
2018-10-10 21:21:44 +01:00
# ifdef USE_MQTT_TLS
2019-06-10 11:06:03 +01:00
tlsClient - > stop ( ) ;
2018-10-10 21:21:44 +01:00
# else
EspClient = WiFiClient ( ) ; // Wifi Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
2019-06-07 17:03:34 +01:00
MqttClient . setClient ( EspClient ) ;
2018-10-10 21:21:44 +01:00
# endif
2019-08-15 12:50:28 +01:00
if ( 2 = = Mqtt . initial_connection_state ) { // Executed once just after power on and wifi is connected
Mqtt . initial_connection_state = 1 ;
2018-10-10 21:21:44 +01:00
}
2019-07-26 08:52:14 +01:00
MqttClient . setCallback ( MqttDataHandler ) ;
2019-06-30 17:50:42 +01:00
# if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
2019-08-04 18:42:21 +01:00
// re-assign private keys in case it was updated in between
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
2019-12-16 14:13:57 +00:00
MqttClient . setServer ( SettingsText ( SET_MQTT_HOST ) , Settings . mqtt_port ) ;
2019-06-10 11:06:03 +01:00
uint32_t mqtt_connect_time = millis ( ) ;
2019-06-10 18:58:57 +01:00
# if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
2019-06-05 10:44:52 +01:00
bool allow_all_fingerprints = false ;
bool learn_fingerprint1 = is_fingerprint_mono_value ( Settings . mqtt_fingerprint [ 0 ] , 0x00 ) ;
bool 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 ;
2019-06-10 11:06:03 +01:00
tlsClient - > setPubKeyFingerprint ( Settings . mqtt_fingerprint [ 0 ] , Settings . mqtt_fingerprint [ 1 ] , allow_all_fingerprints ) ;
# endif
2019-06-30 17:50:42 +01:00
# if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
2019-12-16 14:13:57 +00:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MQTT " AWS IoT endpoint: %s " ) , SettingsText ( SET_MQTT_HOST ) ) ;
2019-06-10 11:06:03 +01:00
if ( MqttClient . connect ( mqtt_client , nullptr , nullptr , stopic , 1 , false , mqtt_data , MQTT_CLEAN_SESSION ) ) {
2019-06-05 10:44:52 +01:00
# else
2019-06-07 17:03:34 +01:00
if ( MqttClient . connect ( mqtt_client , mqtt_user , mqtt_pwd , stopic , 1 , true , mqtt_data , MQTT_CLEAN_SESSION ) ) {
2019-06-05 10:44:52 +01:00
# endif
2019-06-10 11:06:03 +01:00
# ifdef USE_MQTT_TLS
2019-06-19 08:49:48 +01:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MQTT " TLS connected in %d ms, max ThunkStack used %d " ) ,
millis ( ) - mqtt_connect_time , tlsClient - > getMaxThunkStackUse ( ) ) ;
2019-06-10 11:06:03 +01:00
if ( ! tlsClient - > getMFLNStatus ( ) ) {
AddLog_P ( LOG_LEVEL_INFO , S_LOG_MQTT , PSTR ( " MFLN not supported by TLS server " ) ) ;
}
2019-06-10 18:58:57 +01:00
# ifndef USE_MQTT_TLS_CA_CERT // don't bother with fingerprints if using CA validation
// create a printable version of the fingerprint received
char buf_fingerprint [ 64 ] ;
2019-08-13 18:53:12 +01:00
ToHex_P ( ( unsigned char * ) tlsClient - > getRecvPubKeyFingerprint ( ) , 20 , buf_fingerprint , sizeof ( buf_fingerprint ) , ' ' ) ;
2019-06-10 11:06:03 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_MQTT " Server fingerprint: %s " ) , buf_fingerprint ) ;
2019-06-10 18:58:57 +01:00
2019-06-05 10:44:52 +01:00
if ( learn_fingerprint1 | | learn_fingerprint2 ) {
// we potentially need to learn the fingerprint just seen
bool fingerprint_matched = false ;
2019-06-10 11:06:03 +01:00
const uint8_t * recv_fingerprint = tlsClient - > getRecvPubKeyFingerprint ( ) ;
2019-06-05 10:44:52 +01:00
if ( 0 = = memcmp ( recv_fingerprint , Settings . mqtt_fingerprint [ 0 ] , 20 ) ) {
fingerprint_matched = true ;
}
if ( 0 = = memcmp ( recv_fingerprint , Settings . mqtt_fingerprint [ 1 ] , 20 ) ) {
fingerprint_matched = true ;
}
if ( ! fingerprint_matched ) {
// we had no match, so we need to change all fingerprints ready to learn
if ( learn_fingerprint1 ) {
memcpy ( Settings . mqtt_fingerprint [ 0 ] , recv_fingerprint , 20 ) ;
}
if ( learn_fingerprint2 ) {
memcpy ( Settings . mqtt_fingerprint [ 1 ] , recv_fingerprint , 20 ) ;
}
2019-06-10 11:06:03 +01:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MQTT " Fingerprint learned: %s " ) , buf_fingerprint ) ;
SettingsSaveAll ( ) ; // save settings
2019-06-05 10:44:52 +01:00
}
}
2019-06-10 18:58:57 +01:00
# endif // !USE_MQTT_TLS_CA_CERT
2019-06-10 11:06:03 +01:00
# endif // USE_MQTT_TLS
2018-10-10 21:21:44 +01:00
MqttConnected ( ) ;
} else {
2019-06-10 11:06:03 +01:00
# ifdef USE_MQTT_TLS
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MQTT " TLS connection error: %d " ) , tlsClient - > getLastError ( ) ) ;
2019-06-05 10:44:52 +01:00
# endif
2018-10-10 21:21:44 +01:00
MqttDisconnected ( MqttClient . state ( ) ) ; // status codes are documented here http://pubsubclient.knolleary.net/api.html#state
}
}
2018-11-14 13:32:09 +00:00
void MqttCheck ( void )
2018-10-10 21:21:44 +01:00
{
2019-11-03 11:33:36 +00:00
if ( Settings . flag . mqtt_enabled ) { // SetOption3 - Enable MQTT
2018-10-10 21:21:44 +01:00
if ( ! MqttIsConnected ( ) ) {
global_state . mqtt_down = 1 ;
2019-08-15 12:50:28 +01:00
if ( ! Mqtt . retry_counter ) {
2018-10-28 16:57:25 +00:00
# ifdef USE_DISCOVERY
# ifdef MQTT_HOST_DISCOVERY
2019-12-16 14:13:57 +00:00
if ( ! strlen ( SettingsText ( SET_MQTT_HOST ) ) & & ! Wifi . mdns_begun ) { return ; }
2018-10-28 16:57:25 +00:00
# endif // MQTT_HOST_DISCOVERY
# endif // USE_DISCOVERY
2018-10-10 21:21:44 +01:00
MqttReconnect ( ) ;
} else {
2019-08-15 12:50:28 +01:00
Mqtt . retry_counter - - ;
2018-10-10 21:21:44 +01:00
}
} else {
global_state . mqtt_down = 0 ;
}
} else {
global_state . mqtt_down = 0 ;
2019-08-15 12:50:28 +01:00
if ( Mqtt . initial_connection_state ) MqttReconnect ( ) ;
2018-10-10 21:21:44 +01:00
}
}
2019-07-28 16:14:20 +01:00
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2018-10-10 21:21:44 +01:00
2019-06-10 18:58:57 +01:00
# if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
2019-07-28 16:14:20 +01:00
void CmndMqttFingerprint ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = 2 ) ) {
2018-10-10 21:21:44 +01:00
char fingerprint [ 60 ] ;
2019-07-28 16:14:20 +01:00
if ( ( XdrvMailbox . data_len > 0 ) & & ( XdrvMailbox . data_len < sizeof ( fingerprint ) ) ) {
strlcpy ( fingerprint , ( SC_CLEAR = = Shortcut ( ) ) ? " " : ( SC_DEFAULT = = Shortcut ( ) ) ? ( 1 = = XdrvMailbox . index ) ? MQTT_FINGERPRINT1 : MQTT_FINGERPRINT2 : XdrvMailbox . data , sizeof ( fingerprint ) ) ;
2018-10-10 21:21:44 +01:00
char * p = fingerprint ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < 20 ; i + + ) {
2019-07-28 16:14:20 +01:00
Settings . mqtt_fingerprint [ XdrvMailbox . index - 1 ] [ i ] = strtol ( p , & p , 16 ) ;
2018-10-10 21:21:44 +01:00
}
restart_flag = 2 ;
}
2019-08-13 18:53:12 +01:00
ResponseCmndIdxChar ( ToHex_P ( ( unsigned char * ) Settings . mqtt_fingerprint [ XdrvMailbox . index - 1 ] , 20 , fingerprint , sizeof ( fingerprint ) , ' ' ) ) ;
2018-10-10 21:21:44 +01:00
}
2019-07-28 16:14:20 +01:00
}
2018-10-10 21:21:44 +01:00
# endif
2019-07-28 16:14:20 +01:00
void CmndMqttUser ( void )
{
2019-12-16 14:13:57 +00:00
if ( XdrvMailbox . data_len > 0 ) {
SettingsUpdateText ( SET_MQTT_USER , ( SC_CLEAR = = Shortcut ( ) ) ? " " : ( SC_DEFAULT = = Shortcut ( ) ) ? MQTT_USER : XdrvMailbox . data ) ;
2019-07-28 16:14:20 +01:00
restart_flag = 2 ;
2018-10-10 21:21:44 +01:00
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_USER ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndMqttPassword ( void )
{
2019-12-16 14:13:57 +00:00
if ( XdrvMailbox . data_len > 0 ) {
SettingsUpdateText ( SET_MQTT_PWD , ( SC_CLEAR = = Shortcut ( ) ) ? " " : ( SC_DEFAULT = = Shortcut ( ) ) ? MQTT_PASS : XdrvMailbox . data ) ;
ResponseCmndChar ( SettingsText ( SET_MQTT_PWD ) ) ;
2019-07-28 16:14:20 +01:00
restart_flag = 2 ;
} else {
Response_P ( S_JSON_COMMAND_ASTERISK , XdrvMailbox . command ) ;
2018-10-10 21:21:44 +01:00
}
2019-07-28 16:14:20 +01:00
}
2019-09-27 17:13:00 +01:00
void CmndMqttlog ( void )
{
2019-10-24 07:55:00 +01:00
if ( ( XdrvMailbox . payload > = LOG_LEVEL_NONE ) & & ( XdrvMailbox . payload < = LOG_LEVEL_DEBUG_MORE ) ) {
2019-09-27 17:13:00 +01:00
Settings . mqttlog_level = XdrvMailbox . payload ;
}
ResponseCmndNumber ( Settings . mqttlog_level ) ;
}
2019-07-28 16:14:20 +01:00
void CmndMqttHost ( void )
{
2019-12-16 14:13:57 +00:00
if ( XdrvMailbox . data_len > 0 ) {
SettingsUpdateText ( SET_MQTT_HOST , ( SC_CLEAR = = Shortcut ( ) ) ? " " : ( SC_DEFAULT = = Shortcut ( ) ) ? MQTT_HOST : XdrvMailbox . data ) ;
2019-07-28 16:14:20 +01:00
restart_flag = 2 ;
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_HOST ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndMqttPort ( void )
{
if ( ( XdrvMailbox . payload > 0 ) & & ( XdrvMailbox . payload < 65536 ) ) {
Settings . mqtt_port = ( 1 = = XdrvMailbox . payload ) ? MQTT_PORT : XdrvMailbox . payload ;
restart_flag = 2 ;
}
2019-08-03 12:01:34 +01:00
ResponseCmndNumber ( Settings . mqtt_port ) ;
2019-07-28 16:14:20 +01:00
}
void CmndMqttRetry ( void )
{
if ( ( XdrvMailbox . payload > = MQTT_RETRY_SECS ) & & ( XdrvMailbox . payload < 32001 ) ) {
Settings . mqtt_retry = XdrvMailbox . payload ;
2019-08-15 12:50:28 +01:00
Mqtt . retry_counter = Settings . mqtt_retry ;
2019-07-28 16:14:20 +01:00
}
2019-08-03 12:01:34 +01:00
ResponseCmndNumber ( Settings . mqtt_retry ) ;
2019-07-28 16:14:20 +01:00
}
void CmndStateText ( void )
{
2020-01-12 12:10:21 +00:00
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 ) ;
2018-10-10 21:21:44 +01:00
}
2020-01-12 12:10:21 +00:00
ResponseCmndIdxChar ( GetStateText ( XdrvMailbox . index - 1 ) ) ;
2019-07-28 16:14:20 +01:00
}
}
}
void CmndMqttClient ( void )
{
2019-12-16 14:13:57 +00:00
if ( ! XdrvMailbox . grpflg & & ( XdrvMailbox . data_len > 0 ) ) {
SettingsUpdateText ( SET_MQTT_CLIENT , ( SC_DEFAULT = = Shortcut ( ) ) ? MQTT_CLIENT_ID : XdrvMailbox . data ) ;
2019-07-28 16:14:20 +01:00
restart_flag = 2 ;
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_CLIENT ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndFullTopic ( void )
{
2019-12-16 14:13:57 +00:00
if ( XdrvMailbox . data_len > 0 ) {
2019-07-28 16:14:20 +01:00
MakeValidMqtt ( 1 , XdrvMailbox . data ) ;
if ( ! strcmp ( XdrvMailbox . data , mqtt_client ) ) { SetShortcutDefault ( ) ; }
char stemp1 [ TOPSZ ] ;
strlcpy ( stemp1 , ( SC_DEFAULT = = Shortcut ( ) ) ? MQTT_FULLTOPIC : XdrvMailbox . data , sizeof ( stemp1 ) ) ;
2019-12-16 14:13:57 +00:00
if ( strcmp ( stemp1 , SettingsText ( SET_MQTT_FULLTOPIC ) ) ) {
2019-11-03 11:33:36 +00:00
Response_P ( ( Settings . flag . mqtt_offline ) ? S_OFFLINE : " " ) ; // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P ( TELE , PSTR ( D_LWT ) , true ) ; // Offline or remove previous retained topic
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_FULLTOPIC , stemp1 ) ;
2019-07-28 16:14:20 +01:00
restart_flag = 2 ;
2018-10-10 21:21:44 +01:00
}
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_FULLTOPIC ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndPrefix ( void )
{
2020-01-12 12:10:21 +00:00
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 ) ? SUB_PREFIX : ( 2 = = XdrvMailbox . index ) ? PUB_PREFIX : PUB_PREFIX2 : XdrvMailbox . data ) ;
restart_flag = 2 ;
}
ResponseCmndIdxChar ( SettingsText ( SET_MQTTPREFIX1 + XdrvMailbox . index - 1 ) ) ;
2018-10-10 21:21:44 +01:00
}
}
2019-07-28 16:14:20 +01:00
}
void CmndPublish ( void )
{
if ( XdrvMailbox . data_len > 0 ) {
char * mqtt_part = strtok ( XdrvMailbox . data , " " ) ;
if ( mqtt_part ) {
char stemp1 [ TOPSZ ] ;
strlcpy ( stemp1 , mqtt_part , sizeof ( stemp1 ) ) ;
mqtt_part = strtok ( nullptr , " " ) ;
2018-10-10 21:21:44 +01:00
if ( mqtt_part ) {
2019-07-28 16:14:20 +01:00
strlcpy ( mqtt_data , mqtt_part , sizeof ( mqtt_data ) ) ;
} else {
2018-10-10 21:21:44 +01:00
mqtt_data [ 0 ] = ' \0 ' ;
}
2020-01-18 14:34:01 +00:00
MqttPublish ( stemp1 , ( XdrvMailbox . index = = 2 ) ) ;
2019-08-03 12:01:34 +01:00
// ResponseCmndDone();
2019-07-28 16:14:20 +01:00
mqtt_data [ 0 ] = ' \0 ' ;
2018-10-10 21:21:44 +01:00
}
}
2019-07-28 16:14:20 +01:00
}
void CmndGroupTopic ( void )
{
2019-12-16 14:13:57 +00:00
if ( XdrvMailbox . data_len > 0 ) {
2019-07-28 16:14:20 +01:00
MakeValidMqtt ( 0 , XdrvMailbox . data ) ;
if ( ! strcmp ( XdrvMailbox . data , mqtt_client ) ) { SetShortcutDefault ( ) ; }
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_GRP_TOPIC , ( SC_DEFAULT = = Shortcut ( ) ) ? MQTT_GRPTOPIC : XdrvMailbox . data ) ;
2019-07-28 16:14:20 +01:00
restart_flag = 2 ;
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_GRP_TOPIC ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndTopic ( void )
{
2019-12-16 14:13:57 +00:00
if ( ! XdrvMailbox . grpflg & & ( XdrvMailbox . data_len > 0 ) ) {
2019-07-28 16:14:20 +01:00
MakeValidMqtt ( 0 , XdrvMailbox . data ) ;
if ( ! strcmp ( XdrvMailbox . data , mqtt_client ) ) { SetShortcutDefault ( ) ; }
char stemp1 [ TOPSZ ] ;
strlcpy ( stemp1 , ( SC_DEFAULT = = Shortcut ( ) ) ? MQTT_TOPIC : XdrvMailbox . data , sizeof ( stemp1 ) ) ;
2019-12-16 14:13:57 +00:00
if ( strcmp ( stemp1 , SettingsText ( SET_MQTT_TOPIC ) ) ) {
2019-11-03 11:33:36 +00:00
Response_P ( ( Settings . flag . mqtt_offline ) ? S_OFFLINE : " " ) ; // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P ( TELE , PSTR ( D_LWT ) , true ) ; // Offline or remove previous retained topic
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_TOPIC , stemp1 ) ;
2018-10-10 21:21:44 +01:00
restart_flag = 2 ;
}
2019-07-28 16:14:20 +01:00
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_TOPIC ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndButtonTopic ( void )
{
2019-12-16 14:13:57 +00:00
if ( ! XdrvMailbox . grpflg & & ( XdrvMailbox . data_len > 0 ) ) {
2019-07-28 16:14:20 +01:00
MakeValidMqtt ( 0 , XdrvMailbox . data ) ;
if ( ! strcmp ( XdrvMailbox . data , mqtt_client ) ) { SetShortcutDefault ( ) ; }
switch ( Shortcut ( ) ) {
2019-12-16 14:13:57 +00:00
case SC_CLEAR : SettingsUpdateText ( SET_MQTT_BUTTON_TOPIC , " " ) ; break ;
case SC_DEFAULT : SettingsUpdateText ( SET_MQTT_BUTTON_TOPIC , mqtt_topic ) ; break ;
case SC_USER : SettingsUpdateText ( SET_MQTT_BUTTON_TOPIC , MQTT_BUTTON_TOPIC ) ; break ;
default : SettingsUpdateText ( SET_MQTT_BUTTON_TOPIC , XdrvMailbox . data ) ;
2018-10-10 21:21:44 +01:00
}
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_BUTTON_TOPIC ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndSwitchTopic ( void )
{
2019-12-16 14:13:57 +00:00
if ( ! XdrvMailbox . grpflg & & ( XdrvMailbox . data_len > 0 ) ) {
2019-07-28 16:14:20 +01:00
MakeValidMqtt ( 0 , XdrvMailbox . data ) ;
if ( ! strcmp ( XdrvMailbox . data , mqtt_client ) ) { SetShortcutDefault ( ) ; }
switch ( Shortcut ( ) ) {
2019-12-16 14:13:57 +00:00
case SC_CLEAR : SettingsUpdateText ( SET_MQTT_SWITCH_TOPIC , " " ) ; break ;
case SC_DEFAULT : SettingsUpdateText ( SET_MQTT_SWITCH_TOPIC , mqtt_topic ) ; break ;
case SC_USER : SettingsUpdateText ( SET_MQTT_SWITCH_TOPIC , MQTT_SWITCH_TOPIC ) ; break ;
default : SettingsUpdateText ( SET_MQTT_SWITCH_TOPIC , XdrvMailbox . data ) ;
2018-10-10 21:21:44 +01:00
}
}
2019-12-16 14:13:57 +00:00
ResponseCmndChar ( SettingsText ( SET_MQTT_SWITCH_TOPIC ) ) ;
2019-07-28 16:14:20 +01:00
}
void CmndButtonRetain ( void )
{
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 1 ) ) {
if ( ! XdrvMailbox . payload ) {
for ( uint32_t i = 1 ; i < = MAX_KEYS ; i + + ) {
2019-09-04 11:20:04 +01:00
SendKey ( KEY_BUTTON , i , CLEAR_RETAIN ) ; // Clear MQTT retain in broker
2018-10-10 21:21:44 +01:00
}
}
2019-11-03 11:33:36 +00:00
Settings . flag . mqtt_button_retain = XdrvMailbox . payload ; // CMND_BUTTONRETAIN
2019-07-28 16:14:20 +01:00
}
2019-11-03 11:33:36 +00:00
ResponseCmndStateText ( Settings . flag . mqtt_button_retain ) ; // CMND_BUTTONRETAIN
2019-07-28 16:14:20 +01:00
}
void CmndSwitchRetain ( void )
{
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 1 ) ) {
if ( ! XdrvMailbox . payload ) {
for ( uint32_t i = 1 ; i < = MAX_SWITCHES ; i + + ) {
2019-09-04 11:20:04 +01:00
SendKey ( KEY_SWITCH , i , CLEAR_RETAIN ) ; // Clear MQTT retain in broker
2018-10-10 21:21:44 +01:00
}
}
2019-11-03 11:33:36 +00:00
Settings . flag . mqtt_switch_retain = XdrvMailbox . payload ; // CMND_SWITCHRETAIN
2018-10-10 21:21:44 +01:00
}
2019-11-03 11:33:36 +00:00
ResponseCmndStateText ( Settings . flag . mqtt_switch_retain ) ; // CMND_SWITCHRETAIN
2019-07-28 16:14:20 +01:00
}
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 < = devices_present ; i + + ) { // Clear MQTT retain in broker
2019-11-03 11:33:36 +00:00
GetTopic_P ( stemp1 , STAT , mqtt_topic , GetPowerDevice ( scommand , i , sizeof ( scommand ) , Settings . flag . device_index_enable ) ) ; // SetOption26 - Switch between POWER or POWER1
2018-10-10 21:21:44 +01:00
mqtt_data [ 0 ] = ' \0 ' ;
2019-11-03 11:33:36 +00:00
MqttPublish ( stemp1 , Settings . flag . mqtt_power_retain ) ; // CMND_POWERRETAIN
2018-10-10 21:21:44 +01:00
}
}
2019-11-03 11:33:36 +00:00
Settings . flag . mqtt_power_retain = XdrvMailbox . payload ; // CMND_POWERRETAIN
2018-10-10 21:21:44 +01:00
}
2019-11-03 11:33:36 +00:00
ResponseCmndStateText ( Settings . flag . mqtt_power_retain ) ; // CMND_POWERRETAIN
2019-07-28 16:14:20 +01:00
}
2018-10-10 21:21:44 +01:00
2019-07-28 16:14:20 +01:00
void CmndSensorRetain ( void )
{
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 1 ) ) {
if ( ! XdrvMailbox . payload ) {
mqtt_data [ 0 ] = ' \0 ' ;
2019-11-03 11:33:36 +00: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
2019-07-28 16:14:20 +01:00
}
2019-11-03 11:33:36 +00:00
Settings . flag . mqtt_sensor_retain = XdrvMailbox . payload ; // CMND_SENSORRETAIN
2019-07-28 16:14:20 +01:00
}
2019-11-03 11:33:36 +00:00
ResponseCmndStateText ( Settings . flag . mqtt_sensor_retain ) ; // CMND_SENSORRETAIN
2018-10-10 21:21:44 +01:00
}
2019-08-04 18:42:21 +01:00
/*********************************************************************************************\
* TLS private key and certificate - store into Flash
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
2020-01-17 23:02:01 +00:00
// const static uint16_t tls_spi_start_sector = SPIFFS_END + 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
2019-08-04 18:42:21 +01:00
const static size_t tls_spi_len = 0x1000 ; // 4kb blocs
const static size_t tls_block_offset = 0x0400 ;
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 ) {
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_P ( LOG_LEVEL_ERROR , ALLOCATE_ERROR ) ;
return ;
}
memcpy_P ( spi_buffer , tls_spi_start , tls_spi_len ) ;
2019-12-07 14:01:39 +00:00
// remove any white space from the base64
2019-12-09 20:43:30 +00:00
RemoveAllSpaces ( XdrvMailbox . data ) ;
2019-12-07 14:01:39 +00:00
2019-08-04 18:42:21 +01: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 ) {
AddLog_P ( 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 ) ;
if ( 1 = = XdrvMailbox . index ) {
// Try to write Private key
// Start by erasing all
TlsEraseBuffer ( spi_buffer ) ; // Erase any previously stored data
if ( bin_len > 0 ) {
if ( bin_len ! = 32 ) {
// no private key was previously stored, abort
AddLog_P2 ( 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 ) ;
} 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_P ( 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_P2 ( 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 ) ;
}
2019-11-21 10:08:30 +00:00
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 ) ;
}
2019-08-04 18:42:21 +01:00
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 ) {
2019-08-05 11:15:15 +01:00
uint32_t start = ( uint32_t ) tls_spi_start + tls_block_offset ;
uint32_t end = start + tls_block_len - 1 ;
2019-08-04 18:42:21 +01:00
for ( uint32_t pos = start ; pos < end ; pos + = 0x10 ) {
uint32_t * values = ( uint32_t * ) ( pos ) ;
2019-08-05 11:15:15 +01:00
Serial . printf_P ( PSTR ( " %08x: %08x %08x %08x %08x \n " ) , pos , bswap32 ( values [ 0 ] ) , bswap32 ( values [ 1 ] ) , bswap32 ( values [ 2 ] ) , bswap32 ( values [ 3 ] ) ) ;
2019-08-04 18:42:21 +01:00
}
}
# endif // DEBUG_DUMP_TLS
# endif
2018-10-10 21:21:44 +01:00
/*********************************************************************************************\
* 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> " ;
2018-10-10 21:21:44 +01:00
2019-03-04 17:16:07 +00:00
const char HTTP_FORM_MQTT1 [ ] PROGMEM =
2019-02-13 15:05:25 +00:00
" <fieldset><legend><b> " D_MQTT_PARAMETERS " </b></legend> "
" <form method='get' action=' " WEB_HANDLE_MQTT " '> "
2019-05-31 17:24:56 +01:00
" <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> "
" <p><b> " D_CLIENT " </b> (%s)<br><input id='mc' placeholder='%s' value='%s'></p> " ;
2019-03-04 17:16:07 +00:00
const char HTTP_FORM_MQTT2 [ ] PROGMEM =
2019-05-31 17:24:56 +01:00
" <p><b> " D_USER " </b> ( " MQTT_USER " )<br><input id='mu' placeholder=' " MQTT_USER " ' value='%s'></p> "
2019-06-11 13:30:07 +01:00
" <p><b> " D_PASSWORD " </b><input type='checkbox' onclick='sp( \" mp \" )'><br><input id='mp' type='password' placeholder=' " D_PASSWORD " ' value=' " D_ASTERISK_PWD " '></p> "
2019-05-31 17:24:56 +01:00
" <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-10-10 21:21:44 +01:00
2018-11-14 13:32:09 +00:00
void HandleMqttConfiguration ( void )
2018-10-10 21:21:44 +01:00
{
2019-01-10 11:57:42 +00:00
if ( ! HttpCheckPriviledgedAccess ( ) ) { return ; }
2018-10-10 21:21:44 +01:00
AddLog_P ( LOG_LEVEL_DEBUG , S_LOG_HTTP , S_CONFIGURE_MQTT ) ;
if ( WebServer - > hasArg ( " save " ) ) {
MqttSaveSettings ( ) ;
2018-10-12 10:42:52 +01:00
WebRestart ( 1 ) ;
2018-10-10 21:21:44 +01:00
return ;
}
2019-12-22 14:23:52 +00:00
char str [ TOPSZ ] ;
2019-03-04 17:16:07 +00:00
2019-03-10 14:36:34 +00:00
WSContentStart_P ( S_CONFIGURE_MQTT ) ;
2019-03-04 17:16:07 +00:00
WSContentSendStyle ( ) ;
WSContentSend_P ( HTTP_FORM_MQTT1 ,
2019-12-16 14:13:57 +00:00
SettingsText ( SET_MQTT_HOST ) ,
2019-03-04 17:16:07 +00:00
Settings . mqtt_port ,
2019-12-16 14:13:57 +00:00
Format ( str , MQTT_CLIENT_ID , sizeof ( str ) ) , MQTT_CLIENT_ID , SettingsText ( SET_MQTT_CLIENT ) ) ;
2019-03-04 17:16:07 +00:00
WSContentSend_P ( HTTP_FORM_MQTT2 ,
2019-12-16 14:13:57 +00:00
( ! strlen ( SettingsText ( SET_MQTT_USER ) ) ) ? " 0 " : SettingsText ( SET_MQTT_USER ) ,
Format ( str , MQTT_TOPIC , sizeof ( str ) ) , MQTT_TOPIC , SettingsText ( SET_MQTT_TOPIC ) ,
MQTT_FULLTOPIC , MQTT_FULLTOPIC , SettingsText ( SET_MQTT_FULLTOPIC ) ) ;
2019-03-10 14:36:34 +00:00
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 ( ) ;
2018-10-10 21:21:44 +01:00
}
2018-11-14 13:32:09 +00:00
void MqttSaveSettings ( void )
2018-10-10 21:21:44 +01:00
{
2019-12-22 14:23:52 +00:00
char tmp [ TOPSZ ] ;
2018-10-10 21:21:44 +01:00
char stemp [ TOPSZ ] ;
char stemp2 [ TOPSZ ] ;
WebGetArg ( " mt " , tmp , sizeof ( tmp ) ) ;
strlcpy ( stemp , ( ! strlen ( tmp ) ) ? MQTT_TOPIC : tmp , sizeof ( stemp ) ) ;
MakeValidMqtt ( 0 , stemp ) ;
WebGetArg ( " mf " , tmp , sizeof ( tmp ) ) ;
strlcpy ( stemp2 , ( ! strlen ( tmp ) ) ? MQTT_FULLTOPIC : tmp , sizeof ( stemp2 ) ) ;
2019-07-28 16:14:20 +01:00
MakeValidMqtt ( 1 , stemp2 ) ;
2019-12-16 14:13:57 +00:00
if ( ( strcmp ( stemp , SettingsText ( SET_MQTT_TOPIC ) ) ) | | ( strcmp ( stemp2 , SettingsText ( SET_MQTT_FULLTOPIC ) ) ) ) {
2019-11-03 11:33:36 +00:00
Response_P ( ( Settings . flag . mqtt_offline ) ? S_OFFLINE : " " ) ; // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P ( TELE , S_LWT , true ) ; // Offline or remove previous retained topic
2018-10-10 21:21:44 +01:00
}
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_TOPIC , stemp ) ;
SettingsUpdateText ( SET_MQTT_FULLTOPIC , stemp2 ) ;
2018-10-10 21:21:44 +01:00
WebGetArg ( " mh " , tmp , sizeof ( tmp ) ) ;
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_HOST , ( ! strlen ( tmp ) ) ? MQTT_HOST : ( ! strcmp ( tmp , " 0 " ) ) ? " " : tmp ) ;
2018-10-10 21:21:44 +01:00
WebGetArg ( " ml " , tmp , sizeof ( tmp ) ) ;
Settings . mqtt_port = ( ! strlen ( tmp ) ) ? MQTT_PORT : atoi ( tmp ) ;
WebGetArg ( " mc " , tmp , sizeof ( tmp ) ) ;
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_CLIENT , ( ! strlen ( tmp ) ) ? MQTT_CLIENT_ID : tmp ) ;
2019-06-30 17:50:42 +01:00
# if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s " ) ,
2019-12-16 14:13:57 +00:00
SettingsText ( SET_MQTT_HOST ) , Settings . mqtt_port , SettingsText ( SET_MQTT_CLIENT ) , SettingsText ( SET_MQTT_TOPIC ) , SettingsText ( SET_MQTT_FULLTOPIC ) ) ;
2019-06-30 17:50:42 +01:00
# else // USE_MQTT_AWS_IOT
2018-10-10 21:21:44 +01:00
WebGetArg ( " mu " , tmp , sizeof ( tmp ) ) ;
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_USER , ( ! strlen ( tmp ) ) ? MQTT_USER : ( ! strcmp ( tmp , " 0 " ) ) ? " " : tmp ) ;
2018-10-10 21:21:44 +01:00
WebGetArg ( " mp " , tmp , sizeof ( tmp ) ) ;
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MQTT_PWD , ( ! strlen ( tmp ) ) ? " " : ( ! strcmp ( tmp , D_ASTERISK_PWD ) ) ? SettingsText ( SET_MQTT_PWD ) : tmp ) ;
2019-03-08 14:15:42 +00:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_MQTTUSER " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s " ) ,
2019-12-16 14:13:57 +00:00
SettingsText ( SET_MQTT_HOST ) , Settings . mqtt_port , SettingsText ( SET_MQTT_CLIENT ) , SettingsText ( SET_MQTT_USER ) , SettingsText ( SET_MQTT_TOPIC ) , SettingsText ( SET_MQTT_FULLTOPIC ) ) ;
2019-06-10 11:06:03 +01:00
# endif
2018-10-10 21:21:44 +01:00
}
# endif // USE_WEBSERVER
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-01-28 13:08:33 +00:00
bool Xdrv02 ( uint8_t function )
2018-10-10 21:21:44 +01:00
{
2019-01-28 13:08:33 +00:00
bool result = false ;
2018-10-10 21:21:44 +01:00
2019-11-03 11:33:36 +00:00
if ( Settings . flag . mqtt_enabled ) { // SetOption3 - Enable MQTT
2018-10-10 21:21:44 +01:00
switch ( function ) {
2019-06-05 10:44:52 +01:00
case FUNC_PRE_INIT :
MqttInit ( ) ;
break ;
2019-04-06 14:22:25 +01:00
case FUNC_EVERY_50_MSECOND : // https://github.com/knolleary/pubsubclient/issues/556
MqttClient . loop ( ) ;
2019-03-30 12:03:45 +00:00
break ;
2018-10-10 21:21:44 +01:00
# ifdef USE_WEBSERVER
case FUNC_WEB_ADD_BUTTON :
2019-03-10 14:36:34 +00:00
WSContentSend_P ( HTTP_BTN_MENU_MQTT ) ;
2018-10-10 21:21:44 +01:00
break ;
case FUNC_WEB_ADD_HANDLER :
WebServer - > on ( " / " WEB_HANDLE_MQTT , HandleMqttConfiguration ) ;
break ;
# endif // USE_WEBSERVER
case FUNC_COMMAND :
2019-08-01 14:46:12 +01:00
result = DecodeCommand ( kMqttCommands , MqttCommand ) ;
2018-10-10 21:21:44 +01:00
break ;
}
}
return result ;
}