diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index 5583d95fc..4b0a6d335 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -2,6 +2,8 @@ * Add 16 timers using commands Timer and Timers (#1091) * Add commands Timer 0 to clear timer and Timer 1..16 to copy timer * Add optional Timer configuration webpage to be enabled in user_config.h with define USE_TIMERS_WEB + * Change MQTT response topic for Energy changes from ENERGY to SENSOR (#2229, #2251) + * Add Home Assistant MQTT Discovery for Buttons and change SetOption19 response (#2277) * Change webpage parameter communication * * 5.12.0h diff --git a/sonoff/settings.ino b/sonoff/settings.ino index d17f33d35..e1b0a7dcd 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -494,7 +494,7 @@ void SettingsDefaultSet2() strlcpy(Settings.mqtt_user, MQTT_USER, sizeof(Settings.mqtt_user)); strlcpy(Settings.mqtt_pwd, MQTT_PASS, sizeof(Settings.mqtt_pwd)); strlcpy(Settings.mqtt_topic, MQTT_TOPIC, sizeof(Settings.mqtt_topic)); - strlcpy(Settings.button_topic, "0", sizeof(Settings.button_topic)); + strlcpy(Settings.button_topic, MQTT_BUTTON_TOPIC, sizeof(Settings.button_topic)); strlcpy(Settings.mqtt_grptopic, MQTT_GRPTOPIC, sizeof(Settings.mqtt_grptopic)); Settings.tele_period = TELE_PERIOD; @@ -544,7 +544,7 @@ void SettingsDefaultSet2() SettingsDefaultSet_3_9_3(); - strlcpy(Settings.switch_topic, "0", sizeof(Settings.switch_topic)); + strlcpy(Settings.switch_topic, MQTT_SWITCH_TOPIC, sizeof(Settings.switch_topic)); strlcpy(Settings.web_password, WEB_PASSWORD, sizeof(Settings.web_password)); @@ -744,7 +744,7 @@ void SettingsDelta() SettingsDefaultSet_3_2_4(); } if (Settings.version < 0x03020500) { // 3.2.5 - Add parameter - GetMqttClient(Settings.friendlyname[0], Settings.mqtt_client, sizeof(Settings.friendlyname[0])); + Format(Settings.friendlyname[0], Settings.mqtt_client, sizeof(Settings.friendlyname[0])); strlcpy(Settings.friendlyname[1], FRIENDLY_NAME"2", sizeof(Settings.friendlyname[1])); strlcpy(Settings.friendlyname[2], FRIENDLY_NAME"3", sizeof(Settings.friendlyname[2])); strlcpy(Settings.friendlyname[3], FRIENDLY_NAME"4", sizeof(Settings.friendlyname[3])); diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 6aef6181a..a051ccac3 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -196,7 +196,7 @@ String backlog[MAX_BACKLOG]; // Command backlog /********************************************************************************************/ -char* GetMqttClient(char* output, const char* input, int size) +char* Format(char* output, const char* input, int size) { char *token; uint8_t digits = 0; @@ -572,7 +572,7 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len) switch (index) { case 3: // mqtt case 15: // pwm_control - case 19: // hass_discovery +// case 19: // hass_discovery restart_flag = 2; case 0: // save_state case 1: // button_restrict @@ -587,6 +587,7 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len) case 16: // ws_clock_reverse case 17: // decimal_text case 18: // light_signal + case 19: // hass_discovery case 20: // not_power_linked case 21: // no_power_on_check bitWrite(Settings.flag.data, index, payload); @@ -595,6 +596,11 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len) stop_flash_rotate = payload; SettingsSave(2); } +#ifdef USE_HOME_ASSISTANT + if (19 == index) { // hass_discovery + HAssDiscovery(1); + } +#endif // USE_HOME_ASSISTANT } } else { // SetOption32 .. @@ -1107,9 +1113,11 @@ boolean send_button_power(byte key, byte device, byte state) char stopic[TOPSZ]; char scommand[CMDSZ]; + char key_topic[sizeof(Settings.button_topic)]; boolean result = false; - char *key_topic = (key) ? Settings.switch_topic : Settings.button_topic; + char *tmp = (key) ? Settings.switch_topic : Settings.button_topic; + Format(key_topic, tmp, sizeof(key_topic)); if (Settings.flag.mqtt_enabled && MqttIsConnected() && (strlen(key_topic) != 0) && strcmp(key_topic, "0")) { if (!key && (device > devices_present)) device = 1; GetTopic_P(stopic, CMND, key_topic, GetPowerDevice(scommand, device, sizeof(scommand), key)); @@ -2334,8 +2342,8 @@ void setup() SetSerialBaudrate(baudrate); - GetMqttClient(mqtt_client, Settings.mqtt_client, sizeof(mqtt_client)); - GetMqttClient(mqtt_topic, Settings.mqtt_topic, sizeof(mqtt_topic)); + Format(mqtt_client, Settings.mqtt_client, sizeof(mqtt_client)); + Format(mqtt_topic, Settings.mqtt_topic, sizeof(mqtt_topic)); if (strstr(Settings.hostname, "%")) { strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname)); diff --git a/sonoff/user_config.h b/sonoff/user_config.h index e84ad19f7..76d38ce21 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -126,6 +126,8 @@ // %topic% token options (also ButtonTopic and SwitchTopic) #define MQTT_TOPIC PROJECT // [Topic] (unique) MQTT device topic #define MQTT_GRPTOPIC "sonoffs" // [GroupTopic] MQTT Group topic +#define MQTT_BUTTON_TOPIC "0" // [ButtonTopic] MQTT button topic +#define MQTT_SWITCH_TOPIC "0" // [SwitchTopic] MQTT switch topic #define MQTT_CLIENT_ID "DVES_%06X" // [MqttClient] Also fall back topic using Chip Id = last 6 characters of MAC address // -- MQTT - Telemetry ---------------------------- diff --git a/sonoff/webserver.ino b/sonoff/webserver.ino index 79da30741..a7593070c 100644 --- a/sonoff/webserver.ino +++ b/sonoff/webserver.ino @@ -889,7 +889,7 @@ void HandleMqttConfiguration() page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_MQTT); char str[sizeof(Settings.mqtt_client)]; - page.replace(F("{m0"), GetMqttClient(str, MQTT_CLIENT_ID, sizeof(Settings.mqtt_client))); + page.replace(F("{m0"), Format(str, MQTT_CLIENT_ID, sizeof(Settings.mqtt_client))); page.replace(F("{m1"), Settings.mqtt_host); page.replace(F("{m2"), String(Settings.mqtt_port)); page.replace(F("{m3"), Settings.mqtt_client); diff --git a/sonoff/xdrv_00_mqtt.ino b/sonoff/xdrv_00_mqtt.ino index 6a87f1f25..c1f2867da 100644 --- a/sonoff/xdrv_00_mqtt.ino +++ b/sonoff/xdrv_00_mqtt.ino @@ -637,7 +637,7 @@ bool MqttCommand() if ((data_len > 0) && (data_len < sizeof(Settings.button_topic))) { MakeValidMqtt(0, dataBuf); if (!strcmp(dataBuf, mqtt_client)) payload = 1; - strlcpy(Settings.button_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? mqtt_topic : dataBuf, sizeof(Settings.button_topic)); + strlcpy(Settings.button_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_BUTTON_TOPIC : dataBuf, sizeof(Settings.button_topic)); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.button_topic); } @@ -645,7 +645,7 @@ bool MqttCommand() if ((data_len > 0) && (data_len < sizeof(Settings.switch_topic))) { MakeValidMqtt(0, dataBuf); if (!strcmp(dataBuf, mqtt_client)) payload = 1; - strlcpy(Settings.switch_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? mqtt_topic : dataBuf, sizeof(Settings.switch_topic)); + strlcpy(Settings.switch_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_SWITCH_TOPIC : dataBuf, sizeof(Settings.switch_topic)); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.switch_topic); } diff --git a/sonoff/xdrv_03_energy.ino b/sonoff/xdrv_03_energy.ino index 8867ed298..a1052a077 100644 --- a/sonoff/xdrv_03_energy.ino +++ b/sonoff/xdrv_03_energy.ino @@ -778,7 +778,7 @@ void EnergyMqttShow() snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str()); EnergyShow(1); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); - MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), Settings.flag.mqtt_sensor_retain); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); energy_power_delta = 0; } diff --git a/sonoff/xdrv_07_home_assistant.ino b/sonoff/xdrv_07_home_assistant.ino index eaec8686e..3f8e001be 100644 --- a/sonoff/xdrv_07_home_assistant.ino +++ b/sonoff/xdrv_07_home_assistant.ino @@ -31,6 +31,16 @@ const char HASS_DISCOVER_SWITCH[] PROGMEM = "\"payload_available\":\"" D_ONLINE "\"," // Online "\"payload_not_available\":\"" D_OFFLINE "\""; // Offline +const char HASS_DISCOVER_BUTTON[] PROGMEM = + "{\"name\":\"%s\"," // dualr2 1 BTN + "\"state_topic\":\"%s\"," // cmnd/dualr2/POWER (implies "\"optimistic\":\"false\",") +// "\"value_template\":\"{{value_json.%s}}\"," // POWER2 + "\"payload_on\":\"%s\"," // TOGGLE +// "\"optimistic\":\"false\"," // false is Hass default when state_topic is set + "\"availability_topic\":\"%s\"," // tele/dualr2/LWT + "\"payload_available\":\"" D_ONLINE "\"," // Online + "\"payload_not_available\":\"" D_OFFLINE "\""; // Offline + const char HASS_DISCOVER_LIGHT_DIMMER[] PROGMEM = "%s,\"brightness_command_topic\":\"%s\"," // cmnd/led2/Dimmer "\"brightness_state_topic\":\"%s\"," // stat/led2/RESULT @@ -55,32 +65,26 @@ const char HASS_DISCOVER_LIGHT_SCHEME[] PROGMEM = "\"effect_value_template\":\"{{value_json." D_CMND_SCHEME "}}\"," "\"effect_list\":[\"0\",\"1\",\"2\",\"3\",\"4\"]"; // string list with reference to scheme parameter. Currently only supports numbers 0 to 11 as it make the mqtt string too long */ -void HAssDiscovery() + +void HAssDiscoverRelay() { char sidx[8]; char stopic[TOPSZ]; bool is_light = false; - // Configure Tasmota for default Home Assistant parameters to keep discovery message as short as possible - if (Settings.flag.hass_discovery) { - Settings.flag.mqtt_response = 0; // Response always as RESULT and not as uppercase command - Settings.flag.decimal_text = 1; // Respond with decimal color values -// Settings.light_scheme = 0; // To just control color it needs to be Scheme 0 -// strncpy_P(Settings.mqtt_fulltopic, PSTR("%prefix%/%topic%/"), sizeof(Settings.mqtt_fulltopic)); // Make MQTT topic as short as possible to make this process posible within MQTT_MAX_PACKET_SIZE - } - - for (int i = 1; i <= devices_present; i++) { + for (int i = 1; i <= MAX_RELAYS; i++) { is_light = ((i == devices_present) && (light_type)); - mqtt_data[0] = '\0'; + mqtt_data[0] = '\0'; // Clear retained message snprintf_P(sidx, sizeof(sidx), PSTR("_%d"), i); // Clear "other" topic first in case the device has been reconfigured snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s%s/config"), (is_light) ? "switch" : "light", mqtt_topic, sidx); MqttPublish(stopic, true); + // Clear or Set topic snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s%s/config"), (is_light) ? "light" : "switch", mqtt_topic, sidx); - if (Settings.flag.hass_discovery) { + if (Settings.flag.hass_discovery && (i <= devices_present)) { char name[33]; char value_template[33]; char command_topic[TOPSZ]; @@ -129,6 +133,77 @@ void HAssDiscovery() } } +void HAssDiscoverButton() +{ + char sidx[8]; + char stopic[TOPSZ]; + char key_topic[sizeof(Settings.button_topic)]; + + // Send info about buttons + char *tmp = Settings.button_topic; + Format(key_topic, tmp, sizeof(key_topic)); + if ((strlen(key_topic) != 0) && strcmp(key_topic, "0")) { + for (byte button_index = 0; button_index < MAX_KEYS; button_index++) { + uint8_t button_present = 0; + + if (!button_index && ((SONOFF_DUAL == Settings.module) || (CH4 == Settings.module))) { + button_present = 1; + } else { + if (pin[GPIO_KEY1 + button_index] < 99) { + button_present = 1; + } + } + + mqtt_data[0] = '\0'; // Clear retained message + + // Clear or Set topic + snprintf_P(sidx, sizeof(sidx), PSTR("_%d"), button_index+1); + snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s%s/config"), "binary_sensor", key_topic, sidx); + + if (Settings.flag.hass_discovery && button_present) { + char name[33]; + char value_template[33]; + char state_topic[TOPSZ]; + char availability_topic[TOPSZ]; + + if (button_index+1 > MAX_FRIENDLYNAMES) { + snprintf_P(name, sizeof(name), PSTR("%s %d BTN"), Settings.friendlyname[0], button_index+1); + } else { + snprintf_P(name, sizeof(name), PSTR("%s BTN"), Settings.friendlyname[button_index]); + } + GetPowerDevice(value_template, button_index+1, sizeof(value_template)); + GetTopic_P(state_topic, CMND, key_topic, value_template); // State of button is sent as CMND TOGGLE + GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); + snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_BUTTON, name, state_topic, Settings.state_text[2], availability_topic); + + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); + } + MqttPublish(stopic, true); + } + } +} + +void HAssDiscovery(uint8_t mode) +{ + // Configure Tasmota for default Home Assistant parameters to keep discovery message as short as possible + if (Settings.flag.hass_discovery) { + Settings.flag.mqtt_response = 0; // Response always as RESULT and not as uppercase command + Settings.flag.decimal_text = 1; // Respond with decimal color values +// Settings.light_scheme = 0; // To just control color it needs to be Scheme 0 +// strncpy_P(Settings.mqtt_fulltopic, PSTR("%prefix%/%topic%/"), sizeof(Settings.mqtt_fulltopic)); // Make MQTT topic as short as possible to make this process posible within MQTT_MAX_PACKET_SIZE + } + + if (Settings.flag.hass_discovery || (1 == mode)) { + // Send info about relays and lights + HAssDiscoverRelay(); + // Send info about buttons + HAssDiscoverButton(); + // TODO: Send info about switches + + // TODO: Send info about sensors + } +} + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -142,7 +217,7 @@ boolean Xdrv07(byte function) if (Settings.flag.mqtt_enabled) { switch (function) { case FUNC_MQTT_INIT: - HAssDiscovery(); + HAssDiscovery(0); break; } }