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.

1320 lines
39 KiB

7 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
6 years ago
6 years ago
  1. /*
  2. MQTT MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. Updated secure client support by Niek van der Maas < mail at niekvandermaas dot nl>
  5. */
  6. #include "mqtt.h"
  7. #if MQTT_SUPPORT
  8. #include <forward_list>
  9. #include <utility>
  10. #include <Ticker.h>
  11. #include "system.h"
  12. #include "mdns.h"
  13. #include "mqtt.h"
  14. #include "ntp.h"
  15. #include "rpc.h"
  16. #include "rtcmem.h"
  17. #include "ws.h"
  18. #include "libs/AsyncClientHelpers.h"
  19. #include "libs/SecureClientHelpers.h"
  20. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  21. #include <ESPAsyncTCP.h>
  22. #include <AsyncMqttClient.h>
  23. #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  24. #include <MQTTClient.h>
  25. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  26. #include <PubSubClient.h>
  27. #endif
  28. // -----------------------------------------------------------------------------
  29. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  30. AsyncMqttClient _mqtt;
  31. #else // MQTT_LIBRARY_ARDUINOMQTT / MQTT_LIBRARY_PUBSUBCLIENT
  32. WiFiClient _mqtt_client;
  33. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  34. std::unique_ptr<SecureClient> _mqtt_client_secure = nullptr;
  35. #if MQTT_SECURE_CLIENT_INCLUDE_CA
  36. #include "static/mqtt_client_trusted_root_ca.h" // Assumes this header file defines a _mqtt_client_trusted_root_ca[] PROGMEM = "...PEM data..."
  37. #else
  38. #include "static/letsencrypt_isrgroot_pem.h" // Default to LetsEncrypt X3 certificate
  39. #define _mqtt_client_trusted_root_ca _ssl_letsencrypt_isrg_x3_ca
  40. #endif // MQTT_SECURE_CLIENT_INCLUDE_CA
  41. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  42. #if MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  43. MQTTClient _mqtt(MQTT_BUFFER_MAX_SIZE);
  44. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  45. PubSubClient _mqtt;
  46. #endif
  47. #endif // MQTT_LIBRARY == MQTT_ASYNCMQTTCLIENT
  48. unsigned long _mqtt_last_connection = 0;
  49. AsyncClientState _mqtt_state = AsyncClientState::Disconnected;
  50. bool _mqtt_skip_messages = false;
  51. unsigned long _mqtt_skip_time = MQTT_SKIP_TIME;
  52. unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
  53. bool _mqtt_enabled = MQTT_ENABLED;
  54. bool _mqtt_use_json = false;
  55. bool _mqtt_retain = MQTT_RETAIN;
  56. int _mqtt_qos = MQTT_QOS;
  57. int _mqtt_keepalive = MQTT_KEEPALIVE;
  58. String _mqtt_topic;
  59. String _mqtt_topic_json;
  60. String _mqtt_setter;
  61. String _mqtt_getter;
  62. bool _mqtt_forward;
  63. String _mqtt_user;
  64. String _mqtt_pass;
  65. String _mqtt_will;
  66. String _mqtt_server;
  67. uint16_t _mqtt_port;
  68. String _mqtt_clientid;
  69. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  70. struct MqttPidCallback {
  71. uint16_t pid;
  72. mqtt_pid_callback_f run;
  73. };
  74. using MqttPidCallbacks = std::forward_list<MqttPidCallback>;
  75. MqttPidCallbacks _mqtt_publish_callbacks;
  76. MqttPidCallbacks _mqtt_subscribe_callbacks;
  77. #endif
  78. std::forward_list<heartbeat::Callback> _mqtt_heartbeat_callbacks;
  79. heartbeat::Mode _mqtt_heartbeat_mode;
  80. heartbeat::Seconds _mqtt_heartbeat_interval;
  81. String _mqtt_payload_online;
  82. String _mqtt_payload_offline;
  83. std::forward_list<mqtt_callback_f> _mqtt_callbacks;
  84. // -----------------------------------------------------------------------------
  85. // JSON payload
  86. // -----------------------------------------------------------------------------
  87. struct MqttPayload {
  88. MqttPayload() = delete;
  89. MqttPayload(const MqttPayload&) = default;
  90. // TODO: replace String implementation with Core v3 (or just use newer Core)
  91. // 2.7.x still has basic Arduino String move ctor that is not noexcept
  92. MqttPayload(MqttPayload&& other) noexcept :
  93. _topic(std::move(other._topic)),
  94. _message(std::move(other._message))
  95. {}
  96. template <typename Topic, typename Message>
  97. MqttPayload(Topic&& topic, Message&& message) :
  98. _topic(std::forward<Topic>(topic)),
  99. _message(std::forward<Message>(message))
  100. {}
  101. const String& topic() const {
  102. return _topic;
  103. }
  104. const String& message() const {
  105. return _message;
  106. }
  107. private:
  108. String _topic;
  109. String _message;
  110. };
  111. size_t _mqtt_json_payload_count { 0ul };
  112. std::forward_list<MqttPayload> _mqtt_json_payload;
  113. Ticker _mqtt_json_payload_flush;
  114. // -----------------------------------------------------------------------------
  115. // Secure client handlers
  116. // -----------------------------------------------------------------------------
  117. #if SECURE_CLIENT == SECURE_CLIENT_AXTLS
  118. SecureClientConfig _mqtt_sc_config {
  119. "MQTT",
  120. []() -> String {
  121. return _mqtt_server;
  122. },
  123. []() -> int {
  124. return getSetting("mqttScCheck", MQTT_SECURE_CLIENT_CHECK);
  125. },
  126. []() -> String {
  127. return getSetting("mqttFP", MQTT_SSL_FINGERPRINT);
  128. },
  129. true
  130. };
  131. #endif
  132. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  133. SecureClientConfig _mqtt_sc_config {
  134. "MQTT",
  135. []() -> int {
  136. return getSetting("mqttScCheck", MQTT_SECURE_CLIENT_CHECK);
  137. },
  138. []() -> PGM_P {
  139. return _mqtt_client_trusted_root_ca;
  140. },
  141. []() -> String {
  142. return getSetting("mqttFP", MQTT_SSL_FINGERPRINT);
  143. },
  144. []() -> uint16_t {
  145. return getSetting("mqttScMFLN", MQTT_SECURE_CLIENT_MFLN);
  146. },
  147. true
  148. };
  149. #endif
  150. // -----------------------------------------------------------------------------
  151. // Client configuration & setup
  152. // -----------------------------------------------------------------------------
  153. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  154. void _mqttSetupAsyncClient(bool secure = false) {
  155. _mqtt.setServer(_mqtt_server.c_str(), _mqtt_port);
  156. _mqtt.setClientId(_mqtt_clientid.c_str());
  157. _mqtt.setKeepAlive(_mqtt_keepalive);
  158. _mqtt.setCleanSession(false);
  159. _mqtt.setWill(_mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, _mqtt_payload_offline.c_str());
  160. if (_mqtt_user.length() && _mqtt_pass.length()) {
  161. DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str());
  162. _mqtt.setCredentials(_mqtt_user.c_str(), _mqtt_pass.c_str());
  163. }
  164. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  165. if (secure) {
  166. DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
  167. _mqtt.setSecure(secure);
  168. }
  169. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  170. _mqtt.connect();
  171. }
  172. #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  173. #if (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT)
  174. WiFiClient& _mqttGetClient(bool secure) {
  175. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  176. return (secure ? _mqtt_client_secure->get() : _mqtt_client);
  177. #else
  178. return _mqtt_client;
  179. #endif
  180. }
  181. bool _mqttSetupSyncClient(bool secure = false) {
  182. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  183. if (secure) {
  184. if (!_mqtt_client_secure) _mqtt_client_secure = std::make_unique<SecureClient>(_mqtt_sc_config);
  185. return _mqtt_client_secure->beforeConnected();
  186. }
  187. #endif
  188. return true;
  189. }
  190. bool _mqttConnectSyncClient(bool secure = false) {
  191. bool result = false;
  192. #if MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  193. _mqtt.begin(_mqtt_server.c_str(), _mqtt_port, _mqttGetClient(secure));
  194. _mqtt.setWill(_mqtt_will.c_str(), _mqtt_payload_offline.c_str(), _mqtt_retain, _mqtt_qos);
  195. _mqtt.setKeepAlive(_mqtt_keepalive);
  196. result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str());
  197. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  198. _mqtt.setClient(_mqttGetClient(secure));
  199. _mqtt.setServer(_mqtt_server.c_str(), _mqtt_port);
  200. if (_mqtt_user.length() && _mqtt_pass.length()) {
  201. DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str());
  202. result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, _mqtt_payload_offline.c_str());
  203. } else {
  204. result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, _mqtt_payload_offline.c_str());
  205. }
  206. #endif
  207. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  208. if (result && secure) {
  209. result = _mqtt_client_secure->afterConnected();
  210. }
  211. #endif
  212. return result;
  213. }
  214. #endif // (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT)
  215. void _mqttPlaceholders(String& text) {
  216. text.replace("{hostname}", getSetting("hostname"));
  217. text.replace("{magnitude}", "#");
  218. String mac = WiFi.macAddress();
  219. mac.replace(":", "");
  220. text.replace("{mac}", mac);
  221. }
  222. template<typename T>
  223. void _mqttApplySetting(T& current, T& updated) {
  224. if (current != updated) {
  225. current = std::move(updated);
  226. mqttDisconnect();
  227. }
  228. }
  229. template<typename T>
  230. void _mqttApplySetting(T& current, const T& updated) {
  231. if (current != updated) {
  232. current = updated;
  233. mqttDisconnect();
  234. }
  235. }
  236. template<typename T>
  237. void _mqttApplyTopic(T& current, const char* magnitude) {
  238. String updated = mqttTopic(magnitude, false);
  239. if (current != updated) {
  240. mqttFlush();
  241. current = std::move(updated);
  242. }
  243. }
  244. void _mqttConfigure() {
  245. // Enable only when server is set
  246. {
  247. const String server = getSetting("mqttServer", MQTT_SERVER);
  248. const auto port = getSetting("mqttPort", static_cast<uint16_t>(MQTT_PORT));
  249. bool enabled = false;
  250. if (server.length()) {
  251. enabled = getSetting("mqttEnabled", 1 == MQTT_ENABLED);
  252. }
  253. _mqttApplySetting(_mqtt_server, server);
  254. _mqttApplySetting(_mqtt_enabled, enabled);
  255. _mqttApplySetting(_mqtt_port, port);
  256. if (!enabled) return;
  257. }
  258. // Get base topic and apply placeholders
  259. {
  260. String topic = getSetting("mqttTopic", MQTT_TOPIC);
  261. if (topic.endsWith("/")) topic.remove(topic.length()-1);
  262. // Replace things inside curly braces (like {hostname}, {mac} etc.)
  263. _mqttPlaceholders(topic);
  264. if (topic.indexOf("#") == -1) topic.concat("/#");
  265. _mqttApplySetting(_mqtt_topic, topic);
  266. }
  267. // Getter and setter
  268. {
  269. String setter = getSetting("mqttSetter", MQTT_SETTER);
  270. String getter = getSetting("mqttGetter", MQTT_GETTER);
  271. bool forward = !setter.equals(getter) && RELAY_REPORT_STATUS;
  272. _mqttApplySetting(_mqtt_setter, setter);
  273. _mqttApplySetting(_mqtt_getter, getter);
  274. _mqttApplySetting(_mqtt_forward, forward);
  275. }
  276. // MQTT options
  277. {
  278. String user = getSetting("mqttUser", MQTT_USER);
  279. _mqttPlaceholders(user);
  280. String pass = getSetting("mqttPassword", MQTT_PASS);
  281. const auto qos = getSetting("mqttQoS", MQTT_QOS);
  282. const bool retain = getSetting("mqttRetain", 1 == MQTT_RETAIN);
  283. // Note: MQTT spec defines this as 2 bytes
  284. const auto keepalive = constrain(
  285. getSetting("mqttKeep", MQTT_KEEPALIVE),
  286. 0, std::numeric_limits<uint16_t>::max()
  287. );
  288. String id = getSetting("mqttClientID", getIdentifier());
  289. _mqttPlaceholders(id);
  290. _mqttApplySetting(_mqtt_user, user);
  291. _mqttApplySetting(_mqtt_pass, pass);
  292. _mqttApplySetting(_mqtt_qos, qos);
  293. _mqttApplySetting(_mqtt_retain, retain);
  294. _mqttApplySetting(_mqtt_keepalive, keepalive);
  295. _mqttApplySetting(_mqtt_clientid, id);
  296. _mqttApplyTopic(_mqtt_will, MQTT_TOPIC_STATUS);
  297. }
  298. // MQTT JSON
  299. {
  300. _mqttApplySetting(_mqtt_use_json, getSetting("mqttUseJson", 1 == MQTT_USE_JSON));
  301. _mqttApplyTopic(_mqtt_topic_json, MQTT_TOPIC_JSON);
  302. }
  303. _mqttApplySetting(_mqtt_heartbeat_mode,
  304. getSetting("mqttHbMode", heartbeat::currentMode()));
  305. _mqttApplySetting(_mqtt_heartbeat_interval,
  306. getSetting("mqttHbIntvl", heartbeat::currentInterval()));
  307. // Skip messages in a small window right after the connection
  308. _mqtt_skip_time = getSetting("mqttSkipTime", MQTT_SKIP_TIME);
  309. // Custom payload strings
  310. settingsProcessConfig({
  311. {_mqtt_payload_online, "mqttPayloadOnline", MQTT_STATUS_ONLINE},
  312. {_mqtt_payload_offline, "mqttPayloadOffline", MQTT_STATUS_OFFLINE}
  313. });
  314. // Reset reconnect delay to reconnect sooner
  315. _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
  316. }
  317. void _mqttBackwards() {
  318. String mqttTopic = getSetting("mqttTopic", MQTT_TOPIC);
  319. if (mqttTopic.indexOf("{identifier}") > 0) {
  320. mqttTopic.replace("{identifier}", "{hostname}");
  321. setSetting("mqttTopic", mqttTopic);
  322. }
  323. }
  324. void _mqttInfo() {
  325. // Build information
  326. {
  327. #define __MQTT_INFO_STR(X) #X
  328. #define _MQTT_INFO_STR(X) __MQTT_INFO_STR(X)
  329. DEBUG_MSG_P(PSTR(
  330. "[MQTT] "
  331. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  332. "AsyncMqttClient"
  333. #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  334. "Arduino-MQTT"
  335. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  336. "PubSubClient"
  337. #endif
  338. ", SSL "
  339. #if SECURE_CLIENT != SEURE_CLIENT_NONE
  340. "ENABLED"
  341. #else
  342. "DISABLED"
  343. #endif
  344. ", Autoconnect "
  345. #if MQTT_AUTOCONNECT
  346. "ENABLED"
  347. #else
  348. "DISABLED"
  349. #endif
  350. ", Buffer size " _MQTT_INFO_STR(MQTT_BUFFER_MAX_SIZE) " bytes"
  351. "\n"
  352. ));
  353. #undef _MQTT_INFO_STR
  354. #undef __MQTT_INFO_STR
  355. }
  356. // Notify about the general state of the client
  357. {
  358. const __FlashStringHelper* enabled = _mqtt_enabled
  359. ? F("ENABLED")
  360. : F("DISABLED");
  361. const __FlashStringHelper* state = nullptr;
  362. switch (_mqtt_state) {
  363. case AsyncClientState::Connecting:
  364. state = F("CONNECTING");
  365. break;
  366. case AsyncClientState::Connected:
  367. state = F("CONNECTED");
  368. break;
  369. case AsyncClientState::Disconnected:
  370. state = F("DISCONNECTED");
  371. break;
  372. case AsyncClientState::Disconnecting:
  373. state = F("DISCONNECTING");
  374. break;
  375. default:
  376. state = F("WAITING");
  377. break;
  378. }
  379. DEBUG_MSG_P(PSTR("[MQTT] Client %s, %s\n"),
  380. String(enabled).c_str(),
  381. String(state).c_str()
  382. );
  383. if (_mqtt_enabled && (_mqtt_state != AsyncClientState::Connected)) {
  384. DEBUG_MSG_P(PSTR("[MQTT] Retrying, Last %u with Delay %u (Step %u)\n"),
  385. _mqtt_last_connection,
  386. _mqtt_reconnect_delay,
  387. MQTT_RECONNECT_DELAY_STEP
  388. );
  389. }
  390. }
  391. }
  392. // -----------------------------------------------------------------------------
  393. // WEB
  394. // -----------------------------------------------------------------------------
  395. #if WEB_SUPPORT
  396. bool _mqttWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  397. return (strncmp(key, "mqtt", 3) == 0);
  398. }
  399. void _mqttWebSocketOnVisible(JsonObject& root) {
  400. root["mqttVisible"] = 1;
  401. #if ASYNC_TCP_SSL_ENABLED
  402. root["mqttsslVisible"] = 1;
  403. #endif
  404. }
  405. void _mqttWebSocketOnData(JsonObject& root) {
  406. root["mqttStatus"] = mqttConnected();
  407. }
  408. void _mqttWebSocketOnConnected(JsonObject& root) {
  409. root["mqttEnabled"] = mqttEnabled();
  410. root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
  411. root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
  412. root["mqttUser"] = getSetting("mqttUser", MQTT_USER);
  413. root["mqttClientID"] = getSetting("mqttClientID");
  414. root["mqttPassword"] = getSetting("mqttPassword", MQTT_PASS);
  415. root["mqttKeep"] = _mqtt_keepalive;
  416. root["mqttRetain"] = _mqtt_retain;
  417. root["mqttQoS"] = _mqtt_qos;
  418. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  419. root["mqttUseSSL"] = getSetting("mqttUseSSL", 1 == MQTT_SSL_ENABLED);
  420. root["mqttFP"] = getSetting("mqttFP", MQTT_SSL_FINGERPRINT);
  421. #endif
  422. root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
  423. root["mqttUseJson"] = getSetting("mqttUseJson", 1 == MQTT_USE_JSON);
  424. }
  425. #endif
  426. // -----------------------------------------------------------------------------
  427. // SETTINGS
  428. // -----------------------------------------------------------------------------
  429. #if TERMINAL_SUPPORT
  430. void _mqttInitCommands() {
  431. terminalRegisterCommand(F("MQTT.RESET"), [](const terminal::CommandContext&) {
  432. _mqttConfigure();
  433. mqttDisconnect();
  434. terminalOK();
  435. });
  436. terminalRegisterCommand(F("MQTT.INFO"), [](const terminal::CommandContext&) {
  437. _mqttInfo();
  438. terminalOK();
  439. });
  440. }
  441. #endif // TERMINAL_SUPPORT
  442. // -----------------------------------------------------------------------------
  443. // MQTT Callbacks
  444. // -----------------------------------------------------------------------------
  445. void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
  446. if (type == MQTT_CONNECT_EVENT) {
  447. mqttSubscribe(MQTT_TOPIC_ACTION);
  448. }
  449. if (type == MQTT_MESSAGE_EVENT) {
  450. String t = mqttMagnitude(topic);
  451. if (t.equals(MQTT_TOPIC_ACTION)) {
  452. rpcHandleAction(payload);
  453. }
  454. }
  455. }
  456. bool _mqttHeartbeat(heartbeat::Mask mask) {
  457. // No point retrying, since we will be re-scheduled on connection
  458. if (!mqttConnected()) {
  459. return true;
  460. }
  461. #if NTP_SUPPORT
  462. // Backported from the older utils implementation.
  463. // Wait until the time is synced to avoid sending partial report *and*
  464. // as a result, wait until the next interval to actually send the datetime string.
  465. if ((mask & heartbeat::Report::Datetime) && !ntpSynced()) {
  466. return false;
  467. }
  468. #endif
  469. // TODO: rework old HEARTBEAT_REPEAT_STATUS?
  470. // for example: send full report once, send only the dynamic data after that
  471. // (interval, hostname, description, ssid, bssid, ip, mac, rssi, uptime, datetime, heap, loadavg, vcc)
  472. // otherwise, it is still possible by setting everything to 0 *but* the Report::Status bit
  473. // TODO: per-module mask?
  474. // TODO: simply send static data with onConnected, and the rest from here?
  475. if (mask & heartbeat::Report::Status)
  476. mqttSendStatus();
  477. if (mask & heartbeat::Report::Interval)
  478. mqttSend(MQTT_TOPIC_INTERVAL, String(_mqtt_heartbeat_interval.count()).c_str());
  479. if (mask & heartbeat::Report::App)
  480. mqttSend(MQTT_TOPIC_APP, APP_NAME);
  481. if (mask & heartbeat::Report::Version)
  482. mqttSend(MQTT_TOPIC_VERSION, getVersion().c_str());
  483. if (mask & heartbeat::Report::Board)
  484. mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str());
  485. if (mask & heartbeat::Report::Hostname)
  486. mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname", getIdentifier()).c_str());
  487. if (mask & heartbeat::Report::Description) {
  488. auto desc = getSetting("desc");
  489. if (desc.length()) {
  490. mqttSend(MQTT_TOPIC_DESCRIPTION, desc.c_str());
  491. }
  492. }
  493. if (mask & heartbeat::Report::Ssid)
  494. mqttSend(MQTT_TOPIC_SSID, WiFi.SSID().c_str());
  495. if (mask & heartbeat::Report::Bssid)
  496. mqttSend(MQTT_TOPIC_BSSID, WiFi.BSSIDstr().c_str());
  497. if (mask & heartbeat::Report::Ip)
  498. mqttSend(MQTT_TOPIC_IP, getIP().c_str());
  499. if (mask & heartbeat::Report::Mac)
  500. mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str());
  501. if (mask & heartbeat::Report::Rssi)
  502. mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
  503. if (mask & heartbeat::Report::Uptime)
  504. mqttSend(MQTT_TOPIC_UPTIME, String(systemUptime()).c_str());
  505. #if NTP_SUPPORT
  506. if (mask & heartbeat::Report::Datetime)
  507. mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
  508. #endif
  509. if (mask & heartbeat::Report::Freeheap) {
  510. auto stats = systemHeapStats();
  511. mqttSend(MQTT_TOPIC_FREEHEAP, String(stats.available).c_str());
  512. }
  513. if (mask & heartbeat::Report::Loadavg)
  514. mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
  515. if ((mask & heartbeat::Report::Vcc) && (ADC_MODE_VALUE == ADC_VCC))
  516. mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
  517. auto status = mqttConnected();
  518. for (auto& cb : _mqtt_heartbeat_callbacks) {
  519. status = status && cb(mask);
  520. }
  521. return status;
  522. }
  523. void _mqttOnConnect() {
  524. _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
  525. _mqtt_last_connection = millis();
  526. _mqtt_state = AsyncClientState::Connected;
  527. systemHeartbeat(_mqttHeartbeat, _mqtt_heartbeat_mode, _mqtt_heartbeat_interval);
  528. // Notify all subscribers about the connection
  529. for (auto& callback : _mqtt_callbacks) {
  530. callback(MQTT_CONNECT_EVENT, nullptr, nullptr);
  531. }
  532. DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
  533. }
  534. void _mqttOnDisconnect() {
  535. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  536. _mqtt_publish_callbacks.clear();
  537. _mqtt_subscribe_callbacks.clear();
  538. #endif
  539. _mqtt_last_connection = millis();
  540. _mqtt_state = AsyncClientState::Disconnected;
  541. systemStopHeartbeat(_mqttHeartbeat);
  542. // Notify all subscribers about the disconnect
  543. for (auto& callback : _mqtt_callbacks) {
  544. callback(MQTT_DISCONNECT_EVENT, nullptr, nullptr);
  545. }
  546. DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n"));
  547. }
  548. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  549. // Run the associated callback when message PID is acknowledged by the broker
  550. void _mqttPidCallback(MqttPidCallbacks& callbacks, uint16_t pid) {
  551. if (callbacks.empty()) {
  552. return;
  553. }
  554. auto end = callbacks.end();
  555. auto prev = callbacks.before_begin();
  556. auto it = callbacks.begin();
  557. while (it != end) {
  558. if ((*it).pid == pid) {
  559. (*it).run();
  560. it = callbacks.erase_after(prev);
  561. } else {
  562. prev = it;
  563. ++it;
  564. }
  565. }
  566. }
  567. #endif
  568. // Force-skip everything received in a short window right after connecting to avoid syncronization issues.
  569. bool _mqttMaybeSkipRetained(char* topic) {
  570. if (_mqtt_skip_messages && (millis() - _mqtt_last_connection < _mqtt_skip_time)) {
  571. DEBUG_MSG_P(PSTR("[MQTT] Received %s - SKIPPED\n"), topic);
  572. return true;
  573. }
  574. _mqtt_skip_messages = false;
  575. return false;
  576. }
  577. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  578. // MQTT Broker can sometimes send messages in bulk. Even when message size is less than MQTT_BUFFER_MAX_SIZE, we *could*
  579. // receive a message with `len != total`, this requiring buffering of the received data. Prepare a static memory to store the
  580. // data until `(len + index) == total`.
  581. // TODO: One pending issue is streaming arbitrary data (e.g. binary, for OTA). We always set '\0' and API consumer expects C-String.
  582. // In that case, there could be MQTT_MESSAGE_RAW_EVENT and this callback only trigger on small messages.
  583. // TODO: Current callback model does not allow to pass message length. Instead, implement a topic filter and record all subscriptions. That way we don't need to filter out events and could implement per-event callbacks.
  584. void _mqttOnMessageAsync(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
  585. if (!len || (len > MQTT_BUFFER_MAX_SIZE) || (total > MQTT_BUFFER_MAX_SIZE)) return;
  586. if (_mqttMaybeSkipRetained(topic)) return;
  587. static char message[((MQTT_BUFFER_MAX_SIZE + 1) + 31) & -32] = {0};
  588. memmove(message + index, (char *) payload, len);
  589. // Not done yet
  590. if (total != (len + index)) {
  591. DEBUG_MSG_P(PSTR("[MQTT] Buffered %s => %u / %u bytes\n"), topic, len, total);
  592. return;
  593. }
  594. message[len + index] = '\0';
  595. DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message);
  596. // Call subscribers with the message buffer
  597. for (auto& callback : _mqtt_callbacks) {
  598. callback(MQTT_MESSAGE_EVENT, topic, message);
  599. }
  600. }
  601. #else
  602. // Sync client already implements buffering, but we still need to add '\0' because API consumer expects C-String :/
  603. // TODO: consider reworking this (and async counterpart), giving callback func length of the message.
  604. void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
  605. if (!len || (len > MQTT_BUFFER_MAX_SIZE)) return;
  606. if (_mqttMaybeSkipRetained(topic)) return;
  607. static char message[((MQTT_BUFFER_MAX_SIZE + 1) + 31) & -32] = {0};
  608. memmove(message, (char *) payload, len);
  609. message[len] = '\0';
  610. DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message);
  611. // Call subscribers with the message buffer
  612. for (auto& callback : _mqtt_callbacks) {
  613. callback(MQTT_MESSAGE_EVENT, topic, message);
  614. }
  615. }
  616. #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  617. // -----------------------------------------------------------------------------
  618. // Public API
  619. // -----------------------------------------------------------------------------
  620. /**
  621. Returns the magnitude part of a topic
  622. @param topic the full MQTT topic
  623. @return String object with the magnitude part.
  624. */
  625. String mqttMagnitude(const char* topic) {
  626. String output;
  627. String pattern = _mqtt_topic + _mqtt_setter;
  628. int position = pattern.indexOf("#");
  629. if (position >= 0) {
  630. String start = pattern.substring(0, position);
  631. String end = pattern.substring(position + 1);
  632. String magnitude(topic);
  633. if (magnitude.startsWith(start) && magnitude.endsWith(end)) {
  634. magnitude.replace(start, "");
  635. magnitude.replace(end, "");
  636. output = std::move(magnitude);
  637. }
  638. }
  639. return output;
  640. }
  641. /**
  642. Returns a full MQTT topic from the magnitude
  643. @param magnitude the magnitude part of the topic.
  644. @param is_set whether to build a command topic (true)
  645. or a state topic (false).
  646. @return String full MQTT topic.
  647. */
  648. String mqttTopic(const char* magnitude, bool is_set) {
  649. String output;
  650. output.reserve(strlen(magnitude)
  651. + _mqtt_topic.length()
  652. + _mqtt_setter.length()
  653. + _mqtt_getter.length());
  654. output += _mqtt_topic;
  655. output.replace("#", magnitude);
  656. output += is_set ? _mqtt_setter : _mqtt_getter;
  657. return output;
  658. }
  659. /**
  660. Returns a full MQTT topic from the magnitude
  661. @param magnitude the magnitude part of the topic.
  662. @param index index of the magnitude when more than one such magnitudes.
  663. @param is_set whether to build a command topic (true)
  664. or a state topic (false).
  665. @return String full MQTT topic.
  666. */
  667. String mqttTopic(const char* magnitude, unsigned int index, bool is_set) {
  668. String output;
  669. output.reserve(strlen(magnitude) + (sizeof(decltype(index)) * 4));
  670. output += magnitude;
  671. output += '/';
  672. output += index;
  673. return mqttTopic(output.c_str(), is_set);
  674. }
  675. // -----------------------------------------------------------------------------
  676. uint16_t mqttSendRaw(const char * topic, const char * message, bool retain, int qos) {
  677. constexpr size_t MessageLogMax { 128ul };
  678. if (_mqtt.connected()) {
  679. const unsigned int packetId {
  680. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  681. _mqtt.publish(topic, qos, retain, message)
  682. #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  683. _mqtt.publish(topic, message, retain, qos)
  684. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  685. _mqtt.publish(topic, message, retain)
  686. #endif
  687. };
  688. const size_t message_len = strlen(message);
  689. if (message_len > MessageLogMax) {
  690. DEBUG_MSG_P(PSTR("[MQTT] Sending %s => (%u bytes) (PID %u)\n"), topic, message_len, packetId);
  691. } else {
  692. DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %u)\n"), topic, message, packetId);
  693. }
  694. return packetId;
  695. }
  696. return false;
  697. }
  698. uint16_t mqttSendRaw(const char * topic, const char * message, bool retain) {
  699. return mqttSendRaw(topic, message, retain, _mqtt_qos);
  700. }
  701. uint16_t mqttSendRaw(const char * topic, const char * message) {
  702. return mqttSendRaw(topic, message, _mqtt_retain, _mqtt_qos);
  703. }
  704. bool mqttSend(const char * topic, const char * message, bool force, bool retain) {
  705. if (!force && _mqtt_use_json) {
  706. mqttEnqueue(topic, message);
  707. _mqtt_json_payload_flush.once_ms(MQTT_USE_JSON_DELAY, mqttFlush);
  708. return true;
  709. }
  710. return mqttSendRaw(mqttTopic(topic, false).c_str(), message, retain) > 0;
  711. }
  712. bool mqttSend(const char * topic, const char * message, bool force) {
  713. return mqttSend(topic, message, force, _mqtt_retain);
  714. }
  715. bool mqttSend(const char * topic, const char * message) {
  716. return mqttSend(topic, message, false);
  717. }
  718. bool mqttSend(const char * topic, unsigned int index, const char * message, bool force, bool retain) {
  719. char buffer[strlen(topic)+5];
  720. snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
  721. return mqttSend(buffer, message, force, retain);
  722. }
  723. bool mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
  724. return mqttSend(topic, index, message, force, _mqtt_retain);
  725. }
  726. bool mqttSend(const char * topic, unsigned int index, const char * message) {
  727. return mqttSend(topic, index, message, false);
  728. }
  729. // -----------------------------------------------------------------------------
  730. constexpr size_t MqttJsonPayloadBufferSize { 1024ul };
  731. void mqttFlush() {
  732. if (!_mqtt.connected()) {
  733. return;
  734. }
  735. if (_mqtt_json_payload.empty()) {
  736. return;
  737. }
  738. DynamicJsonBuffer jsonBuffer(MqttJsonPayloadBufferSize);
  739. JsonObject& root = jsonBuffer.createObject();
  740. #if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME
  741. if (ntpSynced()) {
  742. root[MQTT_TOPIC_DATETIME] = ntpDateTime();
  743. }
  744. #endif
  745. #if MQTT_ENQUEUE_MAC
  746. root[MQTT_TOPIC_MAC] = WiFi.macAddress();
  747. #endif
  748. #if MQTT_ENQUEUE_HOSTNAME
  749. root[MQTT_TOPIC_HOSTNAME] = getSetting("hostname", getIdentifier());
  750. #endif
  751. #if MQTT_ENQUEUE_IP
  752. root[MQTT_TOPIC_IP] = getIP();
  753. #endif
  754. #if MQTT_ENQUEUE_MESSAGE_ID
  755. root[MQTT_TOPIC_MESSAGE_ID] = (Rtcmem->mqtt)++;
  756. #endif
  757. for (auto& payload : _mqtt_json_payload) {
  758. root[payload.topic().c_str()] = payload.message().c_str();
  759. }
  760. String output;
  761. root.printTo(output);
  762. jsonBuffer.clear();
  763. _mqtt_json_payload_count = 0;
  764. _mqtt_json_payload.clear();
  765. mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false);
  766. }
  767. void mqttEnqueue(const char* topic, const char* message) {
  768. // Queue is not meant to send message "offline"
  769. // We must prevent the queue does not get full while offline
  770. if (_mqtt.connected()) {
  771. if (_mqtt_json_payload_count >= MQTT_QUEUE_MAX_SIZE) {
  772. mqttFlush();
  773. }
  774. _mqtt_json_payload.remove_if([topic](const MqttPayload& payload) {
  775. return payload.topic() == topic;
  776. });
  777. _mqtt_json_payload.emplace_front(topic, message);
  778. ++_mqtt_json_payload_count;
  779. }
  780. }
  781. // -----------------------------------------------------------------------------
  782. // Only async client returns resulting PID, sync libraries return either success (1) or failure (0)
  783. uint16_t mqttSubscribeRaw(const char* topic, int qos) {
  784. uint16_t pid { 0u };
  785. if (_mqtt.connected() && (strlen(topic) > 0)) {
  786. pid = _mqtt.subscribe(topic, qos);
  787. DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, pid);
  788. }
  789. return pid;
  790. }
  791. uint16_t mqttSubscribeRaw(const char* topic) {
  792. return mqttSubscribeRaw(topic, _mqtt_qos);
  793. }
  794. bool mqttSubscribe(const char * topic) {
  795. return mqttSubscribeRaw(mqttTopic(topic, true).c_str(), _mqtt_qos);
  796. }
  797. uint16_t mqttUnsubscribeRaw(const char * topic) {
  798. uint16_t pid { 0u };
  799. if (_mqtt.connected() && (strlen(topic) > 0)) {
  800. pid = _mqtt.unsubscribe(topic);
  801. DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing from %s (PID %d)\n"), topic, pid);
  802. }
  803. return pid;
  804. }
  805. bool mqttUnsubscribe(const char * topic) {
  806. return mqttUnsubscribeRaw(mqttTopic(topic, true).c_str());
  807. }
  808. // -----------------------------------------------------------------------------
  809. void mqttEnabled(bool status) {
  810. _mqtt_enabled = status;
  811. }
  812. bool mqttEnabled() {
  813. return _mqtt_enabled;
  814. }
  815. bool mqttConnected() {
  816. return _mqtt.connected();
  817. }
  818. void mqttDisconnect() {
  819. if (_mqtt.connected()) {
  820. DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n"));
  821. _mqtt.disconnect();
  822. }
  823. }
  824. bool mqttForward() {
  825. return _mqtt_forward;
  826. }
  827. /**
  828. Register a persistent lifecycle callback
  829. @param standalone function pointer
  830. */
  831. void mqttRegister(mqtt_callback_f callback) {
  832. _mqtt_callbacks.push_front(callback);
  833. }
  834. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  835. /**
  836. Register a temporary publish callback
  837. @param callable object
  838. */
  839. void mqttOnPublish(uint16_t pid, mqtt_pid_callback_f callback) {
  840. auto callable = MqttPidCallback { pid, callback };
  841. _mqtt_publish_callbacks.push_front(std::move(callable));
  842. }
  843. /**
  844. Register a temporary subscribe callback
  845. @param callable object
  846. */
  847. void mqttOnSubscribe(uint16_t pid, mqtt_pid_callback_f callback) {
  848. auto callable = MqttPidCallback { pid, callback };
  849. _mqtt_subscribe_callbacks.push_front(std::move(callable));
  850. }
  851. #endif
  852. void mqttSetBroker(IPAddress ip, uint16_t port) {
  853. setSetting("mqttServer", ip.toString());
  854. _mqtt_server = ip.toString();
  855. setSetting("mqttPort", port);
  856. _mqtt_port = port;
  857. mqttEnabled(1 == MQTT_AUTOCONNECT);
  858. }
  859. void mqttSetBrokerIfNone(IPAddress ip, uint16_t port) {
  860. if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
  861. mqttSetBroker(ip, port);
  862. }
  863. }
  864. // TODO: these strings are only updated after running the configuration routine and when MQTT is *enabled*
  865. const String& mqttPayloadOnline() {
  866. return _mqtt_payload_online;
  867. }
  868. const String& mqttPayloadOffline() {
  869. return _mqtt_payload_offline;
  870. }
  871. const char* mqttPayloadStatus(bool status) {
  872. return status ? _mqtt_payload_online.c_str() : _mqtt_payload_offline.c_str();
  873. }
  874. void mqttSendStatus() {
  875. mqttSend(MQTT_TOPIC_STATUS, _mqtt_payload_online.c_str(), true);
  876. }
  877. // -----------------------------------------------------------------------------
  878. // Initialization
  879. // -----------------------------------------------------------------------------
  880. void _mqttConnect() {
  881. // Do not connect if already connected or still trying to connect
  882. if (_mqtt.connected() || (_mqtt_state != AsyncClientState::Disconnected)) return;
  883. // Do not connect if disabled or no WiFi
  884. if (!_mqtt_enabled || (WiFi.status() != WL_CONNECTED)) return;
  885. // Check reconnect interval
  886. if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) return;
  887. // Increase the reconnect delay
  888. _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
  889. if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
  890. _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
  891. }
  892. DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%hu\n"), _mqtt_server.c_str(), _mqtt_port);
  893. DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid.c_str());
  894. DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
  895. DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %c\n"), _mqtt_retain ? 'Y' : 'N');
  896. DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %hu (s)\n"), _mqtt_keepalive);
  897. DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will.c_str());
  898. _mqtt_state = AsyncClientState::Connecting;
  899. _mqtt_skip_messages = (_mqtt_skip_time > 0);
  900. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  901. const bool secure = getSetting("mqttUseSSL", 1 == MQTT_SSL_ENABLED);
  902. #else
  903. const bool secure = false;
  904. #endif
  905. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  906. _mqttSetupAsyncClient(secure);
  907. #elif (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT)
  908. if (_mqttSetupSyncClient(secure) && _mqttConnectSyncClient(secure)) {
  909. _mqttOnConnect();
  910. } else {
  911. DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
  912. _mqttOnDisconnect();
  913. }
  914. #else
  915. #error "please check that MQTT_LIBRARY is valid"
  916. #endif
  917. }
  918. void mqttLoop() {
  919. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  920. _mqttConnect();
  921. #else
  922. if (_mqtt.connected()) {
  923. _mqtt.loop();
  924. } else {
  925. if (_mqtt_state != AsyncClientState::Disconnected) {
  926. _mqttOnDisconnect();
  927. }
  928. _mqttConnect();
  929. }
  930. #endif
  931. }
  932. void mqttHeartbeat(heartbeat::Callback callback) {
  933. _mqtt_heartbeat_callbacks.push_front(callback);
  934. }
  935. void mqttSetup() {
  936. _mqttBackwards();
  937. _mqttInfo();
  938. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  939. // XXX: should not place this in config, addServerFingerprint does not check for duplicates
  940. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  941. {
  942. if (_mqtt_sc_config.on_fingerprint) {
  943. const String fingerprint = _mqtt_sc_config.on_fingerprint();
  944. uint8_t buffer[20] = {0};
  945. if (sslFingerPrintArray(fingerprint.c_str(), buffer)) {
  946. _mqtt.addServerFingerprint(buffer);
  947. }
  948. }
  949. }
  950. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  951. _mqtt.onMessage(_mqttOnMessageAsync);
  952. _mqtt.onConnect([](bool) {
  953. _mqttOnConnect();
  954. });
  955. _mqtt.onSubscribe([](uint16_t pid, int) {
  956. _mqttPidCallback(_mqtt_subscribe_callbacks, pid);
  957. });
  958. _mqtt.onPublish([](uint16_t pid) {
  959. _mqttPidCallback(_mqtt_publish_callbacks, pid);
  960. });
  961. _mqtt.onDisconnect([](AsyncMqttClientDisconnectReason reason) {
  962. switch (reason) {
  963. case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED:
  964. DEBUG_MSG_P(PSTR("[MQTT] TCP Disconnected\n"));
  965. break;
  966. case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED:
  967. DEBUG_MSG_P(PSTR("[MQTT] Identifier Rejected\n"));
  968. break;
  969. case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE:
  970. DEBUG_MSG_P(PSTR("[MQTT] Server unavailable\n"));
  971. break;
  972. case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS:
  973. DEBUG_MSG_P(PSTR("[MQTT] Malformed credentials\n"));
  974. break;
  975. case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED:
  976. DEBUG_MSG_P(PSTR("[MQTT] Not authorized\n"));
  977. break;
  978. case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT:
  979. #if ASYNC_TCP_SSL_ENABLED
  980. DEBUG_MSG_P(PSTR("[MQTT] Bad fingerprint\n"));
  981. #endif
  982. break;
  983. case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
  984. // This is never used by the AsyncMqttClient source
  985. #if 0
  986. DEBUG_MSG_P(PSTR("[MQTT] Unacceptable protocol version\n"));
  987. #endif
  988. break;
  989. case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE:
  990. DEBUG_MSG_P(PSTR("[MQTT] Connect packet too big\n"));
  991. break;
  992. }
  993. _mqttOnDisconnect();
  994. });
  995. #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  996. _mqtt.onMessageAdvanced([](MQTTClient* , char topic[], char payload[], int length) {
  997. _mqttOnMessage(topic, payload, length);
  998. });
  999. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  1000. _mqtt.setCallback([](char* topic, byte* payload, unsigned int length) {
  1001. _mqttOnMessage(topic, (char *) payload, length);
  1002. });
  1003. #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  1004. _mqttConfigure();
  1005. mqttRegister(_mqttCallback);
  1006. #if WEB_SUPPORT
  1007. wsRegister()
  1008. .onVisible(_mqttWebSocketOnVisible)
  1009. .onData(_mqttWebSocketOnData)
  1010. .onConnected(_mqttWebSocketOnConnected)
  1011. .onKeyCheck(_mqttWebSocketOnKeyCheck);
  1012. mqttRegister([](unsigned int type, const char*, const char*) {
  1013. if ((type == MQTT_CONNECT_EVENT) || (type == MQTT_DISCONNECT_EVENT)) {
  1014. wsPost(_mqttWebSocketOnData);
  1015. }
  1016. });
  1017. #endif
  1018. #if TERMINAL_SUPPORT
  1019. _mqttInitCommands();
  1020. #endif
  1021. // Main callbacks
  1022. espurnaRegisterLoop(mqttLoop);
  1023. espurnaRegisterReload(_mqttConfigure);
  1024. }
  1025. #endif // MQTT_SUPPORT