Fix Hass and Tasmota discovery

Fix Hass and Tasmota discovery prefix topic notifications (#12972)
This commit is contained in:
Theo Arends 2021-08-25 09:41:07 +02:00
parent 032c2fed96
commit 6e2a3f5d60
4 changed files with 142 additions and 123 deletions

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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, ...) {