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.

422 lines
11 KiB

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