diff --git a/CHANGELOG.md b/CHANGELOG.md index a0222c54e..d62165be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. - WDT reset on shutters with stepper motors during deceleration (#12849) - Shelly 2.5 negative power values on relay 1 regression from 9.5.0.5 - Wiegand support for keypad zero key in single key mode using ``SetOption124 1`` (#12960) +- Hass and Tasmota discovery prefix topic notifications (#12972) ## [9.5.0.6] 20210820 ### Added diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5961f38a0..0f236ebfd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -170,3 +170,4 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo - WDT reset on shutters with stepper motors during deceleration [#12849](https://github.com/arendst/Tasmota/issues/12849) - Negative power values for ADE7953 based devices like Shelly EM [#12874](https://github.com/arendst/Tasmota/issues/12874) - Wiegand support for keypad zero key in single key mode using ``SetOption124 1`` [#12960](https://github.com/arendst/Tasmota/issues/12960) +- Hass and Tasmota discovery prefix topic notifications [#12972](https://github.com/arendst/Tasmota/issues/12972) diff --git a/tasmota/xdrv_12_discovery.ino b/tasmota/xdrv_12_discovery.ino index f6ab5a425..1f07488a8 100644 --- a/tasmota/xdrv_12_discovery.ino +++ b/tasmota/xdrv_12_discovery.ino @@ -75,9 +75,9 @@ void TasDiscoverMessage(void) { TasmotaGlobal.version, TasmotaGlobal.mqtt_topic, SettingsText(SET_MQTT_FULLTOPIC), - PSTR(SUB_PREFIX), - PSTR(PUB_PREFIX), - PSTR(PUB_PREFIX2)); + SettingsText(SET_MQTTPREFIX1), + SettingsText(SET_MQTTPREFIX2), + SettingsText(SET_MQTTPREFIX3)); uint8_t light_idx = MAX_RELAYS + 1; // Will store the starting position of the lights uint8_t light_subtype = 0; diff --git a/tasmota/xdrv_12_home_assistant.ino b/tasmota/xdrv_12_home_assistant.ino index ed5aeb89a..5fb759f15 100644 --- a/tasmota/xdrv_12_home_assistant.ino +++ b/tasmota/xdrv_12_home_assistant.ino @@ -186,60 +186,74 @@ uint8_t hass_mode = 0; int hass_tele_period = 0; // NEW DISCOVERY +void HassDiscoverMessage(void) { + Response_P(PSTR("{\"ip\":\"%_I\"," // IP Address + "\"dn\":\"%s\"," // Device Name + "\"fn\":["), // Friendly Names (start) + (uint32_t)WiFi.localIP(), + SettingsText(SET_DEVICENAME)); -const char HASS_DISCOVER_DEVICE[] PROGMEM = // Basic parameters for Discovery - "{\"ip\":\"%_I\"," // IP Address - "\"dn\":\"%s\"," // Device Name - "\"fn\":[%s]," // Friendly Names - "\"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\":[%s],\"swc\":[%s],\"swn\":[%s],\"btn\":[%s]," // Inputs / Outputs - "\"so\":{\"4\":%d,\"11\":%d,\"13\":%d,\"17\":%d,\"20\":%d," // SetOptions - "\"30\":%d,\"68\":%d,\"73\":%d,\"82\":%d,\"114\":%d,\"117\":%d}," - "\"lk\":%d,\"lt_st\":%d,\"sho\":[%s],\"ver\":1}"; // Light SubType, Shutter Options and Discovery version - -typedef struct HASS { - uint16_t Relay[MAX_RELAYS]; // Base array to store the relay type - char RelLst[MAX_RELAYS*2]; // Relay as a char list, "0,0,0,0,0,0,0,0" - bool RelPst; // Needed for Switches. If Power devices are not present entities will be created even when switchtopic in not set. -} HASS; - -void HassDiscoveryRelays(struct HASS &Hass) -{ - Hass = {.Relay={0,0,0,0,0,0,0,0}, .RelLst={'\0'}}; - uint16_t Shutter[MAX_RELAYS] = { 0 }; // Array to store a temp list for shutters - uint8_t lightidx = MAX_RELAYS + 1; // Will store the starting position of the lights - bool iFan = false; - - Hass.RelPst = TasmotaGlobal.devices_present > 0; + 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; + bool iFanMod = false; #ifdef ESP8266 - if (SONOFF_IFAN02 == TasmotaGlobal.module_type || SONOFF_IFAN03 == TasmotaGlobal.module_type) { iFan = true;} -#endif // ESP8266 + if ((TUYA_DIMMER == TasmotaGlobal.module_type) || (SK03_TUYA == TasmotaGlobal.module_type)) { TuyaMod = true; }; + if ((SONOFF_IFAN02 == TasmotaGlobal.module_type) || (SONOFF_IFAN03 == TasmotaGlobal.module_type)) { iFanMod = true; }; +#endif // ESP8266 - if (Light.subtype > LST_NONE) { - if (!light_controller.isCTRGBLinked()) { // One or two lights present - lightidx = TasmotaGlobal.devices_present - 2; + 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) + TasmotaGlobal.hostname, + 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), + SettingsText(SET_MQTTPREFIX1), + SettingsText(SET_MQTTPREFIX2), + SettingsText(SET_MQTTPREFIX3)); + + 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; } else { - lightidx = TasmotaGlobal.devices_present - 1; + light_idx = TasmotaGlobal.devices_present - 1; } } - if (Light.device > 0 && Settings->flag3.pwm_multi_channels) { // How many relays are light devices? - lightidx = TasmotaGlobal.devices_present - Light.subtype; + if ((Light.device > 0) && Settings->flag3.pwm_multi_channels) { // How many relays are light devices? + light_idx = TasmotaGlobal.devices_present - light_subtype; } +#endif // USE_LIGHT + 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 for (uint32_t i = 0; i < MAX_RELAYS; i++) { - if (i < TasmotaGlobal.devices_present) { #ifdef USE_SHUTTER @@ -254,112 +268,115 @@ void HassDiscoveryRelays(struct HASS &Hass) } } } -#endif // USE_SHUTTER +#endif // USE_SHUTTER - if (Shutter[i] != 0) { // Check if there are shutters present - Hass.Relay[i] = 3; // Relay is a shutter + if (Shutter[i] != 0) { // Check if there are shutters present + Relay[i] = 3; // Relay is a shutter } else { - if (i >= lightidx || (iFan && i == 0)) { // First relay on Ifan controls the light - Hass.Relay[i] = 2; // Relay is a light + if (i >= light_idx || (iFanMod && (0 == i))) { // First relay on Ifan controls the light + Relay[i] = 2; // Relay is a light } else { - if (!iFan) { // 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 - Hass.Relay[i] = 1; // Simple Relay + 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 } } } } - snprintf_P(Hass.RelLst, sizeof(Hass.RelLst), PSTR("%s%s%d"), Hass.RelLst, (i > 0 ? "," : ""), Hass.Relay[i]); // Vector for the Official Integration - } -} - -void NewHAssDiscovery(void) -{ - char stopic[TOPSZ]; - char stemp1[TOPSZ]; - char stemp2[200]; - char switch_mode[90]; - char switch_name[300]; - char stemp5[90]; - char stemp6[90]; - char unique_id[30]; - char relays[TOPSZ]; - char *state_topic = stemp1; - bool SerialButton = false; - bool TuyaMod = false; - bool iFanMod = false; - - stemp2[0] = '\0'; - struct HASS Hass; - HassDiscoveryRelays(Hass); - -#ifdef ESP8266 - if (TUYA_DIMMER == TasmotaGlobal.module_type || SK03_TUYA == TasmotaGlobal.module_type) { TuyaMod = true; } - if (SONOFF_IFAN02 == TasmotaGlobal.module_type || SONOFF_IFAN03 == TasmotaGlobal.module_type) { iFanMod = true; } -#endif // ESP8266 - - 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()); - snprintf_P(stemp2, sizeof(stemp2), PSTR("%s%s%s"), stemp2, (i > 0 ? "," : ""), (i < maxfn) ? fname : PSTR("null")); + ResponseAppend_P(PSTR("%s%d"), (i > 0 ? "," : ""), Relay[i]); // Vector for the Official Integration } - switch_mode[0] = '\0'; - switch_name[0] = '\0'; + ResponseAppend_P(PSTR("]," // Relays (end) + "\"swc\":[")); // Switch modes (start) + + // Enable Discovery for Switches only if SetOption114 is enabled + for (uint32_t i = 0; i < MAX_SWITCHES; i++) { + ResponseAppend_P(PSTR("%s%d"), (i > 0 ? "," : ""), (PinUsed(GPIO_SWT1, i) && Settings->flag5.mqtt_switches) ? Settings->switchmode[i] : -1); + } + + ResponseAppend_P(PSTR("]," // Switch modes (end) + "\"swn\":[")); // Switch names (start) + // 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()); - snprintf_P(switch_mode, sizeof(switch_mode), PSTR("%s%s%d"), switch_mode, (i > 0 ? "," : ""), (PinUsed(GPIO_SWT1, i) & Settings->flag5.mqtt_switches) ? Settings->switchmode[i] : -1); - snprintf_P(switch_name, sizeof(switch_name), PSTR("%s%s%s"), switch_name, (i > 0 ? "," : ""), (PinUsed(GPIO_SWT1, i) & Settings->flag5.mqtt_switches) ? sname : PSTR("null")); + ResponseAppend_P(PSTR("%s%s"), (i > 0 ? "," : ""), (PinUsed(GPIO_SWT1, i) && Settings->flag5.mqtt_switches) ? sname : PSTR("null")); } - stemp5[0] = '\0'; + ResponseAppend_P(PSTR("]," // Switch names (end) + "\"btn\":[")); // Button flag (start) + + bool SerialButton = false; // Enable Discovery for Buttons only if SetOption73 is enabled for (uint32_t i = 0; i < MAX_KEYS; i++) { #ifdef ESP8266 - if (i == 0 && (SONOFF_DUAL == TasmotaGlobal.module_type )) { SerialButton = true; } -#endif // ESP8266 - snprintf_P(stemp5, sizeof(stemp5), PSTR("%s%s%d"), stemp5, (i > 0 ? "," : ""), (SerialButton ? 1 : (PinUsed(GPIO_KEY1, i)) & Settings->flag3.mqtt_buttons)); - SerialButton = false; + SerialButton = ((0 == i) && (SONOFF_DUAL == TasmotaGlobal.module_type )); +#endif // ESP8266 + ResponseAppend_P(PSTR("%s%d"), (i > 0 ? "," : ""), (SerialButton ? 1 : (PinUsed(GPIO_KEY1, i)) && Settings->flag3.mqtt_buttons)); } - stemp6[0] = '\0'; -#ifdef USE_SHUTTER + + 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) + 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, + light_controller_isCTRGBLinked, + light_subtype); + for (uint32_t i = 0; i < MAX_SHUTTERS; i++) { - snprintf_P(stemp6, sizeof(stemp6), PSTR("%s%s%d"), stemp6, (i > 0 ? "," : ""), Settings->shutter_options[i]); - } +#ifdef USE_SHUTTER + ResponseAppend_P(PSTR("%s%d"), (i > 0 ? "," : ""), Settings->shutter_options[i]); #else - snprintf_P(stemp6, sizeof(stemp6), PSTR("0,0,0,0")); -#endif // USE_SHUTTER - - ResponseClear(); // Clear retained message - - // Full 12 chars MAC address as ID - snprintf_P(unique_id, sizeof(unique_id), PSTR("%s"), NetworkUniqueId().c_str()); - snprintf_P(stopic, sizeof(stopic), PSTR("tasmota/discovery/%s/config"), unique_id); - - // Send empty message if new discovery is disabled - TasmotaGlobal.masterlog_level = 4; // Hide topic on clean and remove use weblog 4 to show it - if (!Settings->flag.hass_discovery) { // HassDiscoveryRelays(relays) - Response_P(HASS_DISCOVER_DEVICE, (uint32_t)WiFi.localIP(), SettingsText(SET_DEVICENAME), - stemp2, TasmotaGlobal.hostname, unique_id, ModuleName().c_str(), TuyaMod, iFanMod, GetStateText(0), GetStateText(1), GetStateText(2), GetStateText(3), - TasmotaGlobal.version, TasmotaGlobal.mqtt_topic, SettingsText(SET_MQTT_FULLTOPIC), PSTR(SUB_PREFIX), PSTR(PUB_PREFIX), PSTR(PUB_PREFIX2), Hass.RelLst, switch_mode, switch_name, - stemp5, 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, light_controller.isCTRGBLinked(), Light.subtype, stemp6); + ResponseAppend_P(PSTR("%s0"), (i > 0 ? "," : "")); +#endif // USE_SHUTTER } + + ResponseAppend_P(PSTR("]," // Shutter Options (end) + "\"ver\":1}")); // Discovery version +} + +void NewHAssDiscovery(void) { + TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide topic on clean and remove use weblog 4 to show it + + ResponseClear(); // Clear retained message + if (!Settings->flag.hass_discovery) { // SetOption19 - Clear retained message + HassDiscoverMessage(); // Build discovery message + } + char stopic[TOPSZ]; + snprintf_P(stopic, sizeof(stopic), PSTR("tasmota/discovery/%s/config"), NetworkUniqueId().c_str()); MqttPublish(stopic, true); - if (!Settings->flag.hass_discovery) { - snprintf_P(stopic, sizeof(stopic), PSTR("tasmota/discovery/%s/sensors"), unique_id); + if (!Settings->flag.hass_discovery) { // SetOption19 - Clear retained message Response_P(PSTR("{\"sn\":")); MqttShowSensor(); ResponseAppend_P(PSTR(",\"ver\":1}")); - MqttPublish(stopic, true); } - TasmotaGlobal.masterlog_level = 0; // Restore WebLog state -} + snprintf_P(stopic, sizeof(stopic), PSTR("tasmota/discovery/%s/sensors"), NetworkUniqueId().c_str()); + MqttPublish(stopic, true); + TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Restore WebLog state +} // NEW DISCOVERY void TryResponseAppend_P(const char *format, ...) {