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.

1319 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. // Backported from the older utils implementation.
  458. // Wait until the time is synced to avoid sending partial report *and*
  459. // as a result, wait until the next interval to actually send the datetime string.
  460. #if NTP_SUPPORT
  461. if ((mask & heartbeat::Report::Datetime) && !ntpSynced()) {
  462. return false;
  463. }
  464. #endif
  465. if (!mqttConnected()) {
  466. return false;
  467. }
  468. // TODO: rework old HEARTBEAT_REPEAT_STATUS?
  469. // for example: send full report once, send only the dynamic data after that
  470. // (interval, hostname, description, ssid, bssid, ip, mac, rssi, uptime, datetime, heap, loadavg, vcc)
  471. // otherwise, it is still possible by setting everything to 0 *but* the Report::Status bit
  472. // TODO: per-module mask?
  473. // TODO: simply send static data with onConnected, and the rest from here?
  474. if (mask & heartbeat::Report::Status)
  475. mqttSendStatus();
  476. if (mask & heartbeat::Report::Interval)
  477. mqttSend(MQTT_TOPIC_INTERVAL, String(_mqtt_heartbeat_interval.count()).c_str());
  478. if (mask & heartbeat::Report::App)
  479. mqttSend(MQTT_TOPIC_APP, APP_NAME);
  480. if (mask & heartbeat::Report::Version)
  481. mqttSend(MQTT_TOPIC_VERSION, getVersion().c_str());
  482. if (mask & heartbeat::Report::Board)
  483. mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str());
  484. if (mask & heartbeat::Report::Hostname)
  485. mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname", getIdentifier()).c_str());
  486. if (mask & heartbeat::Report::Description) {
  487. auto desc = getSetting("desc");
  488. if (desc.length()) {
  489. mqttSend(MQTT_TOPIC_DESCRIPTION, desc.c_str());
  490. }
  491. }
  492. if (mask & heartbeat::Report::Ssid)
  493. mqttSend(MQTT_TOPIC_SSID, WiFi.SSID().c_str());
  494. if (mask & heartbeat::Report::Bssid)
  495. mqttSend(MQTT_TOPIC_BSSID, WiFi.BSSIDstr().c_str());
  496. if (mask & heartbeat::Report::Ip)
  497. mqttSend(MQTT_TOPIC_IP, getIP().c_str());
  498. if (mask & heartbeat::Report::Mac)
  499. mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str());
  500. if (mask & heartbeat::Report::Rssi)
  501. mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str());
  502. if (mask & heartbeat::Report::Uptime)
  503. mqttSend(MQTT_TOPIC_UPTIME, String(systemUptime()).c_str());
  504. #if NTP_SUPPORT
  505. if (mask & heartbeat::Report::Datetime)
  506. mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
  507. #endif
  508. if (mask & heartbeat::Report::Freeheap) {
  509. auto stats = systemHeapStats();
  510. mqttSend(MQTT_TOPIC_FREEHEAP, String(stats.available).c_str());
  511. }
  512. if (mask & heartbeat::Report::Loadavg)
  513. mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
  514. if ((mask & heartbeat::Report::Vcc) && (ADC_MODE_VALUE == ADC_VCC))
  515. mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str());
  516. auto status = mqttConnected();
  517. for (auto& cb : _mqtt_heartbeat_callbacks) {
  518. status = status && cb(mask);
  519. }
  520. return status;
  521. }
  522. void _mqttOnConnect() {
  523. _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
  524. _mqtt_last_connection = millis();
  525. _mqtt_state = AsyncClientState::Connected;
  526. systemHeartbeat(_mqttHeartbeat, _mqtt_heartbeat_mode, _mqtt_heartbeat_interval);
  527. // Notify all subscribers about the connection
  528. for (auto& callback : _mqtt_callbacks) {
  529. callback(MQTT_CONNECT_EVENT, nullptr, nullptr);
  530. }
  531. DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
  532. }
  533. void _mqttOnDisconnect() {
  534. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  535. _mqtt_publish_callbacks.clear();
  536. _mqtt_subscribe_callbacks.clear();
  537. #endif
  538. _mqtt_last_connection = millis();
  539. _mqtt_state = AsyncClientState::Disconnected;
  540. systemStopHeartbeat(_mqttHeartbeat);
  541. // Notify all subscribers about the disconnect
  542. for (auto& callback : _mqtt_callbacks) {
  543. callback(MQTT_DISCONNECT_EVENT, nullptr, nullptr);
  544. }
  545. DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n"));
  546. }
  547. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  548. // Run the associated callback when message PID is acknowledged by the broker
  549. void _mqttPidCallback(MqttPidCallbacks& callbacks, uint16_t pid) {
  550. if (callbacks.empty()) {
  551. return;
  552. }
  553. auto end = callbacks.end();
  554. auto prev = callbacks.before_begin();
  555. auto it = callbacks.begin();
  556. while (it != end) {
  557. if ((*it).pid == pid) {
  558. (*it).run();
  559. it = callbacks.erase_after(prev);
  560. } else {
  561. prev = it;
  562. ++it;
  563. }
  564. }
  565. }
  566. #endif
  567. // Force-skip everything received in a short window right after connecting to avoid syncronization issues.
  568. bool _mqttMaybeSkipRetained(char* topic) {
  569. if (_mqtt_skip_messages && (millis() - _mqtt_last_connection < _mqtt_skip_time)) {
  570. DEBUG_MSG_P(PSTR("[MQTT] Received %s - SKIPPED\n"), topic);
  571. return true;
  572. }
  573. _mqtt_skip_messages = false;
  574. return false;
  575. }
  576. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  577. // MQTT Broker can sometimes send messages in bulk. Even when message size is less than MQTT_BUFFER_MAX_SIZE, we *could*
  578. // receive a message with `len != total`, this requiring buffering of the received data. Prepare a static memory to store the
  579. // data until `(len + index) == total`.
  580. // TODO: One pending issue is streaming arbitrary data (e.g. binary, for OTA). We always set '\0' and API consumer expects C-String.
  581. // In that case, there could be MQTT_MESSAGE_RAW_EVENT and this callback only trigger on small messages.
  582. // 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.
  583. void _mqttOnMessageAsync(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
  584. if (!len || (len > MQTT_BUFFER_MAX_SIZE) || (total > MQTT_BUFFER_MAX_SIZE)) return;
  585. if (_mqttMaybeSkipRetained(topic)) return;
  586. static char message[((MQTT_BUFFER_MAX_SIZE + 1) + 31) & -32] = {0};
  587. memmove(message + index, (char *) payload, len);
  588. // Not done yet
  589. if (total != (len + index)) {
  590. DEBUG_MSG_P(PSTR("[MQTT] Buffered %s => %u / %u bytes\n"), topic, len, total);
  591. return;
  592. }
  593. message[len + index] = '\0';
  594. DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message);
  595. // Call subscribers with the message buffer
  596. for (auto& callback : _mqtt_callbacks) {
  597. callback(MQTT_MESSAGE_EVENT, topic, message);
  598. }
  599. }
  600. #else
  601. // Sync client already implements buffering, but we still need to add '\0' because API consumer expects C-String :/
  602. // TODO: consider reworking this (and async counterpart), giving callback func length of the message.
  603. void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
  604. if (!len || (len > MQTT_BUFFER_MAX_SIZE)) return;
  605. if (_mqttMaybeSkipRetained(topic)) return;
  606. static char message[((MQTT_BUFFER_MAX_SIZE + 1) + 31) & -32] = {0};
  607. memmove(message, (char *) payload, len);
  608. message[len] = '\0';
  609. DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message);
  610. // Call subscribers with the message buffer
  611. for (auto& callback : _mqtt_callbacks) {
  612. callback(MQTT_MESSAGE_EVENT, topic, message);
  613. }
  614. }
  615. #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  616. // -----------------------------------------------------------------------------
  617. // Public API
  618. // -----------------------------------------------------------------------------
  619. /**
  620. Returns the magnitude part of a topic
  621. @param topic the full MQTT topic
  622. @return String object with the magnitude part.
  623. */
  624. String mqttMagnitude(const char* topic) {
  625. String output;
  626. String pattern = _mqtt_topic + _mqtt_setter;
  627. int position = pattern.indexOf("#");
  628. if (position >= 0) {
  629. String start = pattern.substring(0, position);
  630. String end = pattern.substring(position + 1);
  631. String magnitude(topic);
  632. if (magnitude.startsWith(start) && magnitude.endsWith(end)) {
  633. magnitude.replace(start, "");
  634. magnitude.replace(end, "");
  635. output = std::move(magnitude);
  636. }
  637. }
  638. return output;
  639. }
  640. /**
  641. Returns a full MQTT topic from the magnitude
  642. @param magnitude the magnitude part of the topic.
  643. @param is_set whether to build a command topic (true)
  644. or a state topic (false).
  645. @return String full MQTT topic.
  646. */
  647. String mqttTopic(const char* magnitude, bool is_set) {
  648. String output;
  649. output.reserve(strlen(magnitude)
  650. + _mqtt_topic.length()
  651. + _mqtt_setter.length()
  652. + _mqtt_getter.length());
  653. output += _mqtt_topic;
  654. output.replace("#", magnitude);
  655. output += is_set ? _mqtt_setter : _mqtt_getter;
  656. return output;
  657. }
  658. /**
  659. Returns a full MQTT topic from the magnitude
  660. @param magnitude the magnitude part of the topic.
  661. @param index index of the magnitude when more than one such magnitudes.
  662. @param is_set whether to build a command topic (true)
  663. or a state topic (false).
  664. @return String full MQTT topic.
  665. */
  666. String mqttTopic(const char* magnitude, unsigned int index, bool is_set) {
  667. String output;
  668. output.reserve(strlen(magnitude) + (sizeof(decltype(index)) * 4));
  669. output += magnitude;
  670. output += '/';
  671. output += index;
  672. return mqttTopic(output.c_str(), is_set);
  673. }
  674. // -----------------------------------------------------------------------------
  675. uint16_t mqttSendRaw(const char * topic, const char * message, bool retain, int qos) {
  676. constexpr size_t MessageLogMax { 128ul };
  677. if (_mqtt.connected()) {
  678. const unsigned int packetId {
  679. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  680. _mqtt.publish(topic, qos, retain, message)
  681. #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  682. _mqtt.publish(topic, message, retain, qos)
  683. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  684. _mqtt.publish(topic, message, retain)
  685. #endif
  686. };
  687. const size_t message_len = strlen(message);
  688. if (message_len > MessageLogMax) {
  689. DEBUG_MSG_P(PSTR("[MQTT] Sending %s => (%u bytes) (PID %u)\n"), topic, message_len, packetId);
  690. } else {
  691. DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %u)\n"), topic, message, packetId);
  692. }
  693. return packetId;
  694. }
  695. return false;
  696. }
  697. uint16_t mqttSendRaw(const char * topic, const char * message, bool retain) {
  698. return mqttSendRaw(topic, message, retain, _mqtt_qos);
  699. }
  700. uint16_t mqttSendRaw(const char * topic, const char * message) {
  701. return mqttSendRaw(topic, message, _mqtt_retain, _mqtt_qos);
  702. }
  703. bool mqttSend(const char * topic, const char * message, bool force, bool retain) {
  704. if (!force && _mqtt_use_json) {
  705. mqttEnqueue(topic, message);
  706. _mqtt_json_payload_flush.once_ms(MQTT_USE_JSON_DELAY, mqttFlush);
  707. return true;
  708. }
  709. return mqttSendRaw(mqttTopic(topic, false).c_str(), message, retain) > 0;
  710. }
  711. bool mqttSend(const char * topic, const char * message, bool force) {
  712. return mqttSend(topic, message, force, _mqtt_retain);
  713. }
  714. bool mqttSend(const char * topic, const char * message) {
  715. return mqttSend(topic, message, false);
  716. }
  717. bool mqttSend(const char * topic, unsigned int index, const char * message, bool force, bool retain) {
  718. char buffer[strlen(topic)+5];
  719. snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
  720. return mqttSend(buffer, message, force, retain);
  721. }
  722. bool mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
  723. return mqttSend(topic, index, message, force, _mqtt_retain);
  724. }
  725. bool mqttSend(const char * topic, unsigned int index, const char * message) {
  726. return mqttSend(topic, index, message, false);
  727. }
  728. // -----------------------------------------------------------------------------
  729. constexpr size_t MqttJsonPayloadBufferSize { 1024ul };
  730. void mqttFlush() {
  731. if (!_mqtt.connected()) {
  732. return;
  733. }
  734. if (_mqtt_json_payload.empty()) {
  735. return;
  736. }
  737. DynamicJsonBuffer jsonBuffer(MqttJsonPayloadBufferSize);
  738. JsonObject& root = jsonBuffer.createObject();
  739. #if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME
  740. if (ntpSynced()) {
  741. root[MQTT_TOPIC_DATETIME] = ntpDateTime();
  742. }
  743. #endif
  744. #if MQTT_ENQUEUE_MAC
  745. root[MQTT_TOPIC_MAC] = WiFi.macAddress();
  746. #endif
  747. #if MQTT_ENQUEUE_HOSTNAME
  748. root[MQTT_TOPIC_HOSTNAME] = getSetting("hostname", getIdentifier());
  749. #endif
  750. #if MQTT_ENQUEUE_IP
  751. root[MQTT_TOPIC_IP] = getIP();
  752. #endif
  753. #if MQTT_ENQUEUE_MESSAGE_ID
  754. root[MQTT_TOPIC_MESSAGE_ID] = (Rtcmem->mqtt)++;
  755. #endif
  756. for (auto& payload : _mqtt_json_payload) {
  757. root[payload.topic().c_str()] = payload.message().c_str();
  758. }
  759. String output;
  760. root.printTo(output);
  761. jsonBuffer.clear();
  762. _mqtt_json_payload_count = 0;
  763. _mqtt_json_payload.clear();
  764. mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false);
  765. }
  766. void mqttEnqueue(const char* topic, const char* message) {
  767. // Queue is not meant to send message "offline"
  768. // We must prevent the queue does not get full while offline
  769. if (_mqtt.connected()) {
  770. if (_mqtt_json_payload_count >= MQTT_QUEUE_MAX_SIZE) {
  771. mqttFlush();
  772. }
  773. _mqtt_json_payload.remove_if([topic](const MqttPayload& payload) {
  774. return payload.topic() == topic;
  775. });
  776. _mqtt_json_payload.emplace_front(topic, message);
  777. ++_mqtt_json_payload_count;
  778. }
  779. }
  780. // -----------------------------------------------------------------------------
  781. // Only async client returns resulting PID, sync libraries return either success (1) or failure (0)
  782. uint16_t mqttSubscribeRaw(const char* topic, int qos) {
  783. uint16_t pid { 0u };
  784. if (_mqtt.connected() && (strlen(topic) > 0)) {
  785. pid = _mqtt.subscribe(topic, qos);
  786. DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, pid);
  787. }
  788. return pid;
  789. }
  790. uint16_t mqttSubscribeRaw(const char* topic) {
  791. return mqttSubscribeRaw(topic, _mqtt_qos);
  792. }
  793. bool mqttSubscribe(const char * topic) {
  794. return mqttSubscribeRaw(mqttTopic(topic, true).c_str(), _mqtt_qos);
  795. }
  796. uint16_t mqttUnsubscribeRaw(const char * topic) {
  797. uint16_t pid { 0u };
  798. if (_mqtt.connected() && (strlen(topic) > 0)) {
  799. pid = _mqtt.unsubscribe(topic);
  800. DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing from %s (PID %d)\n"), topic, pid);
  801. }
  802. return pid;
  803. }
  804. bool mqttUnsubscribe(const char * topic) {
  805. return mqttUnsubscribeRaw(mqttTopic(topic, true).c_str());
  806. }
  807. // -----------------------------------------------------------------------------
  808. void mqttEnabled(bool status) {
  809. _mqtt_enabled = status;
  810. }
  811. bool mqttEnabled() {
  812. return _mqtt_enabled;
  813. }
  814. bool mqttConnected() {
  815. return _mqtt.connected();
  816. }
  817. void mqttDisconnect() {
  818. if (_mqtt.connected()) {
  819. DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n"));
  820. _mqtt.disconnect();
  821. }
  822. }
  823. bool mqttForward() {
  824. return _mqtt_forward;
  825. }
  826. /**
  827. Register a persistent lifecycle callback
  828. @param standalone function pointer
  829. */
  830. void mqttRegister(mqtt_callback_f callback) {
  831. _mqtt_callbacks.push_front(callback);
  832. }
  833. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  834. /**
  835. Register a temporary publish callback
  836. @param callable object
  837. */
  838. void mqttOnPublish(uint16_t pid, mqtt_pid_callback_f callback) {
  839. auto callable = MqttPidCallback { pid, callback };
  840. _mqtt_publish_callbacks.push_front(std::move(callable));
  841. }
  842. /**
  843. Register a temporary subscribe callback
  844. @param callable object
  845. */
  846. void mqttOnSubscribe(uint16_t pid, mqtt_pid_callback_f callback) {
  847. auto callable = MqttPidCallback { pid, callback };
  848. _mqtt_subscribe_callbacks.push_front(std::move(callable));
  849. }
  850. #endif
  851. void mqttSetBroker(IPAddress ip, uint16_t port) {
  852. setSetting("mqttServer", ip.toString());
  853. _mqtt_server = ip.toString();
  854. setSetting("mqttPort", port);
  855. _mqtt_port = port;
  856. mqttEnabled(1 == MQTT_AUTOCONNECT);
  857. }
  858. void mqttSetBrokerIfNone(IPAddress ip, uint16_t port) {
  859. if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
  860. mqttSetBroker(ip, port);
  861. }
  862. }
  863. // TODO: these strings are only updated after running the configuration routine and when MQTT is *enabled*
  864. const String& mqttPayloadOnline() {
  865. return _mqtt_payload_online;
  866. }
  867. const String& mqttPayloadOffline() {
  868. return _mqtt_payload_offline;
  869. }
  870. const char* mqttPayloadStatus(bool status) {
  871. return status ? _mqtt_payload_online.c_str() : _mqtt_payload_offline.c_str();
  872. }
  873. void mqttSendStatus() {
  874. mqttSend(MQTT_TOPIC_STATUS, _mqtt_payload_online.c_str(), true);
  875. }
  876. // -----------------------------------------------------------------------------
  877. // Initialization
  878. // -----------------------------------------------------------------------------
  879. void _mqttConnect() {
  880. // Do not connect if already connected or still trying to connect
  881. if (_mqtt.connected() || (_mqtt_state != AsyncClientState::Disconnected)) return;
  882. // Do not connect if disabled or no WiFi
  883. if (!_mqtt_enabled || (WiFi.status() != WL_CONNECTED)) return;
  884. // Check reconnect interval
  885. if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) return;
  886. // Increase the reconnect delay
  887. _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
  888. if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
  889. _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
  890. }
  891. DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%hu\n"), _mqtt_server.c_str(), _mqtt_port);
  892. DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid.c_str());
  893. DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos);
  894. DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %c\n"), _mqtt_retain ? 'Y' : 'N');
  895. DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %hu (s)\n"), _mqtt_keepalive);
  896. DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will.c_str());
  897. _mqtt_state = AsyncClientState::Connecting;
  898. _mqtt_skip_messages = (_mqtt_skip_time > 0);
  899. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  900. const bool secure = getSetting("mqttUseSSL", 1 == MQTT_SSL_ENABLED);
  901. #else
  902. const bool secure = false;
  903. #endif
  904. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  905. _mqttSetupAsyncClient(secure);
  906. #elif (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT)
  907. if (_mqttSetupSyncClient(secure) && _mqttConnectSyncClient(secure)) {
  908. _mqttOnConnect();
  909. } else {
  910. DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
  911. _mqttOnDisconnect();
  912. }
  913. #else
  914. #error "please check that MQTT_LIBRARY is valid"
  915. #endif
  916. }
  917. void mqttLoop() {
  918. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  919. _mqttConnect();
  920. #else
  921. if (_mqtt.connected()) {
  922. _mqtt.loop();
  923. } else {
  924. if (_mqtt_state != AsyncClientState::Disconnected) {
  925. _mqttOnDisconnect();
  926. }
  927. _mqttConnect();
  928. }
  929. #endif
  930. }
  931. void mqttHeartbeat(heartbeat::Callback callback) {
  932. _mqtt_heartbeat_callbacks.push_front(callback);
  933. }
  934. void mqttSetup() {
  935. _mqttBackwards();
  936. _mqttInfo();
  937. #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  938. // XXX: should not place this in config, addServerFingerprint does not check for duplicates
  939. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  940. {
  941. if (_mqtt_sc_config.on_fingerprint) {
  942. const String fingerprint = _mqtt_sc_config.on_fingerprint();
  943. uint8_t buffer[20] = {0};
  944. if (sslFingerPrintArray(fingerprint.c_str(), buffer)) {
  945. _mqtt.addServerFingerprint(buffer);
  946. }
  947. }
  948. }
  949. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  950. _mqtt.onMessage(_mqttOnMessageAsync);
  951. _mqtt.onConnect([](bool) {
  952. _mqttOnConnect();
  953. });
  954. _mqtt.onSubscribe([](uint16_t pid, int) {
  955. _mqttPidCallback(_mqtt_subscribe_callbacks, pid);
  956. });
  957. _mqtt.onPublish([](uint16_t pid) {
  958. _mqttPidCallback(_mqtt_publish_callbacks, pid);
  959. });
  960. _mqtt.onDisconnect([](AsyncMqttClientDisconnectReason reason) {
  961. switch (reason) {
  962. case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED:
  963. DEBUG_MSG_P(PSTR("[MQTT] TCP Disconnected\n"));
  964. break;
  965. case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED:
  966. DEBUG_MSG_P(PSTR("[MQTT] Identifier Rejected\n"));
  967. break;
  968. case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE:
  969. DEBUG_MSG_P(PSTR("[MQTT] Server unavailable\n"));
  970. break;
  971. case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS:
  972. DEBUG_MSG_P(PSTR("[MQTT] Malformed credentials\n"));
  973. break;
  974. case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED:
  975. DEBUG_MSG_P(PSTR("[MQTT] Not authorized\n"));
  976. break;
  977. case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT:
  978. #if ASYNC_TCP_SSL_ENABLED
  979. DEBUG_MSG_P(PSTR("[MQTT] Bad fingerprint\n"));
  980. #endif
  981. break;
  982. case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
  983. // This is never used by the AsyncMqttClient source
  984. #if 0
  985. DEBUG_MSG_P(PSTR("[MQTT] Unacceptable protocol version\n"));
  986. #endif
  987. break;
  988. case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE:
  989. DEBUG_MSG_P(PSTR("[MQTT] Connect packet too big\n"));
  990. break;
  991. }
  992. _mqttOnDisconnect();
  993. });
  994. #elif MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT
  995. _mqtt.onMessageAdvanced([](MQTTClient* , char topic[], char payload[], int length) {
  996. _mqttOnMessage(topic, payload, length);
  997. });
  998. #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT
  999. _mqtt.setCallback([](char* topic, byte* payload, unsigned int length) {
  1000. _mqttOnMessage(topic, (char *) payload, length);
  1001. });
  1002. #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT
  1003. _mqttConfigure();
  1004. mqttRegister(_mqttCallback);
  1005. #if WEB_SUPPORT
  1006. wsRegister()
  1007. .onVisible(_mqttWebSocketOnVisible)
  1008. .onData(_mqttWebSocketOnData)
  1009. .onConnected(_mqttWebSocketOnConnected)
  1010. .onKeyCheck(_mqttWebSocketOnKeyCheck);
  1011. mqttRegister([](unsigned int type, const char*, const char*) {
  1012. if ((type == MQTT_CONNECT_EVENT) || (type == MQTT_DISCONNECT_EVENT)) {
  1013. wsPost(_mqttWebSocketOnData);
  1014. }
  1015. });
  1016. #endif
  1017. #if TERMINAL_SUPPORT
  1018. _mqttInitCommands();
  1019. #endif
  1020. // Main callbacks
  1021. espurnaRegisterLoop(mqttLoop);
  1022. espurnaRegisterReload(_mqttConfigure);
  1023. }
  1024. #endif // MQTT_SUPPORT