2021-04-10 17:20:15 +01:00
/*
2021-05-11 10:01:46 +01:00
xdrv_12_discovery . ino - MQTT Discovery support for Tasmota
2021-04-10 17:20:15 +01:00
Copyright ( C ) 2021 Erik Montnemery , Federico Leoni and Theo Arends
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# ifdef USE_TASMOTA_DISCOVERY
# undef USE_HOME_ASSISTANT
/*********************************************************************************************\
* Tasmota discovery
*
2022-06-10 09:43:36 +01:00
* Supported by latest versions of Home Assistant ( with hatasmota ) and TasmoManager .
2021-04-10 17:20:15 +01:00
*
2021-04-12 11:20:49 +01:00
* SetOption19 0 - [ DiscoverOff 0 ] [ Discover 1 ] Enables discovery ( default )
* SetOption19 1 - [ DiscoverOff 1 ] [ Discover 0 ] Disables discovery and removes retained message from MQTT server
* SetOption73 1 - [ DiscoverButton ] Enable discovery for buttons
* SetOption114 1 - [ DiscoverSwitch ] Enable discovery for switches
2021-04-10 17:20:15 +01:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define XDRV_12 12
2021-04-11 12:29:33 +01:00
void TasDiscoverMessage ( void ) {
2022-02-22 14:42:04 +00:00
uint32_t ip_address = ( uint32_t ) WiFi . localIP ( ) ;
char * hostname = TasmotaGlobal . hostname ;
# if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET)
if ( static_cast < uint32_t > ( EthernetLocalIP ( ) ) ! = 0 ) {
ip_address = ( uint32_t ) EthernetLocalIP ( ) ;
hostname = EthernetHostname ( ) ;
}
# endif
2021-04-11 12:29:33 +01:00
Response_P ( PSTR ( " { \" ip \" : \" %_I \" , " // IP Address
" \" dn \" : \" %s \" , " // Device Name
" \" fn \" :[ " ) , // Friendly Names (start)
2022-02-22 14:42:04 +00:00
ip_address ,
2021-04-11 12:29:33 +01:00
SettingsText ( SET_DEVICENAME ) ) ;
uint32_t maxfn = ( TasmotaGlobal . devices_present > MAX_FRIENDLYNAMES ) ? MAX_FRIENDLYNAMES : ( ! TasmotaGlobal . devices_present ) ? 1 : TasmotaGlobal . devices_present ;
for ( uint32_t i = 0 ; i < MAX_FRIENDLYNAMES ; i + + ) {
char fname [ TOPSZ ] ;
snprintf_P ( fname , sizeof ( fname ) , PSTR ( " \" %s \" " ) , EscapeJSONString ( SettingsText ( SET_FRIENDLYNAME1 + i ) ) . c_str ( ) ) ;
ResponseAppend_P ( PSTR ( " %s%s " ) , ( i > 0 ? " , " : " " ) , ( i < maxfn ) ? fname : PSTR ( " null " ) ) ;
}
bool TuyaMod = false ;
2021-12-19 15:41:10 +00:00
# ifdef USE_TUYA_MCU
TuyaMod = IsModuleTuya ( ) ;
# endif
2021-04-11 12:29:33 +01:00
bool iFanMod = false ;
2021-04-10 17:20:15 +01:00
# ifdef ESP8266
2021-12-19 15:41:10 +00:00
iFanMod = ( ( SONOFF_IFAN02 = = TasmotaGlobal . module_type ) | | ( SONOFF_IFAN03 = = TasmotaGlobal . module_type ) ) ;
2021-04-10 17:20:15 +01:00
# endif // ESP8266
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " ], " // Friendly Names (end)
" \" hn \" : \" %s \" , " // Host Name
" \" mac \" : \" %s \" , " // Full MAC as Device id
" \" md \" : \" %s \" , " // Module or Template Name
" \" ty \" :%d, \" if \" :%d, " // Flag for TuyaMCU and Ifan devices
" \" ofln \" : \" " MQTT_LWT_OFFLINE " \" , " // Payload Offline
" \" onln \" : \" " MQTT_LWT_ONLINE " \" , " // Payload Online
" \" state \" :[ \" %s \" , \" %s \" , \" %s \" , \" %s \" ], " // State text for "OFF","ON","TOGGLE","HOLD"
" \" sw \" : \" %s \" , " // Software Version
" \" t \" : \" %s \" , " // Topic
" \" ft \" : \" %s \" , " // Full Topic
" \" tp \" :[ \" %s \" , \" %s \" , \" %s \" ], " // Topics for command, stat and tele
" \" rl \" :[ " ) , // Relays (start)
2022-02-22 14:42:04 +00:00
hostname ,
2021-04-11 12:29:33 +01:00
NetworkUniqueId ( ) . c_str ( ) ,
ModuleName ( ) . c_str ( ) ,
TuyaMod , iFanMod ,
GetStateText ( 0 ) , GetStateText ( 1 ) , GetStateText ( 2 ) , GetStateText ( 3 ) ,
TasmotaGlobal . version ,
TasmotaGlobal . mqtt_topic ,
SettingsText ( SET_MQTT_FULLTOPIC ) ,
2021-08-25 08:41:07 +01:00
SettingsText ( SET_MQTTPREFIX1 ) ,
SettingsText ( SET_MQTTPREFIX2 ) ,
SettingsText ( SET_MQTTPREFIX3 ) ) ;
2021-04-11 12:29:33 +01:00
2021-07-27 14:50:00 +01:00
uint8_t light_idx = MAX_RELAYS + 1 ; // Will store the starting position of the lights
uint8_t light_subtype = 0 ;
bool light_controller_isCTRGBLinked = false ;
# ifdef USE_LIGHT
light_subtype = Light . subtype ;
if ( light_subtype > LST_NONE ) {
light_controller_isCTRGBLinked = light_controller . isCTRGBLinked ( ) ;
if ( ! light_controller_isCTRGBLinked ) { // One or two lights present
light_idx = TasmotaGlobal . devices_present - 2 ;
2021-04-10 17:20:15 +01:00
} else {
2021-07-27 14:50:00 +01:00
light_idx = TasmotaGlobal . devices_present - 1 ;
2021-04-10 17:20:15 +01:00
}
}
2021-06-11 17:14:12 +01:00
if ( ( Light . device > 0 ) & & Settings - > flag3 . pwm_multi_channels ) { // How many relays are light devices?
2021-07-27 14:50:00 +01:00
light_idx = TasmotaGlobal . devices_present - light_subtype ;
2021-04-10 17:20:15 +01:00
}
2021-07-27 14:50:00 +01:00
# endif // USE_LIGHT
2021-04-10 17:20:15 +01:00
2021-04-11 12:29:33 +01:00
uint16_t Relay [ MAX_RELAYS ] = { 0 } ; // Base array to store the relay type
uint16_t Shutter [ MAX_RELAYS ] = { 0 } ; // Array to store a temp list for shutters
2021-04-10 17:20:15 +01:00
for ( uint32_t i = 0 ; i < MAX_RELAYS ; i + + ) {
if ( i < TasmotaGlobal . devices_present ) {
# ifdef USE_SHUTTER
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . shutter_mode ) {
2021-04-10 17:20:15 +01:00
for ( uint32_t k = 0 ; k < MAX_SHUTTERS ; k + + ) {
2021-11-05 08:17:07 +00:00
if ( Settings - > shutter_startrelay [ k ] > 0 ) {
Shutter [ Settings - > shutter_startrelay [ k ] - 1 ] = Shutter [ Settings - > shutter_startrelay [ k ] ] = 1 ;
2021-04-10 17:20:15 +01:00
} else {
2021-11-05 08:17:07 +00:00
// terminate loop at first INVALID Settings->shutter_startrelay[i].
break ;
2021-04-10 17:20:15 +01:00
}
}
}
# endif // USE_SHUTTER
2021-04-11 12:29:33 +01:00
if ( Shutter [ i ] ! = 0 ) { // Check if there are shutters present
Relay [ i ] = 3 ; // Relay is a shutter
2021-04-10 17:20:15 +01:00
} else {
2022-06-10 09:43:36 +01:00
if ( i > = light_idx | | ( iFanMod & & ( 0 = = i ) ) ) { // First relay on Ifan controls the light
2021-04-11 12:29:33 +01:00
Relay [ i ] = 2 ; // Relay is a light
2021-04-10 17:20:15 +01:00
} else {
2021-04-11 12:29:33 +01:00
if ( ! iFanMod ) { // Relays 2-4 for ifan are controlled by FANSPEED and don't need to be present if TasmotaGlobal.module_type = SONOFF_IFAN02 or SONOFF_IFAN03
Relay [ i ] = 1 ; // Simple Relay
2021-04-10 17:20:15 +01:00
}
}
}
}
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " %s%d " ) , ( i > 0 ? " , " : " " ) , Relay [ i ] ) ; // Vector for the Official Integration
2021-04-10 17:20:15 +01:00
}
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " ], " // Relays (end)
" \" swc \" :[ " ) ) ; // Switch modes (start)
2021-04-10 17:20:15 +01:00
2021-04-11 12:29:33 +01:00
// Enable Discovery for Switches only if SetOption114 is enabled
for ( uint32_t i = 0 ; i < MAX_SWITCHES ; i + + ) {
2021-06-11 17:14:12 +01:00
ResponseAppend_P ( PSTR ( " %s%d " ) , ( i > 0 ? " , " : " " ) , ( PinUsed ( GPIO_SWT1 , i ) & & Settings - > flag5 . mqtt_switches ) ? Settings - > switchmode [ i ] : - 1 ) ;
2021-04-10 17:20:15 +01:00
}
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " ], " // Switch modes (end)
" \" swn \" :[ " ) ) ; // Switch names (start)
2021-04-10 17:20:15 +01:00
// Enable Discovery for Switches only if SetOption114 is enabled
for ( uint32_t i = 0 ; i < MAX_SWITCHES ; i + + ) {
char sname [ TOPSZ ] ;
snprintf_P ( sname , sizeof ( sname ) , PSTR ( " \" %s \" " ) , GetSwitchText ( i ) . c_str ( ) ) ;
2021-06-11 17:14:12 +01:00
ResponseAppend_P ( PSTR ( " %s%s " ) , ( i > 0 ? " , " : " " ) , ( PinUsed ( GPIO_SWT1 , i ) & & Settings - > flag5 . mqtt_switches ) ? sname : PSTR ( " null " ) ) ;
2021-04-10 17:20:15 +01:00
}
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " ], " // Switch names (end)
" \" btn \" :[ " ) ) ; // Button flag (start)
2021-04-10 17:20:15 +01:00
bool SerialButton = false ;
// Enable Discovery for Buttons only if SetOption73 is enabled
for ( uint32_t i = 0 ; i < MAX_KEYS ; i + + ) {
# ifdef ESP8266
SerialButton = ( ( 0 = = i ) & & ( SONOFF_DUAL = = TasmotaGlobal . module_type ) ) ;
2021-04-11 12:29:33 +01:00
# endif // ESP8266
2021-06-11 17:14:12 +01:00
ResponseAppend_P ( PSTR ( " %s%d " ) , ( i > 0 ? " , " : " " ) , ( SerialButton ? 1 : ( PinUsed ( GPIO_KEY1 , i ) ) & & Settings - > flag3 . mqtt_buttons ) ) ;
2021-04-10 17:20:15 +01:00
}
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " ], " // Button flag (end)
" \" so \" :{ \" 4 \" :%d, " // SetOptions
" \" 11 \" :%d, "
" \" 13 \" :%d, "
" \" 17 \" :%d, "
" \" 20 \" :%d, "
" \" 30 \" :%d, "
" \" 68 \" :%d, "
" \" 73 \" :%d, "
" \" 82 \" :%d, "
" \" 114 \" :%d, "
" \" 117 \" :%d}, "
" \" lk \" :%d, " // Light CTRGB linked
" \" lt_st \" :%d, " // Light SubType
" \" sho \" :[ " ) , // Shutter Options (start)
2021-06-11 17:14:12 +01:00
Settings - > flag . mqtt_response ,
Settings - > flag . button_swap ,
Settings - > flag . button_single ,
Settings - > flag . decimal_text ,
Settings - > flag . not_power_linked ,
Settings - > flag . hass_light ,
Settings - > flag3 . pwm_multi_channels ,
Settings - > flag3 . mqtt_buttons ,
Settings - > flag4 . alexa_ct_range ,
Settings - > flag5 . mqtt_switches ,
Settings - > flag5 . fade_fixed_duration ,
2021-07-27 14:50:00 +01:00
light_controller_isCTRGBLinked ,
light_subtype ) ;
2021-04-11 12:29:33 +01:00
2021-04-10 17:20:15 +01:00
for ( uint32_t i = 0 ; i < MAX_SHUTTERS ; i + + ) {
# ifdef USE_SHUTTER
2021-06-11 17:14:12 +01:00
ResponseAppend_P ( PSTR ( " %s%d " ) , ( i > 0 ? " , " : " " ) , Settings - > shutter_options [ i ] ) ;
2021-04-10 17:20:15 +01:00
# else
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " %s0 " ) , ( i > 0 ? " , " : " " ) ) ;
2021-04-10 17:20:15 +01:00
# endif // USE_SHUTTER
}
2021-04-11 12:29:33 +01:00
ResponseAppend_P ( PSTR ( " ], " // Shutter Options (end)
2022-05-13 11:28:48 +01:00
" \" sht \" :[ " ) ) ; // Shutter Tilt (start)
for ( uint32_t i = 0 ; i < MAX_SHUTTERS ; i + + ) {
# ifdef USE_SHUTTER
ResponseAppend_P ( PSTR ( " %s[%d,%d,%d] " ) , ( i > 0 ? " , " : " " ) ,
Settings - > shutter_tilt_config [ 0 ] [ i ] ,
Settings - > shutter_tilt_config [ 1 ] [ i ] ,
Settings - > shutter_tilt_config [ 2 ] [ i ] ) ;
# else
ResponseAppend_P ( PSTR ( " %s[0,0,0] " ) , ( i > 0 ? " , " : " " ) ) ;
# endif // USE_SHUTTER
}
ResponseAppend_P ( PSTR ( " ], " // Shutter Tilt (end)
2021-04-11 12:29:33 +01:00
" \" ver \" :1} " ) ) ; // Discovery version
}
void TasDiscovery ( void ) {
TasmotaGlobal . masterlog_level = LOG_LEVEL_DEBUG_MORE ; // Hide topic on clean and remove use weblog 4 to show it
ResponseClear ( ) ; // Clear retained message
2022-06-10 09:43:36 +01:00
if ( ! Settings - > flag . hass_discovery ) { // SetOption19 - Clear retained message
2021-04-11 12:29:33 +01:00
TasDiscoverMessage ( ) ; // Build discovery message
2021-04-10 17:20:15 +01:00
}
char stopic [ TOPSZ ] ;
2021-04-11 12:29:33 +01:00
snprintf_P ( stopic , sizeof ( stopic ) , PSTR ( " tasmota/discovery/%s/config " ) , NetworkUniqueId ( ) . c_str ( ) ) ;
2021-04-10 17:20:15 +01:00
MqttPublish ( stopic , true ) ;
2022-06-10 09:43:36 +01:00
if ( ! Settings - > flag . hass_discovery ) { // SetOption19 - Clear retained message
2021-04-10 17:20:15 +01:00
Response_P ( PSTR ( " { \" sn \" : " ) ) ;
2021-11-21 17:54:13 +00:00
MqttShowSensor ( true ) ;
2021-04-10 17:20:15 +01:00
ResponseAppend_P ( PSTR ( " , \" ver \" :1} " ) ) ;
}
2021-04-11 12:29:33 +01:00
snprintf_P ( stopic , sizeof ( stopic ) , PSTR ( " tasmota/discovery/%s/sensors " ) , NetworkUniqueId ( ) . c_str ( ) ) ;
2021-04-10 17:20:15 +01:00
MqttPublish ( stopic , true ) ;
2021-04-11 12:29:33 +01:00
TasmotaGlobal . masterlog_level = LOG_LEVEL_NONE ; // Restore WebLog state
2021-04-10 17:20:15 +01:00
}
void TasRediscover ( void ) {
2021-05-22 15:25:02 +01:00
TasmotaGlobal . discovery_counter = 1 ; // Delayed discovery or clear retained messages
2021-04-10 17:20:15 +01:00
}
2021-04-11 16:33:22 +01:00
void TasDiscoverInit ( void ) {
2021-04-12 11:20:49 +01:00
if ( ResetReason ( ) ! = REASON_DEEP_SLEEP_AWAKE ) {
2022-06-10 09:43:36 +01:00
Settings - > flag . hass_discovery = 0 ; // SetOption19 - Enable Tasmota discovery and Disable legacy Hass discovery
2021-05-22 15:25:02 +01:00
TasmotaGlobal . discovery_counter = 10 ; // Delayed discovery
2021-04-12 11:20:49 +01:00
}
}
2022-06-10 09:58:32 +01:00
/*********************************************************************************************\
* Stubs replacing legacy Hass discovery
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void HAssPublishStatus ( void ) { return ; }
void HAssDiscover ( void ) { TasRediscover ( ) ; }
2021-04-12 11:20:49 +01:00
/*********************************************************************************************\
* Commands
*
* Discover 0 - Disables discovery and removes retained message from MQTT server
* Discover 1 - Enables discovery ( default )
* DiscoverOff 0 - Enables discovery ( default )
* DiscoverOff 1 - Disables discovery and removes retained message from MQTT server
* DiscoverButton 1 - Enable discovery for buttons
* DiscoverSwitch 1 - Enable discovery for switches
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const char kTasDiscoverCommands [ ] PROGMEM = " Discover| " // Prefix
// SetOption synonyms
" Off|Button|Switch| "
// Commands
" | " ;
SO_SYNONYMS ( kTasDiscoverSynonyms ,
19 , 73 , 114 ) ;
void ( * const TasDiscoverCommand [ ] ) ( void ) PROGMEM = {
& CmndTasDiscover } ;
void CmndTasDiscover ( void ) {
if ( XdrvMailbox . payload > = 0 ) {
2022-06-10 09:58:32 +01:00
Settings - > flag . hass_discovery = ! ( XdrvMailbox . payload & 1 ) ; // SetOption19 - Tasmota discovery (0)
2021-04-12 11:20:49 +01:00
TasRediscover ( ) ;
}
2021-06-11 17:14:12 +01:00
ResponseCmndChar ( GetStateText ( ! Settings - > flag . hass_discovery ) ) ;
2021-04-11 16:33:22 +01:00
}
2021-04-10 17:20:15 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv12 ( uint8_t function ) {
bool result = false ;
2022-06-10 09:43:36 +01:00
if ( Settings - > flag . mqtt_enabled ) { // SetOption3 - Enable MQTT
2021-04-10 17:20:15 +01:00
switch ( function ) {
case FUNC_EVERY_SECOND :
2021-05-11 10:01:46 +01:00
if ( TasmotaGlobal . discovery_counter ) {
TasmotaGlobal . discovery_counter - - ;
if ( ! TasmotaGlobal . discovery_counter ) {
2021-04-11 12:29:33 +01:00
TasDiscovery ( ) ; // Send the topics for discovery
2021-04-10 17:20:15 +01:00
}
}
break ;
2021-04-12 11:20:49 +01:00
case FUNC_COMMAND :
result = DecodeCommand ( kTasDiscoverCommands , TasDiscoverCommand , kTasDiscoverSynonyms ) ;
break ;
2021-05-22 15:25:02 +01:00
case FUNC_MQTT_SUBSCRIBE :
if ( 0 = = Mqtt . initial_connection_state ) {
TasRediscover ( ) ;
}
break ;
2021-04-10 17:20:15 +01:00
case FUNC_MQTT_INIT :
2021-04-11 16:33:22 +01:00
TasDiscoverInit ( ) ;
2021-04-10 17:20:15 +01:00
break ;
}
}
return result ;
}
2021-11-05 08:17:07 +00:00
# endif // USE_TASMOTA_DISCOVERY