2020-10-25 22:56:18 +01:00
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * Program : MQTTstuff
2024-04-17 06:52:21 +02:00
* * Version : v0 .10 .3
2020-10-25 22:56:18 +01:00
* *
2024-04-27 18:42:12 +02:00
* * Copyright ( c ) 2021 - 2024 Robert van den Breemen
2020-10-25 22:56:18 +01:00
* * Modified version from ( c ) 2020 Willem Aandewiel
* *
* * TERMS OF USE : MIT License . See bottom of file .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
2021-12-28 16:28:17 +01:00
# include <PubSubClient.h> // MQTT client publish and subscribe functionality
2021-03-24 21:12:58 +01:00
# define MQTTDebugTln(...) ({ if (bDebugMQTT) DebugTln(__VA_ARGS__); })
# define MQTTDebugln(...) ({ if (bDebugMQTT) Debugln(__VA_ARGS__); })
# define MQTTDebugTf(...) ({ if (bDebugMQTT) DebugTf(__VA_ARGS__); })
# define MQTTDebugf(...) ({ if (bDebugMQTT) Debugf(__VA_ARGS__); })
# define MQTTDebugT(...) ({ if (bDebugMQTT) DebugT(__VA_ARGS__); })
# define MQTTDebug(...) ({ if (bDebugMQTT) Debug(__VA_ARGS__); })
2020-10-25 22:56:18 +01:00
// Declare some variables within global scope
2021-11-28 13:06:25 +01:00
static IPAddress MQTTbrokerIP ;
static char MQTTbrokerIPchar [ 20 ] ;
2020-10-25 22:56:18 +01:00
2021-11-28 13:06:25 +01:00
static PubSubClient MQTTclient ( wifiClient ) ;
2020-10-25 22:56:18 +01:00
2021-11-28 13:06:25 +01:00
int8_t reconnectAttempts = 0 ;
char lastMQTTtimestamp [ 15 ] = " " ;
enum states_of_MQTT { MQTT_STATE_INIT , MQTT_STATE_TRY_TO_CONNECT , MQTT_STATE_IS_CONNECTED , MQTT_STATE_WAIT_CONNECTION_ATTEMPT , MQTT_STATE_WAIT_FOR_RECONNECT , MQTT_STATE_ERROR } ;
enum states_of_MQTT stateMQTT = MQTT_STATE_INIT ;
String MQTTclientId ;
String MQTTPubNamespace = " " ;
String MQTTSubNamespace = " " ;
String NodeId = " " ;
//set command list
struct MQTT_set_cmd_t
{
2021-12-31 16:23:03 +01:00
const char * setcmd ;
const char * otgwcmd ;
const char * ottype ;
2021-11-28 13:06:25 +01:00
} ;
const MQTT_set_cmd_t setcmds [ ] {
{ " command " , " " , " raw " } ,
{ " setpoint " , " TT " , " temp " } ,
{ " constant " , " TC " , " temp " } ,
{ " outside " , " OT " , " temp " } ,
{ " hotwater " , " HW " , " on " } ,
{ " gatewaymode " , " GW " , " on " } ,
{ " setback " , " SB " , " temp " } ,
{ " maxchsetpt " , " SH " , " temp " } ,
{ " maxdhwsetpt " , " SW " , " temp " } ,
{ " maxmodulation " , " MM " , " level " } ,
{ " ctrlsetpt " , " CS " , " temp " } ,
{ " ctrlsetpt2 " , " C2 " , " temp " } ,
{ " chenable " , " CH " , " on " } ,
{ " chenable2 " , " H2 " , " on " } ,
{ " ventsetpt " , " VS " , " level " } ,
{ " temperaturesensor " , " TS " , " function " } ,
{ " addalternative " , " AA " , " function " } ,
{ " delalternative " , " DA " , " function " } ,
{ " unknownid " , " UI " , " function " } ,
{ " knownid " , " KI " , " function " } ,
{ " priomsg " , " PM " , " function " } ,
{ " setresponse " , " SR " , " function " } ,
{ " clearrespons " , " CR " , " function " } ,
{ " resetcounter " , " RS " , " function " } ,
{ " ignoretransitations " , " IT " , " function " } ,
{ " overridehb " , " OH " , " function " } ,
{ " forcethermostat " , " FT " , " function " } ,
{ " voltageref " , " VR " , " function " } ,
{ " debugptr " , " DP " , " function " } ,
} ;
const int nrcmds = sizeof ( setcmds ) / sizeof ( setcmds [ 0 ] ) ;
// const char learnmsg[] { "LA", "PR=L", "LB", "PR=L", "LC", "PR=L", "LD", "PR=L", "LE", "PR=L", "LF", "PR=L", "GA", "PR=G", "GB", "PR=G", "VR", "PR=V", "GW", "PR=M", "IT", "PR=T", "SB", "PR=S", "HW", "PR=W" } ;
// const int nrlearnmsg = sizeof(learnmsg) / sizeof(learnmsg[0]);
2020-10-25 22:56:18 +01:00
//===========================================================================================
void startMQTT ( )
{
2021-01-31 23:43:52 +01:00
if ( ! settingMQTTenable ) return ;
2020-10-25 22:56:18 +01:00
stateMQTT = MQTT_STATE_INIT ;
2021-03-06 10:39:17 +01:00
//setup for mqtt discovery
2021-12-28 18:36:50 +01:00
clearMQTTConfigDone ( ) ;
2021-04-01 22:33:58 +02:00
NodeId = settingMQTTuniqueid ;
MQTTPubNamespace = settingMQTTtopTopic + " /value/ " + NodeId ;
MQTTSubNamespace = settingMQTTtopTopic + " /set/ " + NodeId ;
2020-10-31 18:46:04 +01:00
handleMQTT ( ) ; //initialize the MQTT statemachine
2020-11-20 00:43:22 +01:00
// handleMQTT(); //then try to connect to MQTT
// handleMQTT(); //now you should be connected to MQTT ready to send
2020-10-25 22:56:18 +01:00
}
2020-11-09 00:05:06 +01:00
2022-01-04 21:22:49 +01:00
bool bHAcycle = false ;
2021-02-28 15:44:53 +01:00
// handles MQTT subscribe incoming stuff
2020-11-09 00:05:06 +01:00
void handleMQTTcallback ( char * topic , byte * payload , unsigned int length ) {
2021-03-24 21:12:58 +01:00
if ( bDebugMQTT ) {
DebugT ( " Message arrived on topic [ " ) ; Debug ( topic ) ; Debug ( " ] = [ " ) ;
2021-12-31 16:23:03 +01:00
for ( unsigned int i = 0 ; i < length ; i + + ) {
2021-03-24 21:12:58 +01:00
Debug ( ( char ) payload [ i ] ) ;
}
2022-01-08 21:16:06 +01:00
Debug ( " ] ( " ) ; Debug ( length ) ; Debug ( " ) " ) ; Debugln ( ) ; DebugFlush ( ) ;
2021-03-24 21:12:58 +01:00
}
2021-11-28 13:06:25 +01:00
2021-10-23 21:18:41 +02:00
//detect home assistant going down...
char msgPayload [ 50 ] ;
2021-11-15 21:39:02 +01:00
int msglen = min ( ( int ) ( length ) + 1 , ( int ) sizeof ( msgPayload ) ) ;
strlcpy ( msgPayload , ( char * ) payload , msglen ) ;
2022-01-04 23:59:05 +01:00
if ( strcasecmp ( topic , " homeassistant/status " ) = = 0 ) {
2021-10-23 21:18:41 +02:00
//incoming message on status, detect going down
2022-01-16 10:56:39 +01:00
if ( ! settingMQTTharebootdetection ) {
//So if the HA reboot detection is turned of, we will just look for HA going online.
//This means everytime there is "online" message, we will restart MQTT configuration, including the HA Auto Discovery.
bHAcycle = true ;
}
2022-01-04 23:59:05 +01:00
if ( strcasecmp ( msgPayload , " offline " ) = = 0 ) {
2021-10-23 21:18:41 +02:00
//home assistant went down
DebugTln ( F ( " Home Assistant went offline! " ) ) ;
2022-01-04 21:22:49 +01:00
bHAcycle = true ; //set flag, so it triggers when it goes back online
2022-01-08 11:43:03 +01:00
} else if ( ( strcasecmp ( msgPayload , " online " ) = = 0 ) & & bHAcycle ) {
2021-10-23 21:18:41 +02:00
DebugTln ( F ( " Home Assistant went online! " ) ) ;
2022-01-04 21:22:49 +01:00
bHAcycle = false ; //clear flag, so it does not trigger again
2021-10-23 21:18:41 +02:00
//restart stuff, to make sure it works correctly again
startMQTT ( ) ; // fixing some issues with hanging HA AutoDiscovery in some scenario's?
} else {
2023-01-10 20:42:04 +01:00
DebugTf ( PSTR ( " Home Assistant Status=[%s] and HA cycle status [%s] \r \n " ) , msgPayload , CBOOLEAN ( bHAcycle ) ) ;
2021-10-23 21:18:41 +02:00
}
}
2021-11-28 13:06:25 +01:00
2022-01-08 21:16:06 +01:00
2021-11-28 13:06:25 +01:00
// parse the incoming topic and execute commands
char * token ;
2023-01-03 14:33:00 +01:00
char otgwcmd [ 51 ] = { 0 } ;
2022-01-08 21:16:06 +01:00
//first check toptopic part, it can include the seperator, e.g. "myHome/OTGW" or "OTGW""
if ( strncmp ( topic , settingMQTTtopTopic . c_str ( ) , settingMQTTtopTopic . length ( ) ) ! = 0 ) {
MQTTDebugln ( F ( " MQTT: wrong top topic " ) ) ;
return ;
} else {
//remove the top topic part
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Parsing topic: %s/ " ) , settingMQTTtopTopic . c_str ( ) ) ;
2022-01-08 21:16:06 +01:00
topic + = settingMQTTtopTopic . length ( ) ;
2022-01-09 01:46:49 +01:00
while ( * topic = = ' / ' ) {
topic + + ;
}
2022-01-08 21:16:06 +01:00
}
// naming convention /set/<node id>/<command>
2021-11-28 13:06:25 +01:00
token = strtok ( topic , " / " ) ;
MQTTDebugf ( " %s/ " , token ) ;
2022-01-08 21:16:06 +01:00
if ( strcasecmp ( token , " set " ) = = 0 ) {
token = strtok ( NULL , " / " ) ;
MQTTDebugf ( " %s/ " , token ) ;
if ( strcasecmp ( token , CSTR ( NodeId ) ) = = 0 ) {
2021-11-28 13:06:25 +01:00
token = strtok ( NULL , " / " ) ;
2022-01-08 21:16:06 +01:00
MQTTDebugf ( " %s " , token ) ;
if ( token ! = NULL ) {
//loop thru command list
int i ;
for ( i = 0 ; i < nrcmds ; i + + ) {
if ( strcasecmp ( token , setcmds [ i ] . setcmd ) = = 0 ) {
//found a match
if ( strcasecmp ( setcmds [ i ] . ottype , " raw " ) = = 0 ) {
//raw command
snprintf ( otgwcmd , sizeof ( otgwcmd ) , " %s " , msgPayload ) ;
MQTTDebugf ( " found command, sending payload [%s] \r \n " , otgwcmd ) ;
addOTWGcmdtoqueue ( ( char * ) otgwcmd , strlen ( otgwcmd ) , true ) ;
} else {
//all other commands are <otgwcmd>=<payload message>
snprintf ( otgwcmd , sizeof ( otgwcmd ) , " %s=%s " , setcmds [ i ] . otgwcmd , msgPayload ) ;
MQTTDebugf ( " found command, sending payload [%s] \r \n " , otgwcmd ) ;
addOTWGcmdtoqueue ( ( char * ) otgwcmd , strlen ( otgwcmd ) , true ) ;
}
break ; //exit loop
}
}
if ( i > = nrcmds ) {
//no match found
MQTTDebugln ( ) ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " No match found for command: [%s] \r \n " ) , token ) ;
2021-11-28 13:06:25 +01:00
}
}
}
}
2020-11-09 00:05:06 +01:00
}
2020-10-25 22:56:18 +01:00
//===========================================================================================
void handleMQTT ( )
2021-01-31 23:43:52 +01:00
{
if ( ! settingMQTTenable ) return ;
2021-12-30 16:03:11 +01:00
DECLARE_TIMER_SEC ( timerMQTTwaitforconnect , 42 , CATCH_UP_MISSED_TICKS ) ; // wait before trying to connect again
DECLARE_TIMER_SEC ( timerMQTTwaitforretry , 3 , CATCH_UP_MISSED_TICKS ) ; // wait for retry
2020-10-25 22:56:18 +01:00
2021-03-24 21:12:58 +01:00
//State debug timers
2021-03-31 21:04:29 +02:00
DECLARE_TIMER_SEC ( timerMQTTdebugwaitforreconnect , 13 ) ;
DECLARE_TIMER_SEC ( timerMQTTdebugerrorstate , 13 ) ;
DECLARE_TIMER_SEC ( timerMQTTdebugwaitconnectionattempt , 1 ) ;
DECLARE_TIMER_SEC ( timerMQTTdebugisconnected , 60 ) ;
2021-03-24 21:12:58 +01:00
2021-12-30 16:03:11 +01:00
if ( MQTTclient . connected ( ) ) MQTTclient . loop ( ) ; //always do a MQTTclient.loop() first
2020-10-25 22:56:18 +01:00
switch ( stateMQTT )
{
case MQTT_STATE_INIT :
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " MQTT State: MQTT Initializing " ) ) ;
2021-02-08 01:00:21 +01:00
WiFi . hostByName ( CSTR ( settingMQTTbroker ) , MQTTbrokerIP ) ; // lookup the MQTTbroker convert to IP
2020-10-25 22:56:18 +01:00
sprintf ( MQTTbrokerIPchar , " %d.%d.%d.%d " , MQTTbrokerIP [ 0 ] , MQTTbrokerIP [ 1 ] , MQTTbrokerIP [ 2 ] , MQTTbrokerIP [ 3 ] ) ;
if ( isValidIP ( MQTTbrokerIP ) )
{
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " [%s] => setServer(%s, %d) \r \n " ) , CSTR ( settingMQTTbroker ) , MQTTbrokerIPchar , settingMQTTbrokerPort ) ;
2020-10-25 22:56:18 +01:00
MQTTclient . disconnect ( ) ;
MQTTclient . setServer ( MQTTbrokerIPchar , settingMQTTbrokerPort ) ;
2020-11-09 00:05:06 +01:00
MQTTclient . setCallback ( handleMQTTcallback ) ;
2021-03-06 22:26:15 +01:00
MQTTclient . setSocketTimeout ( 4 ) ;
2020-10-25 22:56:18 +01:00
MQTTclientId = String ( _HOSTNAME ) + WiFi . macAddress ( ) ;
//skip try to connect
2021-03-24 21:12:58 +01:00
reconnectAttempts = 0 ;
2020-10-25 22:56:18 +01:00
stateMQTT = MQTT_STATE_TRY_TO_CONNECT ;
}
else
{ // invalid IP, then goto error state
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " ERROR: [%s] => is not a valid URL \r \n " ) , CSTR ( settingMQTTbroker ) ) ;
2020-10-25 22:56:18 +01:00
stateMQTT = MQTT_STATE_ERROR ;
//DebugTln(F("Next State: MQTT_STATE_ERROR"));
}
RESTART_TIMER ( timerMQTTwaitforconnect ) ;
break ;
case MQTT_STATE_TRY_TO_CONNECT :
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " MQTT State: MQTT try to connect " ) ) ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " MQTT server is [%s], IP[%s] \r \n " ) , settingMQTTbroker . c_str ( ) , MQTTbrokerIPchar ) ;
2020-10-25 22:56:18 +01:00
2021-03-24 21:12:58 +01:00
MQTTDebugT ( F ( " Attempting MQTT connection .. " ) ) ;
2020-10-25 22:56:18 +01:00
reconnectAttempts + + ;
//If no username, then anonymous connection to broker, otherwise assume username/password.
2021-03-05 10:56:51 +01:00
if ( settingMQTTuser . length ( ) = = 0 )
2020-10-25 22:56:18 +01:00
{
2021-03-24 21:12:58 +01:00
MQTTDebug ( F ( " without a Username/Password " ) ) ;
2021-04-03 19:07:15 +02:00
if ( ! MQTTclient . connect ( CSTR ( MQTTclientId ) , CSTR ( MQTTPubNamespace ) , 0 , true , " offline " ) ) PrintMQTTError ( ) ;
2020-10-25 22:56:18 +01:00
}
else
{
2021-03-24 21:12:58 +01:00
MQTTDebugf ( " Username [%s] " , CSTR ( settingMQTTuser ) ) ;
2021-04-03 19:07:15 +02:00
if ( ! MQTTclient . connect ( CSTR ( MQTTclientId ) , CSTR ( settingMQTTuser ) , CSTR ( settingMQTTpasswd ) , CSTR ( MQTTPubNamespace ) , 0 , true , " offline " ) ) PrintMQTTError ( ) ;
2020-10-25 22:56:18 +01:00
}
//If connection was made succesful, move on to next state...
2021-02-08 01:00:21 +01:00
if ( MQTTclient . connected ( ) )
2020-10-25 22:56:18 +01:00
{
reconnectAttempts = 0 ;
2022-01-14 00:36:05 +01:00
MQTTDebugln ( F ( " .. connected \r " ) ) ;
Debugln ( F ( " MQTT connected " ) ) ;
2020-10-25 22:56:18 +01:00
stateMQTT = MQTT_STATE_IS_CONNECTED ;
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " Next State: MQTT_STATE_IS_CONNECTED " ) ) ;
2021-02-28 15:44:53 +01:00
// birth message, sendMQTT retains by default
2021-03-06 18:36:14 +01:00
sendMQTT ( CSTR ( MQTTPubNamespace ) , " online " ) ;
2021-12-28 16:28:17 +01:00
// First do AutoConfiguration for Homeassistant
// doAutoConfigure();
2020-11-09 08:24:47 +01:00
//Subscribe to topics
2020-11-20 00:43:22 +01:00
char topic [ 100 ] ;
2021-03-06 18:36:14 +01:00
strcpy ( topic , CSTR ( MQTTSubNamespace ) ) ;
2021-02-28 15:44:53 +01:00
strlcat ( topic , " /# " , sizeof ( topic ) ) ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Subscribe to MQTT: TopicId [%s] \r \n " ) , topic ) ;
2021-02-28 15:44:53 +01:00
if ( MQTTclient . subscribe ( topic ) ) {
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " MQTT: Subscribed successfully to TopicId [%s] \r \n " ) , topic ) ;
2021-02-28 15:44:53 +01:00
}
else
{
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " MQTT: Subscribe TopicId [%s] FAILED! \r \n " ) , topic ) ;
2021-04-03 19:07:15 +02:00
PrintMQTTError ( ) ;
2021-02-28 15:44:53 +01:00
}
2021-10-23 21:18:41 +02:00
MQTTclient . subscribe ( " homeassistant/status " ) ; //start monitoring the status of homeassistant, if it goes down, then force a restart after it comes back online.
2021-03-08 02:15:28 +01:00
sendMQTTversioninfo ( ) ;
2020-10-25 22:56:18 +01:00
}
else
{ // no connection, try again, do a non-blocking wait for 3 seconds.
2021-03-24 21:12:58 +01:00
MQTTDebugln ( F ( " .. \r " ) ) ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " failed, retrycount=[%d], rc=[%d] .. try again in 3 seconds \r \n " ) , reconnectAttempts , MQTTclient . state ( ) ) ;
2020-10-25 22:56:18 +01:00
RESTART_TIMER ( timerMQTTwaitforretry ) ;
stateMQTT = MQTT_STATE_WAIT_CONNECTION_ATTEMPT ; // if the re-connect did not work, then return to wait for reconnect
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " Next State: MQTT_STATE_WAIT_CONNECTION_ATTEMPT " ) ) ;
2020-10-25 22:56:18 +01:00
}
//After 5 attempts... go wait for a while.
if ( reconnectAttempts > = 5 )
{
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " 5 attempts have failed. Retry wait for next reconnect in 10 minutes \r " ) ) ;
2020-10-25 22:56:18 +01:00
RESTART_TIMER ( timerMQTTwaitforconnect ) ;
stateMQTT = MQTT_STATE_WAIT_FOR_RECONNECT ; // if the re-connect did not work, then return to wait for reconnect
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " Next State: MQTT_STATE_WAIT_FOR_RECONNECT " ) ) ;
2020-10-25 22:56:18 +01:00
}
break ;
case MQTT_STATE_IS_CONNECTED :
2021-03-31 21:04:29 +02:00
if DUE ( timerMQTTdebugisconnected ) MQTTDebugTln ( F ( " MQTT State: MQTT is Connected " ) ) ;
2020-10-25 22:56:18 +01:00
if ( MQTTclient . connected ( ) )
{ //if the MQTT client is connected, then please do a .loop call...
MQTTclient . loop ( ) ;
}
else
{ //else go and wait 10 minutes, before trying again.
RESTART_TIMER ( timerMQTTwaitforconnect ) ;
stateMQTT = MQTT_STATE_WAIT_FOR_RECONNECT ;
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " Next State: MQTT_STATE_WAIT_FOR_RECONNECT " ) ) ;
2020-10-25 22:56:18 +01:00
}
break ;
case MQTT_STATE_WAIT_CONNECTION_ATTEMPT :
//do non-blocking wait for 3 seconds
2021-03-31 21:04:29 +02:00
if DUE ( timerMQTTdebugwaitconnectionattempt ) MQTTDebugTln ( F ( " MQTT State: MQTT_WAIT_CONNECTION_ATTEMPT " ) ) ;
2020-10-25 22:56:18 +01:00
if ( DUE ( timerMQTTwaitforretry ) )
{
//Try again... after waitforretry non-blocking delay
stateMQTT = MQTT_STATE_TRY_TO_CONNECT ;
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " Next State: MQTT_STATE_TRY_TO_CONNECT " ) ) ;
2020-10-25 22:56:18 +01:00
}
break ;
case MQTT_STATE_WAIT_FOR_RECONNECT :
//do non-blocking wait for 10 minutes, then try to connect again.
2021-03-31 21:04:29 +02:00
if DUE ( timerMQTTdebugwaitforreconnect ) MQTTDebugTln ( F ( " MQTT State: MQTT wait for reconnect " ) ) ;
if ( DUE ( timerMQTTwaitforconnect ) )
2020-10-25 22:56:18 +01:00
{
//remember when you tried last time to reconnect
RESTART_TIMER ( timerMQTTwaitforretry ) ;
reconnectAttempts = 0 ;
stateMQTT = MQTT_STATE_TRY_TO_CONNECT ;
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " Next State: MQTT_STATE_TRY_TO_CONNECT " ) ) ;
2020-10-25 22:56:18 +01:00
}
break ;
case MQTT_STATE_ERROR :
2021-03-31 21:04:29 +02:00
if DUE ( timerMQTTdebugerrorstate ) MQTTDebugTln ( F ( " MQTT State: MQTT ERROR, wait for 10 minutes, before trying again " ) ) ;
2021-12-30 16:03:11 +01:00
//wait for next retry
2020-10-25 22:56:18 +01:00
RESTART_TIMER ( timerMQTTwaitforconnect ) ;
stateMQTT = MQTT_STATE_WAIT_FOR_RECONNECT ;
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " Next State: MQTT_STATE_WAIT_FOR_RECONNECT " ) ) ;
2020-10-25 22:56:18 +01:00
break ;
default :
2021-03-24 21:12:58 +01:00
MQTTDebugTln ( F ( " MQTT State: default, this should NEVER happen! " ) ) ;
2020-10-25 22:56:18 +01:00
//do nothing, this state should not happen
stateMQTT = MQTT_STATE_INIT ;
2021-03-24 21:12:58 +01:00
DebugTln ( F ( " Next State: MQTT_STATE_INIT " ) ) ;
2020-10-25 22:56:18 +01:00
break ;
}
2021-02-08 01:00:21 +01:00
statusMQTTconnection = MQTTclient . connected ( ) ;
2020-10-25 22:56:18 +01:00
} // handleMQTT()
//===========================================================================================
String trimVal ( char * in )
{
String Out = in ;
Out . trim ( ) ;
return Out ;
} // trimVal()
2021-04-03 19:07:15 +02:00
void PrintMQTTError ( ) {
2022-01-04 01:24:04 +01:00
MQTTDebugln ( ) ;
2021-04-03 19:07:15 +02:00
switch ( MQTTclient . state ( ) )
{
case MQTT_CONNECTION_TIMEOUT : MQTTDebugTln ( F ( " Error: MQTT connection timeout " ) ) ; break ;
case MQTT_CONNECTION_LOST : MQTTDebugTln ( F ( " Error: MQTT connections lost " ) ) ; break ;
case MQTT_CONNECT_FAILED : MQTTDebugTln ( F ( " Error: MQTT connection failed " ) ) ; break ;
case MQTT_DISCONNECTED : MQTTDebugTln ( F ( " Error: MQTT disconnected " ) ) ; break ;
case MQTT_CONNECTED : MQTTDebugTln ( F ( " Error: MQTT connected " ) ) ; break ;
case MQTT_CONNECT_BAD_PROTOCOL : MQTTDebugTln ( F ( " Error: MQTT connect bad protocol " ) ) ; break ;
case MQTT_CONNECT_BAD_CLIENT_ID : MQTTDebugTln ( F ( " Error: MQTT connect bad client id " ) ) ; break ;
case MQTT_CONNECT_UNAVAILABLE : MQTTDebugTln ( F ( " Error: MQTT connect unavailable " ) ) ; break ;
case MQTT_CONNECT_BAD_CREDENTIALS : MQTTDebugTln ( F ( " Error: MQTT connect bad credentials " ) ) ; break ;
case MQTT_CONNECT_UNAUTHORIZED : MQTTDebugTln ( F ( " Error: MQTT connect unauthorized " ) ) ; break ;
default : MQTTDebugTln ( F ( " Error: MQTT unknown error " ) ) ;
}
}
2021-02-28 15:44:53 +01:00
/*
2021-03-08 02:15:28 +01:00
topic : < string > , sensor topic , will be automatically prefixed with < mqtt topic > / value / < node_id >
json : < string > , payload to send
retain : < bool > , retain mqtt message
2021-02-28 15:44:53 +01:00
*/
void sendMQTTData ( const String topic , const String json , const bool retain = false )
2020-11-03 01:28:05 +01:00
{
2021-02-08 01:00:21 +01:00
if ( ! settingMQTTenable ) return ;
2021-02-28 15:44:53 +01:00
sendMQTTData ( CSTR ( topic ) , CSTR ( json ) , retain ) ;
}
2020-11-03 01:28:05 +01:00
2021-02-28 15:44:53 +01:00
/*
2021-03-08 02:15:28 +01:00
topic : < string > , sensor topic , will be automatically prefixed with < mqtt topic > / value / < node_id >
json : < string > , payload to send
retain : < bool > , retain mqtt message
2021-02-28 15:44:53 +01:00
*/
2021-03-04 23:27:47 +01:00
void sendMQTTData ( const char * topic , const char * json , const bool retain = false )
2020-10-25 22:56:18 +01:00
{
2021-01-31 23:43:52 +01:00
if ( ! settingMQTTenable ) return ;
2021-10-30 17:50:01 +02:00
if ( ! MQTTclient . connected ( ) ) { DebugTln ( F ( " Error: MQTT broker not connected. " ) ) ; PrintMQTTError ( ) ; return ; }
if ( ! isValidIP ( MQTTbrokerIP ) ) { DebugTln ( F ( " Error: MQTT broker IP not valid. " ) ) ; return ; }
2021-02-28 15:44:53 +01:00
char full_topic [ 100 ] ;
2021-03-06 18:36:14 +01:00
snprintf ( full_topic , sizeof ( full_topic ) , " %s/ " , CSTR ( MQTTPubNamespace ) ) ;
2021-02-28 15:44:53 +01:00
strlcat ( full_topic , topic , sizeof ( full_topic ) ) ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Sending MQTT: server %s:%d => TopicId [%s] --> Message [%s] \r \n " ) , settingMQTTbroker . c_str ( ) , settingMQTTbrokerPort , full_topic , json ) ;
2021-04-03 19:07:15 +02:00
if ( ! MQTTclient . publish ( full_topic , json , retain ) ) PrintMQTTError ( ) ;
2020-11-02 08:09:26 +01:00
feedWatchDog ( ) ; //feed the dog
2020-10-31 18:46:04 +01:00
} // sendMQTTData()
2020-10-25 22:56:18 +01:00
2021-02-28 15:44:53 +01:00
/*
* topic : < string > , topic will be used as is ( no prefixing ) , retained = true
* json : < string > , payload to send
*/
2020-10-31 18:46:04 +01:00
//===========================================================================================
2021-02-28 15:44:53 +01:00
void sendMQTT ( String topic , String json ) {
if ( ! settingMQTTenable ) return ;
sendMQTT ( CSTR ( topic ) , CSTR ( json ) , json . length ( ) ) ;
}
void sendMQTT ( const char * topic , const char * json , const size_t len )
2020-10-31 18:46:04 +01:00
{
2021-01-31 23:43:52 +01:00
if ( ! settingMQTTenable ) return ;
2021-10-30 17:50:01 +02:00
if ( ! MQTTclient . connected ( ) ) { DebugTln ( F ( " Error: MQTT broker not connected. " ) ) ; PrintMQTTError ( ) ; return ; }
if ( ! isValidIP ( MQTTbrokerIP ) ) { DebugTln ( F ( " Error: MQTT broker IP not valid. " ) ) ; return ; }
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Sending MQTT: server %s:%d => TopicId [%s] --> Message [%s] \r \n " ) , settingMQTTbroker . c_str ( ) , settingMQTTbrokerPort , topic , json ) ;
2020-10-31 18:46:04 +01:00
if ( MQTTclient . getBufferSize ( ) < len ) MQTTclient . setBufferSize ( len ) ; //resize buffer when needed
2021-02-28 15:44:53 +01:00
if ( MQTTclient . beginPublish ( topic , len , true ) ) {
2021-04-03 19:07:15 +02:00
for ( size_t i = 0 ; i < len ; i + + ) {
if ( ! MQTTclient . write ( json [ i ] ) ) PrintMQTTError ( ) ;
}
2021-02-28 15:44:53 +01:00
MQTTclient . endPublish ( ) ;
2021-04-03 19:07:15 +02:00
} else PrintMQTTError ( ) ;
2021-02-28 15:44:53 +01:00
2020-10-31 18:46:04 +01:00
feedWatchDog ( ) ;
2020-10-25 22:56:18 +01:00
} // sendMQTTData()
2020-11-02 08:09:26 +01:00
2022-01-05 22:08:28 +01:00
2021-03-08 02:15:28 +01:00
//===========================================================================================
2021-02-28 15:44:53 +01:00
void resetMQTTBufferSize ( )
2020-11-02 08:09:26 +01:00
{
2021-01-31 23:43:52 +01:00
if ( ! settingMQTTenable ) return ;
2021-02-28 15:44:53 +01:00
MQTTclient . setBufferSize ( 256 ) ;
}
2021-03-08 02:15:28 +01:00
//===========================================================================================
2021-12-28 16:28:17 +01:00
bool splitLine ( String sIn , char del , byte & cID , String & cKey , String & cVal ) {
2021-03-08 02:15:28 +01:00
sIn . trim ( ) ; //trim spaces
2021-12-28 16:28:17 +01:00
cID = 39 ; // ID 39 is unused in the OT spec
2021-03-08 02:15:28 +01:00
cKey = " " ;
cVal = " " ;
2021-12-28 16:28:17 +01:00
int posC = sIn . indexOf ( " // " ) ;
if ( posC > - 1 ) sIn . remove ( posC ) ; // strip comments
2021-03-08 02:15:28 +01:00
if ( sIn . length ( ) < = 3 ) return false ; //not enough buffer, skip split
2021-12-28 16:28:17 +01:00
2022-01-03 22:46:57 +01:00
unsigned int pos = sIn . indexOf ( del ) ; //determine first split point
2021-12-28 16:28:17 +01:00
if ( ( pos < = 0 ) | | ( pos = = ( sIn . length ( ) - 1 ) ) ) return false ; // no key or no value
2022-01-03 22:46:57 +01:00
unsigned int pos2 = sIn . indexOf ( del , pos + 1 ) ; //determine second split point, starting from the first found
2021-12-28 16:28:17 +01:00
if ( ( pos2 < = 0 ) | | ( pos2 < = pos ) | | ( pos2 = = ( sIn . length ( ) - 1 ) ) ) return false ; // no key or no value
String sID = sIn . substring ( 0 , pos ) ;
sID . trim ( ) ;
cID = ( byte ) sID . toInt ( ) ;
cKey = sIn . substring ( pos + 1 , pos2 ) ;
2021-03-08 02:15:28 +01:00
cKey . trim ( ) ; //before, and trim spaces
2021-12-28 16:28:17 +01:00
cVal = sIn . substring ( pos2 + 1 ) ;
2021-03-08 02:15:28 +01:00
cVal . trim ( ) ; //after,and trim spaces
2021-12-28 16:28:17 +01:00
// Debugf("Split line into: [%d] [%s] [%s]\r\n", cID, CSTR(cKey), CSTR(cVal)); DebugFlush();
2021-03-08 02:15:28 +01:00
return true ;
}
//===========================================================================================
2022-01-03 22:46:57 +01:00
bool getMQTTConfigDone ( const uint8_t MSGid )
2021-12-28 16:28:17 +01:00
{
uint8_t group = MSGid & 0 b11100000 ;
group = group > > 5 ;
uint8_t index = MSGid & 0 b00011111 ;
uint32_t result = bitRead ( MQTTautoConfigMap [ group ] , index ) ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Reading bit %d from group %d for MSGid %d: result = %d \r \n " ) , index , group , MSGid , result ) ;
2021-12-28 16:28:17 +01:00
if ( result > 0 ) {
return true ;
} else {
return false ;
}
}
//===========================================================================================
2022-01-03 22:46:57 +01:00
bool setMQTTConfigDone ( const uint8_t MSGid )
2021-12-28 16:28:17 +01:00
{
uint8_t group = MSGid & 0 b11100000 ;
group = group > > 5 ;
uint8_t index = MSGid & 0 b00011111 ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Setting bit %d from group %d for MSGid %d \r \n " ) , index , group , MSGid ) ;
MQTTDebugTf ( PSTR ( " Value before setting bit %d \r \n " ) , MQTTautoConfigMap [ group ] ) ;
2021-12-28 16:28:17 +01:00
if ( bitSet ( MQTTautoConfigMap [ group ] , index ) > 0 ) {
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Value after setting bit %d \r \n " ) , MQTTautoConfigMap [ group ] ) ;
2021-12-28 16:28:17 +01:00
return true ;
} else {
return false ;
}
}
//===========================================================================================
2021-12-28 18:36:50 +01:00
void clearMQTTConfigDone ( )
{
memset ( MQTTautoConfigMap , 0 , sizeof ( MQTTautoConfigMap ) ) ;
}
//===========================================================================================
2022-01-08 11:43:03 +01:00
void doAutoConfigure ( bool bForcaAll = false ) {
2022-01-04 01:24:04 +01:00
//force all sensors to be sent to auto configuration
for ( int i = 0 ; i < 255 ; i + + ) {
2022-01-08 11:43:03 +01:00
if ( ( getMQTTConfigDone ( ( byte ) i ) = = true ) | | bForcaAll ) {
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Sending auto configuration for sensor %d \r \n " ) , i ) ;
2022-01-08 11:43:03 +01:00
doAutoConfigureMsgid ( ( byte ) i ) ;
2022-01-14 00:36:05 +01:00
doBackgroundTasks ( ) ;
2022-01-08 11:43:03 +01:00
}
2022-01-04 01:24:04 +01:00
}
2021-12-28 16:28:17 +01:00
// bool success = doAutoConfigure("config"); // the string "config" should match every line non-comment in mqttha.cfg
}
//===========================================================================================
2022-01-04 01:24:04 +01:00
bool doAutoConfigureMsgid ( byte OTid )
2023-01-23 20:00:27 +01:00
{
String cfgSensorId = " " ;
// check if foney dataid is called to do autoconfigure for temp sensors, call configsensors instead
if ( OTid = = OTGWdallasdataid ) {
MQTTDebugTf ( PSTR ( " Sending auto configuration for temp sensors %d \r \n " ) , OTid ) ;
configSensors ( ) ;
return true ;
}
else return doAutoConfigureMsgid ( OTid , cfgSensorId ) ;
}
bool doAutoConfigureMsgid ( byte OTid , String cfgSensorId )
2021-12-28 16:28:17 +01:00
{
bool _result = false ;
2022-01-14 00:36:05 +01:00
if ( ! settingMQTTenable ) {
return _result ;
}
if ( ! MQTTclient . connected ( ) ) {
DebugTln ( F ( " Error: MQTT broker not connected. " ) ) ;
return _result ;
}
if ( ! isValidIP ( MQTTbrokerIP ) ) {
DebugTln ( F ( " Error: MQTT broker IP not valid. " ) ) ;
return _result ;
}
2021-12-28 16:28:17 +01:00
byte lineID = 39 ; // 39 is unused in OT protocol so is a safe value
2021-03-08 02:15:28 +01:00
String sMsg = " " ;
2021-12-28 16:28:17 +01:00
String sTopic = " " ;
2021-03-08 02:15:28 +01:00
//Let's open the MQTT autoconfig file
2021-12-28 16:28:17 +01:00
File fh ; //filehandle
const char * cfgFilename = " /mqttha.cfg " ;
2021-03-08 02:15:28 +01:00
LittleFS . begin ( ) ;
2022-01-14 00:36:05 +01:00
if ( ! LittleFS . exists ( cfgFilename ) ) {
DebugTln ( F ( " Error: confuration file not found. " ) ) ;
return _result ;
}
2021-12-28 16:28:17 +01:00
fh = LittleFS . open ( cfgFilename , " r " ) ;
2021-03-08 02:15:28 +01:00
2022-01-14 00:36:05 +01:00
if ( ! fh ) {
DebugTln ( F ( " Error: could not open confuration file. " ) ) ;
return _result ;
}
2021-03-08 02:15:28 +01:00
2021-12-28 16:28:17 +01:00
//Lets go read the config and send it out to MQTT line by line
while ( fh . available ( ) )
2022-01-14 00:36:05 +01:00
{
//read file line by line, split and send to MQTT (topic, msg)
2021-12-28 16:28:17 +01:00
feedWatchDog ( ) ; //start with feeding the dog
String sLine = fh . readStringUntil ( ' \n ' ) ;
2023-01-10 20:42:04 +01:00
// DebugTf(PSTR("sline[%s]\r\n"), CSTR(sLine));
2021-12-28 17:48:21 +01:00
if ( ! splitLine ( sLine , ' ; ' , lineID , sTopic , sMsg ) ) { //splitLine() also filters comments
2023-01-10 20:42:04 +01:00
//MQTTDebugTf(PSTR("Either comment or invalid config line: [%s]\r\n"), CSTR(sLine));
2021-12-28 16:28:17 +01:00
continue ;
}
2021-12-28 17:48:21 +01:00
2023-01-10 20:42:04 +01:00
// DebugTf(PSTR("looking in config file line for %d: [%d][%s] \r\n"), OTid, lineID, CSTR(sTopic));
2021-12-28 18:37:53 +01:00
2021-12-28 16:28:17 +01:00
// check if this is the specific line we are looking for
if ( lineID ! = OTid ) continue ;
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " Found line in config file for %d: [%d][%s] \r \n " ) , OTid , lineID , CSTR ( sTopic ) ) ;
2021-12-28 18:37:53 +01:00
2021-12-28 16:28:17 +01:00
// discovery topic prefix
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " sTopic[%s]==> " ) , CSTR ( sTopic ) ) ;
2021-12-28 16:28:17 +01:00
sTopic . replace ( " %homeassistant% " , CSTR ( settingMQTThaprefix ) ) ;
2021-03-08 02:15:28 +01:00
2021-12-28 16:28:17 +01:00
/// node
sTopic . replace ( " %node_id% " , CSTR ( NodeId ) ) ;
2023-01-23 20:00:27 +01:00
/// SensorId
sTopic . replace ( " %sensor_id% " , CSTR ( cfgSensorId ) ) ;
2023-01-24 20:33:00 +01:00
2021-12-28 16:28:17 +01:00
MQTTDebugf ( " [%s] \r \n " , CSTR ( sTopic ) ) ;
/// ----------------------
2021-03-08 02:15:28 +01:00
2023-01-10 20:42:04 +01:00
MQTTDebugTf ( PSTR ( " sMsg[%s]==> " ) , CSTR ( sMsg ) ) ;
2021-03-08 02:15:28 +01:00
2021-12-28 16:28:17 +01:00
/// node
sMsg . replace ( " %node_id% " , CSTR ( NodeId ) ) ;
2021-03-08 02:15:28 +01:00
2023-01-23 20:00:27 +01:00
/// SensorId
2023-01-24 20:33:00 +01:00
sMsg . replace ( " %sensor_id% " , CSTR ( cfgSensorId ) ) ;
2023-01-23 20:00:27 +01:00
2021-12-28 16:28:17 +01:00
/// hostname
sMsg . replace ( " %hostname% " , CSTR ( settingHostname ) ) ;
2021-03-08 02:15:28 +01:00
2021-12-28 16:28:17 +01:00
/// version
2022-01-04 00:49:58 +01:00
sMsg . replace ( " %version% " , _VERSION ) ;
2021-03-08 02:15:28 +01:00
2021-12-28 16:28:17 +01:00
// pub topics prefix
sMsg . replace ( " %mqtt_pub_topic% " , CSTR ( MQTTPubNamespace ) ) ;
2021-03-08 02:15:28 +01:00
2021-12-28 16:28:17 +01:00
// sub topics
sMsg . replace ( " %mqtt_sub_topic% " , CSTR ( MQTTSubNamespace ) ) ;
2021-02-28 15:44:53 +01:00
2022-01-05 21:29:27 +01:00
MQTTDebugf ( " [%s] \r \n " , CSTR ( sMsg ) ) ;
DebugFlush ( ) ;
2021-02-28 15:44:53 +01:00
2021-12-28 16:28:17 +01:00
//sendMQTT(CSTR(sTopic), CSTR(sMsg), (sTopic.length() + sMsg.length()+2));
sendMQTT ( sTopic , sMsg ) ;
2021-03-08 02:15:28 +01:00
resetMQTTBufferSize ( ) ;
2022-01-14 00:36:05 +01:00
// delay(10);
2021-12-28 16:28:17 +01:00
_result = true ;
// TODO: enable this break if we are sure the old config dump method is no longer needed
// break;
} // while available()
fh . close ( ) ;
// HA discovery msg's are rather large, reset the buffer size to release some memory
resetMQTTBufferSize ( ) ;
return _result ;
2020-11-02 08:09:26 +01:00
}
2023-01-23 20:00:27 +01:00
void sensorAutoConfigure ( byte dataid , bool finishflag , String cfgSensorId = " " ) {
// Special version of Autoconfigure for sensors
// dataid is a foney id, not used by OT
// check wheter MQTT topic needs to be configured
// cfgNodeId can be set to alternate NodeId to allow for multiple temperature sensors, should normally be NodeId
// When finishflag is true, check on dataid is already done and complete the config. On false do the config and leave completion to caller
if ( getMQTTConfigDone ( dataid ) = = false or ! finishflag ) {
MQTTDebugTf ( PSTR ( " Need to set MQTT config for sensor id(%d) \r \n " ) , dataid ) ;
bool success = doAutoConfigureMsgid ( dataid , cfgSensorId ) ;
if ( success ) {
MQTTDebugTf ( PSTR ( " Successfully sent MQTT config for sensor id(%d) \r \n " ) , dataid ) ;
if ( finishflag ) setMQTTConfigDone ( dataid ) ;
} else {
MQTTDebugTf ( PSTR ( " Not able to complete MQTT configuration for sensor id(%d) \r \n " ) , dataid ) ;
}
} else {
// MQTTDebugTf("No need to set MQTT config for sensor id(%d)\r\n",dataid);
}
}
2021-01-21 08:28:47 +01:00
2020-11-02 08:09:26 +01:00
2020-10-25 22:56:18 +01:00
/***************************************************************************
*
* Permission is hereby granted , free of charge , to any person obtaining a
* copy of this software and associated documentation files ( the
* " Software " ) , to deal in the Software without restriction , including
* without limitation the rights to use , copy , modify , merge , publish ,
* distribute , sublicense , and / or sell copies of the Software , and to permit
* persons to whom the Software is furnished to do so , subject to the
* following conditions :
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS
* OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT
* OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */