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.

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