xdrv_12_home_assistant.ino 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. /*
  2. xdrv_12_home_assistant.ino - home assistant support for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_HOME_ASSISTANT
  16. #define XDRV_12 12
  17. const char HASS_DISCOVER_RELAY[] PROGMEM =
  18. "{\"name\":\"%s\"," // dualr2 1
  19. "\"cmd_t\":\"%s\"," // cmnd/dualr2/POWER2
  20. "\"stat_t\":\"%s\"," // stat/dualr2/RESULT (implies "\"optimistic\":\"false\",")
  21. "\"val_tpl\":\"{{value_json.%s}}\"," // POWER2
  22. "\"pl_off\":\"%s\"," // OFF
  23. "\"pl_on\":\"%s\"," // ON
  24. // "\"optimistic\":\"false\"," // false is Hass default when state_topic is set
  25. "\"avty_t\":\"%s\"," // tele/dualr2/LWT
  26. "\"pl_avail\":\"" D_ONLINE "\"," // Online
  27. "\"pl_not_avail\":\"" D_OFFLINE "\""; // Offline
  28. const char HASS_DISCOVER_BUTTON_SWITCH[] PROGMEM =
  29. "{\"name\":\"%s\"," // dualr2 1 BTN
  30. "\"stat_t\":\"%s\"," // cmnd/dualr2/POWER (implies "\"optimistic\":\"false\",")
  31. // "\"value_template\":\"{{value_json.%s}}\"," // POWER2
  32. "\"pl_on\":\"%s\"," // TOGGLE
  33. // "\"optimistic\":\"false\"," // false is Hass default when state_topic is set
  34. "\"avty_t\":\"%s\"," // tele/dualr2/LWT
  35. "\"pl_avail\":\"" D_ONLINE "\"," // Online
  36. "\"pl_not_avail\":\"" D_OFFLINE "\""; // Offline
  37. const char HASS_DISCOVER_BUTTON_SWITCH_TOGGLE[] PROGMEM =
  38. "%s,\"off_delay\":1"; // Hass has no support for TOGGLE, fake it by resetting to OFF after 1s
  39. const char HASS_DISCOVER_BUTTON_SWITCH_ONOFF[] PROGMEM =
  40. "%s,\"frc_upd\":true," // In ON/OFF case, enable force_update to make automations work
  41. "\"pl_off\":\"%s\""; // OFF
  42. const char HASS_DISCOVER_LIGHT_DIMMER[] PROGMEM =
  43. "%s,\"bri_cmd_t\":\"%s\"," // cmnd/led2/Dimmer
  44. "\"bri_stat_t\":\"%s\"," // stat/led2/RESULT
  45. "\"bri_scl\":100," // 100%
  46. "\"on_cmd_type\":\"brightness\"," // power on (first), power on (last), no power on (brightness)
  47. "\"bri_val_tpl\":\"{{value_json." D_CMND_DIMMER "}}\"";
  48. const char HASS_DISCOVER_LIGHT_COLOR[] PROGMEM =
  49. "%s,\"rgb_cmd_t\":\"%s2\"," // cmnd/led2/Color2
  50. "\"rgb_stat_t\":\"%s\"," // stat/led2/RESULT
  51. "\"rgb_val_tpl\":\"{{value_json." D_CMND_COLOR ".split(',')[0:3]|join(',')}}\"";
  52. const char HASS_DISCOVER_LIGHT_WHITE[] PROGMEM =
  53. "%s,\"whit_val_cmd_t\":\"%s\"," // cmnd/led2/White
  54. "\"whit_val_stat_t\":\"%s\"," // stat/led2/RESULT
  55. "\"white_value_scale\":100," // (No abbreviation defined)
  56. "\"whit_val_tpl\":\"{{ value_json.Channel[3] }}\"";
  57. const char HASS_DISCOVER_LIGHT_CT[] PROGMEM =
  58. "%s,\"clr_temp_cmd_t\":\"%s\"," // cmnd/led2/CT
  59. "\"clr_temp_stat_t\":\"%s\"," // stat/led2/RESULT
  60. "\"clr_temp_val_tpl\":\"{{value_json." D_CMND_COLORTEMPERATURE "}}\"";
  61. const char HASS_DISCOVER_LIGHT_SCHEME[] PROGMEM =
  62. "%s,\"fx_cmd_t\":\"%s\"," // cmnd/led2/Scheme
  63. "\"fx_stat_t\":\"%s\"," // stat/led2/RESULT
  64. "\"fx_val_tpl\":\"{{value_json." D_CMND_SCHEME "}}\","
  65. "\"fx_list\":[\"0\",\"1\",\"2\",\"3\",\"4\"]"; // string list with reference to scheme parameter.
  66. const char HASS_DISCOVER_SENSOR[] PROGMEM =
  67. "{\"name\":\"%s\"," // dualr2 1 BTN
  68. "\"stat_t\":\"%s\"," // cmnd/dualr2/POWER (implies "\"optimistic\":\"false\",")
  69. "\"avty_t\":\"%s\"," // tele/dualr2/LWT
  70. "\"pl_avail\":\"" D_ONLINE "\"," // Online
  71. "\"pl_not_avail\":\"" D_OFFLINE "\""; // Offline
  72. const char HASS_DISCOVER_SENSOR_TEMP[] PROGMEM =
  73. "%s,\"unit_of_meas\":\"°%c\"," // °C / °F
  74. "\"val_tpl\":\"{{value_json['%s'].Temperature}}\""; // "SI7021-14":{"Temperature":null,"Humidity":null} -> {{ value_json['SI7021-14'].Temperature }}
  75. const char HASS_DISCOVER_SENSOR_HUM[] PROGMEM =
  76. "%s,\"unit_of_meas\":\"%%\"," // %
  77. "\"val_tpl\":\"{{value_json['%s'].Humidity}}\"," // "SI7021-14":{"Temperature":null,"Humidity":null} -> {{ value_json['SI7021-14'].Humidity }}
  78. "\"dev_cla\":\"humidity\""; // humidity
  79. const char HASS_DISCOVER_SENSOR_PRESS[] PROGMEM =
  80. "%s,\"unit_of_meas\":\"%s\"," // PressureUnit() setting
  81. "\"val_tpl\":\"{{value_json['%s'].Pressure}}\"," // "BME280":{"Temperature":19.7,"Humidity":27.8,"Pressure":990.1} -> {{ value_json['BME280'].Pressure }}
  82. "\"dev_cla\":\"pressure\""; // pressure
  83. //ENERGY
  84. const char HASS_DISCOVER_SENSOR_KWH[] PROGMEM =
  85. "%s,\"unit_of_meas\":\"kWh\"," // kWh
  86. "\"val_tpl\":\"{{value_json['%s'].%s}}\""; // "ENERGY":{"TotalStartTime":null,"Total":null,"Yesterday":null,"Today":null,"Power":null,"ApparentPower":null,"ReactivePower":null,"Factor":null,"Voltage":null,"Current":null} -> {{ value_json['ENERGY'].Total/Yesterday/Today }}
  87. const char HASS_DISCOVER_SENSOR_WATT[] PROGMEM =
  88. "%s,\"unit_of_meas\":\"W\"," // W
  89. "\"val_tpl\":\"{{value_json['%s'].%s}}\""; // "ENERGY":{"TotalStartTime":null,"Total":null,"Yesterday":null,"Today":null,"Power":null,"ApparentPower":null,"ReactivePower":null,"Factor":null,"Voltage":null,"Current":null} -> {{ value_json['ENERGY'].POWER }}
  90. const char HASS_DISCOVER_SENSOR_VOLTAGE[] PROGMEM =
  91. "%s,\"unit_of_meas\":\"V\"," // V
  92. "\"val_tpl\":\"{{value_json['%s'].%s}}\""; // "ENERGY":{"TotalStartTime":null,"Total":null,"Yesterday":null,"Today":null,"Power":null,"ApparentPower":null,"ReactivePower":null,"Factor":null,"Voltage":null,"Current":null} -> {{ value_json['ENERGY'].Voltage }}
  93. const char HASS_DISCOVER_SENSOR_AMPERE[] PROGMEM =
  94. "%s,\"unit_of_meas\":\"A\"," // A
  95. "\"val_tpl\":\"{{value_json['%s'].%s}}\""; // "ENERGY":{"TotalStartTime":null,"Total":null,"Yesterday":null,"Today":null,"Power":null,"ApparentPower":null,"ReactivePower":null,"Factor":null,"Voltage":null,"Current":null} -> {{ value_json['ENERGY'].Current }}
  96. const char HASS_DISCOVER_SENSOR_ANY[] PROGMEM =
  97. "%s,\"unit_of_meas\":\" \"," // " " As unit of measurement to get a value graph in Hass
  98. "\"val_tpl\":\"{{value_json['%s'].%s}}\""; // "COUNTER":{"C1":0} -> {{ value_json['COUNTER'].C1 }}
  99. const char HASS_DISCOVER_DEVICE_INFO[] PROGMEM =
  100. "%s,\"uniq_id\":\"%s\","
  101. "\"device\":{\"identifiers\":[\"%06X\"],"
  102. "\"name\":\"%s\","
  103. "\"model\":\"%s\","
  104. "\"sw_version\":\"%s%s\","
  105. "\"manufacturer\":\"%s\"}";
  106. const char HASS_DISCOVER_TOPIC_PREFIX[] PROGMEM =
  107. "%s, \"~\":\"%s\"";
  108. static void FindPrefix(char* s1, char* s2, char* out)
  109. {
  110. int prefixlen = 0;
  111. while (s1[prefixlen] != '\0' && s2[prefixlen] != '\0' && s1[prefixlen] == s2[prefixlen]) {
  112. prefixlen++;
  113. }
  114. strlcpy(out, s1, prefixlen+1);
  115. }
  116. static void Shorten(char** s, char *prefix)
  117. {
  118. size_t len = strlen(*s);
  119. size_t prefixlen = strlen(prefix);
  120. if (len > prefixlen && !strncmp(*s, prefix, prefixlen)) {
  121. *s += prefixlen-1;
  122. *s[0] = '~';
  123. }
  124. }
  125. void HAssAnnounceRelayLight(void)
  126. {
  127. char stopic[TOPSZ];
  128. char stemp1[TOPSZ];
  129. char stemp2[TOPSZ];
  130. char stemp3[TOPSZ];
  131. char unique_id[30];
  132. bool is_light = false;
  133. bool is_topic_light = false;
  134. for (int i = 1; i <= MAX_RELAYS; i++) {
  135. is_light = ((i == devices_present) && (light_type));
  136. is_topic_light = Settings.flag.hass_light || is_light;
  137. mqtt_data[0] = '\0'; // Clear retained message
  138. // Clear "other" topic first in case the device has been reconfigured from ligth to switch or vice versa
  139. snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), (is_topic_light) ? "RL" : "LI", i);
  140. snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s/config"), (is_topic_light) ? "switch" : "light", unique_id);
  141. MqttPublish(stopic, true);
  142. // Clear or Set topic
  143. snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), (is_topic_light) ? "LI" : "RL", i);
  144. snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s/config"), (is_topic_light) ? "light" : "switch", unique_id);
  145. if (Settings.flag.hass_discovery && (i <= devices_present)) {
  146. char name[33];
  147. char value_template[33];
  148. char prefix[TOPSZ];
  149. char *command_topic = stemp1;
  150. char *state_topic = stemp2;
  151. char *availability_topic = stemp3;
  152. if (i > MAX_FRIENDLYNAMES) {
  153. snprintf_P(name, sizeof(name), PSTR("%s %d"), Settings.friendlyname[0], i);
  154. } else {
  155. snprintf_P(name, sizeof(name), Settings.friendlyname[i -1]);
  156. }
  157. GetPowerDevice(value_template, i, sizeof(value_template), Settings.flag.device_index_enable);
  158. GetTopic_P(command_topic, CMND, mqtt_topic, value_template);
  159. //GetTopic_P(state_topic, STAT, mqtt_topic, S_RSLT_RESULT);
  160. GetTopic_P(state_topic, TELE, mqtt_topic, D_RSLT_STATE);
  161. GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
  162. FindPrefix(command_topic, state_topic, prefix);
  163. Shorten(&command_topic, prefix);
  164. Shorten(&state_topic, prefix);
  165. Shorten(&availability_topic, prefix);
  166. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_RELAY,
  167. name, command_topic, state_topic, value_template, Settings.state_text[0], Settings.state_text[1], availability_topic);
  168. if (is_light) {
  169. char *brightness_command_topic = stemp1;
  170. GetTopic_P(brightness_command_topic, CMND, mqtt_topic, D_CMND_DIMMER);
  171. Shorten(&brightness_command_topic, prefix);
  172. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_LIGHT_DIMMER, mqtt_data, brightness_command_topic, state_topic);
  173. if (light_subtype >= LST_RGB) {
  174. char *rgb_command_topic = stemp1;
  175. GetTopic_P(rgb_command_topic, CMND, mqtt_topic, D_CMND_COLOR);
  176. Shorten(&rgb_command_topic, prefix);
  177. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_LIGHT_COLOR, mqtt_data, rgb_command_topic, state_topic);
  178. char *effect_command_topic = stemp1;
  179. GetTopic_P(effect_command_topic, CMND, mqtt_topic, D_CMND_SCHEME);
  180. Shorten(&effect_command_topic, prefix);
  181. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_LIGHT_SCHEME, mqtt_data, effect_command_topic, state_topic);
  182. }
  183. if (LST_RGBW == light_subtype) {
  184. char *white_temp_command_topic = stemp1;
  185. GetTopic_P(white_temp_command_topic, CMND, mqtt_topic, D_CMND_WHITE);
  186. Shorten(&white_temp_command_topic, prefix);
  187. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_LIGHT_WHITE, mqtt_data, white_temp_command_topic, state_topic);
  188. }
  189. if ((LST_COLDWARM == light_subtype) || (LST_RGBWC == light_subtype)) {
  190. char *color_temp_command_topic = stemp1;
  191. GetTopic_P(color_temp_command_topic, CMND, mqtt_topic, D_CMND_COLORTEMPERATURE);
  192. Shorten(&color_temp_command_topic, prefix);
  193. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_LIGHT_CT, mqtt_data, color_temp_command_topic, state_topic);
  194. }
  195. }
  196. snprintf_P(stemp1, sizeof(stemp1), kModules[Settings.module].name);
  197. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_DEVICE_INFO, mqtt_data,
  198. unique_id, ESP.getChipId(),
  199. Settings.friendlyname[0], stemp1, my_version, my_image, "Tasmota");
  200. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_TOPIC_PREFIX, mqtt_data, prefix);
  201. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  202. }
  203. MqttPublish(stopic, true);
  204. }
  205. }
  206. void HAssAnnounceButtonSwitch(byte device, char* topic, byte present, byte key, byte toggle)
  207. {
  208. // key 0 = button
  209. // key 1 = switch
  210. char stopic[TOPSZ];
  211. char stemp1[TOPSZ];
  212. char stemp2[TOPSZ];
  213. char unique_id[30];
  214. mqtt_data[0] = '\0'; // Clear retained message
  215. // Clear or Set topic
  216. snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), key?"SW":"BTN", device+1);
  217. snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/binary_sensor/%s/config"), unique_id);
  218. if (Settings.flag.hass_discovery && present) {
  219. char name[33];
  220. char value_template[33];
  221. char prefix[TOPSZ];
  222. char *state_topic = stemp1;
  223. char *availability_topic = stemp2;
  224. if (device+1 > MAX_FRIENDLYNAMES) {
  225. snprintf_P(name, sizeof(name), PSTR("%s %s %d"), Settings.friendlyname[0], key?"SW":"BTN", device+1);
  226. } else {
  227. snprintf_P(name, sizeof(name), PSTR("%s %s"), Settings.friendlyname[device], key?"SW":"BTN");
  228. }
  229. GetPowerDevice(value_template, device+1, sizeof(value_template),
  230. key + Settings.flag.device_index_enable); // Force index for Switch 1, Index on Button1 is controlled by Settings.flag.device_index_enable
  231. GetTopic_P(state_topic, CMND, topic, value_template); // State of button is sent as CMND TOGGLE, state of switch is sent as ON/OFF
  232. GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
  233. FindPrefix(state_topic, availability_topic, prefix);
  234. Shorten(&state_topic, prefix);
  235. Shorten(&availability_topic, prefix);
  236. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_BUTTON_SWITCH,
  237. name, state_topic, Settings.state_text[toggle?2:1], availability_topic);
  238. if (toggle) snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_BUTTON_SWITCH_TOGGLE, mqtt_data);
  239. else snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_BUTTON_SWITCH_ONOFF, mqtt_data, Settings.state_text[0]);
  240. snprintf_P(stemp1, sizeof(stemp1), kModules[Settings.module].name);
  241. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_DEVICE_INFO, mqtt_data,
  242. unique_id, ESP.getChipId(),
  243. Settings.friendlyname[0], stemp1, my_version, my_image, "Tasmota");
  244. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_TOPIC_PREFIX, mqtt_data, prefix);
  245. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  246. }
  247. MqttPublish(stopic, true);
  248. }
  249. void HAssAnnounceSwitches(void)
  250. {
  251. char sw_topic[sizeof(Settings.switch_topic)];
  252. // Send info about buttons
  253. char *tmp = Settings.switch_topic;
  254. Format(sw_topic, tmp, sizeof(sw_topic));
  255. if ((strlen(sw_topic) != 0) && strcmp(sw_topic, "0")) {
  256. for (byte switch_index = 0; switch_index < MAX_SWITCHES; switch_index++) {
  257. byte switch_present = 0;
  258. byte toggle = 1;
  259. if ((pin[GPIO_SWT1 + switch_index] < 99) || (pin[GPIO_SWT1_NP + switch_index] < 99)) {
  260. switch_present = 1;
  261. }
  262. // Check if MQTT message will be ON/OFF or TOGGLE
  263. if (Settings.switchmode[switch_index] == FOLLOW || Settings.switchmode[switch_index] == FOLLOW_INV ||
  264. Settings.flag3.button_switch_force_local ||
  265. !strcmp(mqtt_topic, sw_topic) || !strcmp(Settings.mqtt_grptopic, sw_topic))
  266. {
  267. toggle = 0; // MQTT message will be ON/OFF
  268. }
  269. HAssAnnounceButtonSwitch(switch_index, sw_topic, switch_present, 1, toggle);
  270. }
  271. }
  272. }
  273. void HAssAnnounceButtons(void)
  274. {
  275. char key_topic[sizeof(Settings.button_topic)];
  276. // Send info about buttons
  277. char *tmp = Settings.button_topic;
  278. Format(key_topic, tmp, sizeof(key_topic));
  279. if ((strlen(key_topic) != 0) && strcmp(key_topic, "0")) {
  280. for (byte button_index = 0; button_index < MAX_KEYS; button_index++) {
  281. byte button_present = 0;
  282. byte toggle = 1;
  283. if (!button_index && ((SONOFF_DUAL == Settings.module) || (CH4 == Settings.module))) {
  284. button_present = 1;
  285. } else {
  286. if ((pin[GPIO_KEY1 + button_index] < 99) || (pin[GPIO_KEY1_NP + button_index] < 99)) {
  287. button_present = 1;
  288. }
  289. }
  290. // Check if MQTT message will be ON/OFF or TOGGLE
  291. if (Settings.flag3.button_switch_force_local ||
  292. !strcmp(mqtt_topic, key_topic) || !strcmp(Settings.mqtt_grptopic, key_topic))
  293. {
  294. toggle = 0; // MQTT message will be ON/OFF
  295. }
  296. HAssAnnounceButtonSwitch(button_index, key_topic, button_present, 0, toggle);
  297. }
  298. }
  299. }
  300. void HAssAnnounceSensor(const char* sensorname, const char* subsensortype)
  301. {
  302. char stopic[TOPSZ];
  303. char stemp1[TOPSZ];
  304. char stemp2[TOPSZ];
  305. char unique_id[30];
  306. // Announce sensor, special handling of temperature and humidity sensors
  307. mqtt_data[0] = '\0'; // Clear retained message
  308. // Clear or Set topic
  309. snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%s"), ESP.getChipId(), sensorname, subsensortype);
  310. snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/sensor/%s/config"), unique_id);
  311. if (Settings.flag.hass_discovery) {
  312. char name[33];
  313. char prefix[TOPSZ];
  314. char *state_topic = stemp1;
  315. char *availability_topic = stemp2;
  316. snprintf_P(name, sizeof(name), PSTR("%s %s %s"), Settings.friendlyname[0], sensorname, subsensortype);
  317. GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_SENSOR));
  318. GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
  319. FindPrefix(state_topic, availability_topic, prefix);
  320. Shorten(&state_topic, prefix);
  321. Shorten(&availability_topic, prefix);
  322. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR,
  323. name, state_topic, availability_topic);
  324. if (!strcmp_P(subsensortype, PSTR(D_JSON_TEMPERATURE))) {
  325. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_TEMP,
  326. mqtt_data, TempUnit(), sensorname);
  327. } else if (!strcmp_P(subsensortype, PSTR(D_JSON_HUMIDITY))) {
  328. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_HUM,
  329. mqtt_data, sensorname);
  330. } else if (!strcmp_P(subsensortype, PSTR(D_JSON_PRESSURE))) {
  331. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_PRESS,
  332. mqtt_data, PressureUnit().c_str(), sensorname);
  333. } else if (!strcmp_P(subsensortype, PSTR(D_JSON_TOTAL)) || !strcmp_P(subsensortype, PSTR(D_JSON_TODAY)) || !strcmp_P(subsensortype, PSTR(D_JSON_YESTERDAY))){
  334. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_KWH,
  335. mqtt_data, sensorname, subsensortype);
  336. } else if (!strcmp_P(subsensortype, PSTR(D_JSON_POWERUSAGE))){
  337. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_WATT,
  338. mqtt_data, sensorname, subsensortype);
  339. } else if (!strcmp_P(subsensortype, PSTR(D_JSON_VOLTAGE))){
  340. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_VOLTAGE,
  341. mqtt_data, sensorname, subsensortype);
  342. } else if (!strcmp_P(subsensortype, PSTR(D_JSON_CURRENT))){
  343. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_AMPERE,
  344. mqtt_data, sensorname, subsensortype);
  345. }
  346. else {
  347. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_SENSOR_ANY,
  348. mqtt_data, sensorname, subsensortype);
  349. }
  350. snprintf_P(stemp1, sizeof(stemp1), kModules[Settings.module].name);
  351. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_DEVICE_INFO, mqtt_data,
  352. unique_id, ESP.getChipId(),
  353. Settings.friendlyname[0], stemp1, my_version, my_image, "Tasmota");
  354. snprintf_P(mqtt_data, sizeof(mqtt_data), HASS_DISCOVER_TOPIC_PREFIX, mqtt_data, prefix);
  355. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  356. }
  357. MqttPublish(stopic, true);
  358. }
  359. void HAssAnnounceSensors(void)
  360. {
  361. uint8_t hass_xsns_index = 0;
  362. do {
  363. mqtt_data[0] = '\0';
  364. int tele_period_save = tele_period;
  365. tele_period = 2; // Do not allow HA updates during next function call
  366. XsnsNextCall(FUNC_JSON_APPEND, hass_xsns_index); // ,"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
  367. tele_period = tele_period_save;
  368. char sensordata[256]; // Copy because we need to write to mqtt_data
  369. strlcpy(sensordata, mqtt_data, sizeof(sensordata));
  370. if (strlen(sensordata)) {
  371. sensordata[0] = '{'; // {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
  372. snprintf_P(sensordata, sizeof(sensordata), PSTR("%s}"), sensordata);
  373. StaticJsonBuffer<256> jsonBuffer;
  374. JsonObject& root = jsonBuffer.parseObject(sensordata);
  375. if (!root.success()) {
  376. snprintf_P(log_data, sizeof(log_data), PSTR("HASS: failed to parse '%s'"), sensordata);
  377. AddLog(LOG_LEVEL_ERROR);
  378. continue;
  379. }
  380. for (auto sensor : root) {
  381. const char* sensorname = sensor.key;
  382. JsonObject& sensors = sensor.value.as<JsonObject>();
  383. if (!sensors.success()) {
  384. snprintf_P(log_data, sizeof(log_data), PSTR("HASS: failed to parse '%s'"), sensordata);
  385. AddLog(LOG_LEVEL_ERROR);
  386. continue;
  387. }
  388. for (auto subsensor : sensors) {
  389. HAssAnnounceSensor(sensorname, subsensor.key);
  390. }
  391. }
  392. }
  393. } while (hass_xsns_index != 0);
  394. }
  395. static int string_ends_with(const char * str, const char * suffix)
  396. {
  397. int str_len = strlen(str);
  398. int suffix_len = strlen(suffix);
  399. return (str_len >= suffix_len) && (0 == strcmp(str + (str_len-suffix_len), suffix));
  400. }
  401. void HAssDiscovery(uint8_t mode)
  402. {
  403. // Configure Tasmota for default Home Assistant parameters to keep discovery message as short as possible
  404. if (Settings.flag.hass_discovery) {
  405. Settings.flag.mqtt_response = 0; // Response always as RESULT and not as uppercase command
  406. Settings.flag.decimal_text = 1; // Respond with decimal color values
  407. Settings.flag3.hass_tele_on_power = 1; // send tele/STATE message as stat/RESULT
  408. // Settings.light_scheme = 0; // To just control color it needs to be Scheme 0
  409. if (!string_ends_with(Settings.mqtt_fulltopic, "%prefix%/")) {
  410. strncpy_P(Settings.mqtt_fulltopic, PSTR("%topic%/%prefix%/"), sizeof(Settings.mqtt_fulltopic));
  411. restart_flag = 2;
  412. }
  413. }
  414. if (Settings.flag.hass_discovery || (1 == mode)) {
  415. // Send info about relays and lights
  416. HAssAnnounceRelayLight();
  417. // Send info about buttons
  418. HAssAnnounceButtons();
  419. // Send info about switches
  420. HAssAnnounceSwitches();
  421. // Send info about sensors
  422. HAssAnnounceSensors();
  423. }
  424. }
  425. /*
  426. #define D_CMND_HASSDISCOVER "HassDiscover"
  427. enum HassCommands { CMND_HASSDISCOVER };
  428. const char kHassCommands[] PROGMEM = D_CMND_HASSDISCOVER ;
  429. boolean HassCommand(void)
  430. {
  431. char command[CMDSZ];
  432. boolean serviced = true;
  433. int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kHassCommands);
  434. if (-1 == command_code) {
  435. serviced = false; // Unknown command
  436. }
  437. else if (CMND_HASSDISCOVER == command_code) {
  438. if (XdrvMailbox.data_len > 0) {
  439. switch (XdrvMailbox.payload) {
  440. case 0: // Off
  441. case 1: // On
  442. Settings.flag.hass_discovery = XdrvMailbox.payload;
  443. break;
  444. case 2: // Toggle
  445. Settings.flag.hass_discovery ^= 1;
  446. break;
  447. case 4: // Off
  448. case 5: // On
  449. Settings.flag.hass_light = XdrvMailbox.payload &1;
  450. break;
  451. case 6: // Toggle
  452. Settings.flag.hass_light ^= 1;
  453. break;
  454. }
  455. HAssDiscovery(1);
  456. }
  457. snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\",\"Force light\":\"%s\"}"),
  458. command, GetStateText(Settings.flag.hass_discovery), GetStateText(Settings.flag.hass_light));
  459. }
  460. else serviced = false; // Unknown command
  461. return serviced;
  462. }
  463. */
  464. /*********************************************************************************************\
  465. * Interface
  466. \*********************************************************************************************/
  467. boolean Xdrv12(byte function)
  468. {
  469. boolean result = false;
  470. if (Settings.flag.mqtt_enabled) {
  471. switch (function) {
  472. case FUNC_MQTT_INIT:
  473. HAssDiscovery(0);
  474. break;
  475. /*
  476. case FUNC_COMMAND:
  477. result = HassCommand();
  478. break;
  479. */
  480. }
  481. }
  482. return result;
  483. }
  484. #endif // USE_HOME_ASSISTANT