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.

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