MqttClient.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #include <stddef.h>
  2. #include <MqttClient.h>
  3. #include <TokenIterator.h>
  4. #include <UrlTokenBindings.h>
  5. #include <IntParsing.h>
  6. #include <ArduinoJson.h>
  7. #include <WiFiClient.h>
  8. #include <MiLightRadioConfig.h>
  9. #include <AboutHelper.h>
  10. static const char* STATUS_CONNECTED = "connected";
  11. static const char* STATUS_DISCONNECTED = "disconnected_clean";
  12. static const char* STATUS_LWT_DISCONNECTED = "disconnected_unclean";
  13. MqttClient::MqttClient(Settings& settings, MiLightClient*& milightClient)
  14. : mqttClient(tcpClient),
  15. milightClient(milightClient),
  16. settings(settings),
  17. lastConnectAttempt(0)
  18. {
  19. String strDomain = settings.mqttServer();
  20. this->domain = new char[strDomain.length() + 1];
  21. strcpy(this->domain, strDomain.c_str());
  22. }
  23. MqttClient::~MqttClient() {
  24. String aboutStr = generateConnectionStatusMessage(STATUS_DISCONNECTED);
  25. mqttClient.publish(settings.mqttClientStatusTopic.c_str(), aboutStr.c_str(), true);
  26. mqttClient.disconnect();
  27. delete this->domain;
  28. }
  29. void MqttClient::begin() {
  30. #ifdef MQTT_DEBUG
  31. printf_P(
  32. PSTR("MqttClient - Connecting to: %s\nparsed:%s:%u\n"),
  33. settings._mqttServer.c_str(),
  34. settings.mqttServer().c_str(),
  35. settings.mqttPort()
  36. );
  37. #endif
  38. mqttClient.setServer(this->domain, settings.mqttPort());
  39. mqttClient.setCallback(
  40. [this](char* topic, byte* payload, int length) {
  41. this->publishCallback(topic, payload, length);
  42. }
  43. );
  44. reconnect();
  45. }
  46. bool MqttClient::connect() {
  47. char nameBuffer[30];
  48. sprintf_P(nameBuffer, PSTR("milight-hub-%u"), ESP.getChipId());
  49. #ifdef MQTT_DEBUG
  50. Serial.println(F("MqttClient - connecting"));
  51. #endif
  52. if (settings.mqttUsername.length() > 0 && settings.mqttClientStatusTopic.length() > 0) {
  53. return mqttClient.connect(
  54. nameBuffer,
  55. settings.mqttUsername.c_str(),
  56. settings.mqttPassword.c_str(),
  57. settings.mqttClientStatusTopic.c_str(),
  58. 2,
  59. true,
  60. generateConnectionStatusMessage(STATUS_LWT_DISCONNECTED).c_str()
  61. );
  62. } else if (settings.mqttUsername.length() > 0) {
  63. return mqttClient.connect(
  64. nameBuffer,
  65. settings.mqttUsername.c_str(),
  66. settings.mqttPassword.c_str()
  67. );
  68. } else if (settings.mqttClientStatusTopic.length() > 0) {
  69. return mqttClient.connect(
  70. nameBuffer,
  71. settings.mqttClientStatusTopic.c_str(),
  72. 2,
  73. true,
  74. generateConnectionStatusMessage(STATUS_LWT_DISCONNECTED).c_str()
  75. );
  76. } else {
  77. return mqttClient.connect(nameBuffer);
  78. }
  79. }
  80. void MqttClient::sendBirthMessage() {
  81. if (settings.mqttClientStatusTopic.length() > 0) {
  82. String aboutStr = generateConnectionStatusMessage(STATUS_CONNECTED);
  83. mqttClient.publish(settings.mqttClientStatusTopic.c_str(), aboutStr.c_str(), true);
  84. }
  85. }
  86. void MqttClient::reconnect() {
  87. if (lastConnectAttempt > 0 && (millis() - lastConnectAttempt) < MQTT_CONNECTION_ATTEMPT_FREQUENCY) {
  88. return;
  89. }
  90. if (! mqttClient.connected()) {
  91. if (connect()) {
  92. subscribe();
  93. sendBirthMessage();
  94. #ifdef MQTT_DEBUG
  95. Serial.println(F("MqttClient - Successfully connected to MQTT server"));
  96. #endif
  97. } else {
  98. Serial.println(F("ERROR: Failed to connect to MQTT server"));
  99. }
  100. }
  101. lastConnectAttempt = millis();
  102. }
  103. void MqttClient::handleClient() {
  104. reconnect();
  105. mqttClient.loop();
  106. }
  107. void MqttClient::sendUpdate(const MiLightRemoteConfig& remoteConfig, uint16_t deviceId, uint16_t groupId, const char* update) {
  108. publish(settings.mqttUpdateTopicPattern, remoteConfig, deviceId, groupId, update);
  109. }
  110. void MqttClient::sendState(const MiLightRemoteConfig& remoteConfig, uint16_t deviceId, uint16_t groupId, const char* update) {
  111. publish(settings.mqttStateTopicPattern, remoteConfig, deviceId, groupId, update, true);
  112. }
  113. void MqttClient::subscribe() {
  114. String topic = settings.mqttTopicPattern;
  115. topic.replace(":device_id", "+");
  116. topic.replace(":hex_device_id", "+");
  117. topic.replace(":dec_device_id", "+");
  118. topic.replace(":group_id", "+");
  119. topic.replace(":device_type", "+");
  120. topic.replace(":device_alias", "+");
  121. #ifdef MQTT_DEBUG
  122. printf_P(PSTR("MqttClient - subscribing to topic: %s\n"), topic.c_str());
  123. #endif
  124. mqttClient.subscribe(topic.c_str());
  125. }
  126. void MqttClient::publish(
  127. const String& _topic,
  128. const MiLightRemoteConfig &remoteConfig,
  129. uint16_t deviceId,
  130. uint16_t groupId,
  131. const char* message,
  132. const bool retain
  133. ) {
  134. if (_topic.length() == 0) {
  135. return;
  136. }
  137. String topic = _topic;
  138. MqttClient::bindTopicString(topic, remoteConfig, deviceId, groupId);
  139. #ifdef MQTT_DEBUG
  140. printf("MqttClient - publishing update to %s\n", topic.c_str());
  141. #endif
  142. mqttClient.publish(topic.c_str(), message, retain);
  143. }
  144. void MqttClient::publishCallback(char* topic, byte* payload, int length) {
  145. uint16_t deviceId = 0;
  146. uint8_t groupId = 0;
  147. const MiLightRemoteConfig* config = &FUT092Config;
  148. char cstrPayload[length + 1];
  149. cstrPayload[length] = 0;
  150. memcpy(cstrPayload, payload, sizeof(byte)*length);
  151. #ifdef MQTT_DEBUG
  152. printf("MqttClient - Got message on topic: %s\n%s\n", topic, cstrPayload);
  153. #endif
  154. char topicPattern[settings.mqttTopicPattern.length()];
  155. strcpy(topicPattern, settings.mqttTopicPattern.c_str());
  156. TokenIterator patternIterator(topicPattern, settings.mqttTopicPattern.length(), '/');
  157. TokenIterator topicIterator(topic, strlen(topic), '/');
  158. UrlTokenBindings tokenBindings(patternIterator, topicIterator);
  159. if (tokenBindings.hasBinding("device_alias")) {
  160. String alias = tokenBindings.get("device_alias");
  161. auto itr = settings.groupIdAliases.find(alias);
  162. if (itr == settings.groupIdAliases.end()) {
  163. Serial.printf_P(PSTR("MqttClient - WARNING: could not find device alias: `%s'. Ignoring packet.\n"), alias.c_str());
  164. return;
  165. } else {
  166. BulbId bulbId = itr->second;
  167. deviceId = bulbId.deviceId;
  168. config = MiLightRemoteConfig::fromType(bulbId.deviceType);
  169. groupId = bulbId.groupId;
  170. }
  171. } else {
  172. if (tokenBindings.hasBinding("device_id")) {
  173. deviceId = parseInt<uint16_t>(tokenBindings.get("device_id"));
  174. } else if (tokenBindings.hasBinding("hex_device_id")) {
  175. deviceId = parseInt<uint16_t>(tokenBindings.get("hex_device_id"));
  176. } else if (tokenBindings.hasBinding("dec_device_id")) {
  177. deviceId = parseInt<uint16_t>(tokenBindings.get("dec_device_id"));
  178. }
  179. if (tokenBindings.hasBinding("group_id")) {
  180. groupId = parseInt<uint16_t>(tokenBindings.get("group_id"));
  181. }
  182. if (tokenBindings.hasBinding("device_type")) {
  183. config = MiLightRemoteConfig::fromType(tokenBindings.get("device_type"));
  184. } else {
  185. Serial.println(F("MqttClient - WARNING: could not find device_type token. Defaulting to FUT092.\n"));
  186. }
  187. }
  188. if (config == NULL) {
  189. Serial.println(F("MqttClient - ERROR: unknown device_type specified"));
  190. return;
  191. }
  192. StaticJsonDocument<400> buffer;
  193. deserializeJson(buffer, cstrPayload);
  194. JsonObject obj = buffer.as<JsonObject>();
  195. #ifdef MQTT_DEBUG
  196. printf("MqttClient - device %04X, group %u\n", deviceId, groupId);
  197. #endif
  198. milightClient->prepare(config, deviceId, groupId);
  199. milightClient->update(obj);
  200. }
  201. inline void MqttClient::bindTopicString(
  202. String& topicPattern,
  203. const MiLightRemoteConfig& remoteConfig,
  204. const uint16_t deviceId,
  205. const uint16_t groupId
  206. ) {
  207. String deviceIdHex = String(deviceId, 16);
  208. deviceIdHex.toUpperCase();
  209. deviceIdHex = String("0x") + deviceIdHex;
  210. topicPattern.replace(":device_id", deviceIdHex);
  211. topicPattern.replace(":hex_device_id", deviceIdHex);
  212. topicPattern.replace(":dec_device_id", String(deviceId));
  213. topicPattern.replace(":group_id", String(groupId));
  214. topicPattern.replace(":device_type", remoteConfig.name);
  215. auto it = settings.findAlias(remoteConfig.type, deviceId, groupId);
  216. if (it != settings.groupIdAliases.end()) {
  217. topicPattern.replace(":device_alias", it->first);
  218. } else {
  219. topicPattern.replace(":device_alias", "__unnamed_group");
  220. }
  221. }
  222. String MqttClient::generateConnectionStatusMessage(const char* connectionStatus) {
  223. if (settings.simpleMqttClientStatus) {
  224. // Don't expand disconnect type for simple status
  225. if (0 == strcmp(connectionStatus, STATUS_CONNECTED)) {
  226. return connectionStatus;
  227. } else {
  228. return "disconnected";
  229. }
  230. } else {
  231. StaticJsonDocument<1024> json;
  232. json["status"] = connectionStatus;
  233. // Fill other fields
  234. AboutHelper::generateAboutObject(json, true);
  235. String response;
  236. serializeJson(json, response);
  237. return response;
  238. }
  239. }