Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
9.3 KiB

6 years ago
6 years ago
  1. /*
  2. HOME ASSISTANT MODULE
  3. Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if HOMEASSISTANT_SUPPORT
  6. #include <ArduinoJson.h>
  7. #include <queue>
  8. bool _haEnabled = false;
  9. bool _haSendFlag = false;
  10. std::queue<uint32_t> _ha_send_config;
  11. // -----------------------------------------------------------------------------
  12. // UTILS
  13. // -----------------------------------------------------------------------------
  14. String _haFixName(String name) {
  15. for (unsigned char i=0; i<name.length(); i++) {
  16. if (!isalnum(name.charAt(i))) name.setCharAt(i, '_');
  17. }
  18. return name;
  19. }
  20. // -----------------------------------------------------------------------------
  21. // SENSORS
  22. // -----------------------------------------------------------------------------
  23. #if SENSOR_SUPPORT
  24. void _haSendMagnitude(unsigned char i, JsonObject& config) {
  25. unsigned char type = magnitudeType(i);
  26. config["name"] = _haFixName(getSetting("hostname") + String(" ") + magnitudeTopic(type));
  27. config.set("platform", "mqtt");
  28. config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
  29. config["unit_of_measurement"] = magnitudeUnits(type);
  30. }
  31. void _haSendMagnitudes() {
  32. for (unsigned char i=0; i<magnitudeCount(); i++) {
  33. String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
  34. "/sensor/" +
  35. getSetting("hostname") + "_" + String(i) +
  36. "/config";
  37. String output;
  38. if (_haEnabled) {
  39. DynamicJsonBuffer jsonBuffer;
  40. JsonObject& config = jsonBuffer.createObject();
  41. _haSendMagnitude(i, config);
  42. config.printTo(output);
  43. jsonBuffer.clear();
  44. }
  45. mqttSendRaw(topic.c_str(), output.c_str());
  46. mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
  47. }
  48. }
  49. #endif // SENSOR_SUPPORT
  50. // -----------------------------------------------------------------------------
  51. // SWITCHES & LIGHTS
  52. // -----------------------------------------------------------------------------
  53. void _haSendSwitch(unsigned char i, JsonObject& config) {
  54. String name = getSetting("hostname");
  55. if (relayCount() > 1) {
  56. name += String("_") + String(i);
  57. }
  58. config.set("name", _haFixName(name));
  59. config.set("platform", "mqtt");
  60. if (relayCount()) {
  61. config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false);
  62. config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true);
  63. config["payload_on"] = String(HOMEASSISTANT_PAYLOAD_ON);
  64. config["payload_off"] = String(HOMEASSISTANT_PAYLOAD_OFF);
  65. config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false);
  66. config["payload_available"] = String(HOMEASSISTANT_PAYLOAD_AVAILABLE);
  67. config["payload_not_available"] = String(HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE);
  68. }
  69. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  70. if (i == 0) {
  71. if (lightHasColor()) {
  72. config["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false);
  73. config["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true);
  74. config["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false);
  75. config["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true);
  76. config["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true);
  77. }
  78. if (lightChannels() > 3) {
  79. config["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false);
  80. config["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true);
  81. }
  82. }
  83. #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  84. }
  85. void _haSendSwitches() {
  86. #if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
  87. String type = String("light");
  88. #else
  89. String type = String("switch");
  90. #endif
  91. for (unsigned char i=0; i<relayCount(); i++) {
  92. String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
  93. "/" + type +
  94. "/" + getSetting("hostname") + "_" + String(i) +
  95. "/config";
  96. String output;
  97. if (_haEnabled) {
  98. DynamicJsonBuffer jsonBuffer;
  99. JsonObject& config = jsonBuffer.createObject();
  100. _haSendSwitch(i, config);
  101. config.printTo(output);
  102. jsonBuffer.clear();
  103. }
  104. mqttSendRaw(topic.c_str(), output.c_str());
  105. mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
  106. }
  107. }
  108. // -----------------------------------------------------------------------------
  109. void _haDumpConfig(std::function<void(String&)> printer) {
  110. #if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
  111. String type = String("light");
  112. #else
  113. String type = String("switch");
  114. #endif
  115. for (unsigned char i=0; i<relayCount(); i++) {
  116. DynamicJsonBuffer jsonBuffer;
  117. JsonObject& config = jsonBuffer.createObject();
  118. _haSendSwitch(i, config);
  119. String output;
  120. output.reserve(config.measureLength() + 32);
  121. output += "\n\n" + type + ":\n";
  122. bool first = true;
  123. for (auto kv : config) {
  124. if (first) {
  125. output += " - ";
  126. first = false;
  127. } else {
  128. output += " ";
  129. }
  130. output += kv.key;
  131. output += ": ";
  132. output += kv.value.as<String>();
  133. output += "\n";
  134. }
  135. output += " ";
  136. jsonBuffer.clear();
  137. printer(output);
  138. }
  139. #if SENSOR_SUPPORT
  140. for (unsigned char i=0; i<magnitudeCount(); i++) {
  141. DynamicJsonBuffer jsonBuffer;
  142. JsonObject& config = jsonBuffer.createObject();
  143. _haSendMagnitude(i, config);
  144. String output;
  145. output.reserve(config.measureLength() + 32);
  146. output += "\n\nsensor:\n";
  147. bool first = true;
  148. for (auto kv : config) {
  149. if (first) {
  150. output += " - ";
  151. first = false;
  152. } else {
  153. output += " ";
  154. }
  155. String value = kv.value.as<String>();
  156. value.replace("%", "'%'");
  157. output += kv.key;
  158. output += ": ";
  159. output += value;
  160. output += "\n";
  161. }
  162. output += " ";
  163. jsonBuffer.clear();
  164. printer(output);
  165. }
  166. #endif
  167. }
  168. void _haSend() {
  169. // Pending message to send?
  170. if (!_haSendFlag) return;
  171. // Are we connected?
  172. if (!mqttConnected()) return;
  173. DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
  174. // Send messages
  175. _haSendSwitches();
  176. #if SENSOR_SUPPORT
  177. _haSendMagnitudes();
  178. #endif
  179. _haSendFlag = false;
  180. }
  181. void _haConfigure() {
  182. bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
  183. _haSendFlag = (enabled != _haEnabled);
  184. _haEnabled = enabled;
  185. _haSend();
  186. }
  187. #if WEB_SUPPORT
  188. bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
  189. return (strncmp(key, "ha", 2) == 0);
  190. }
  191. void _haWebSocketOnSend(JsonObject& root) {
  192. root["haVisible"] = 1;
  193. root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
  194. root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
  195. }
  196. void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  197. if (strcmp(action, "haconfig") == 0) {
  198. _ha_send_config.push(client_id);
  199. }
  200. }
  201. #endif
  202. #if TERMINAL_SUPPORT
  203. void _haInitCommands() {
  204. terminalRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
  205. _haDumpConfig([](String& data) {
  206. DEBUG_MSG(data.c_str());
  207. });
  208. DEBUG_MSG("\n");
  209. terminalOK();
  210. });
  211. terminalRegisterCommand(F("HA.SEND"), [](Embedis* e) {
  212. setSetting("haEnabled", "1");
  213. _haConfigure();
  214. #if WEB_SUPPORT
  215. wsSend(_haWebSocketOnSend);
  216. #endif
  217. terminalOK();
  218. });
  219. terminalRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
  220. setSetting("haEnabled", "0");
  221. _haConfigure();
  222. #if WEB_SUPPORT
  223. wsSend(_haWebSocketOnSend);
  224. #endif
  225. terminalOK();
  226. });
  227. }
  228. #endif
  229. // -----------------------------------------------------------------------------
  230. void _haLoop() {
  231. if (_ha_send_config.empty()) return;
  232. uint32_t client_id = _ha_send_config.front();
  233. _ha_send_config.pop();
  234. _haDumpConfig([client_id](String& output) {
  235. output.replace(" ", "&nbsp;");
  236. output.replace("\n", "<br />");
  237. output = String("{\"haConfig\": \"") + output + String("\"}");
  238. wsSend(client_id, output.c_str());
  239. yield();
  240. });
  241. }
  242. void haSetup() {
  243. _haConfigure();
  244. #if WEB_SUPPORT
  245. wsOnSendRegister(_haWebSocketOnSend);
  246. wsOnActionRegister(_haWebSocketOnAction);
  247. wsOnReceiveRegister(_haWebSocketOnReceive);
  248. #endif
  249. #if TERMINAL_SUPPORT
  250. _haInitCommands();
  251. #endif
  252. // On MQTT connect check if we have something to send
  253. mqttRegister([](unsigned int type, const char * topic, const char * payload) {
  254. if (type == MQTT_CONNECT_EVENT) _haSend();
  255. });
  256. // Main callbacks
  257. espurnaRegisterReload(_haConfigure);
  258. espurnaRegisterLoop(_haLoop);
  259. }
  260. #endif // HOMEASSISTANT_SUPPORT