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.

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