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.

414 lines
11 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. /*
  2. THINGSPEAK MODULE
  3. Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if THINGSPEAK_SUPPORT
  6. #if THINGSPEAK_USE_ASYNC
  7. #include <ESPAsyncTCP.h>
  8. #else
  9. #include <ESP8266WiFi.h>
  10. #endif
  11. #define THINGSPEAK_DATA_BUFFER_SIZE 256
  12. const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
  13. "POST %s HTTP/1.1\r\n"
  14. "Host: %s\r\n"
  15. "User-Agent: ESPurna\r\n"
  16. "Connection: close\r\n"
  17. "Content-Type: application/x-www-form-urlencoded\r\n"
  18. "Content-Length: %d\r\n\r\n";
  19. bool _tspk_enabled = false;
  20. bool _tspk_clear = false;
  21. char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL};
  22. String _tspk_data;
  23. bool _tspk_flush = false;
  24. unsigned long _tspk_last_flush = 0;
  25. unsigned char _tspk_tries = THINGSPEAK_TRIES;
  26. #if THINGSPEAK_USE_ASYNC
  27. AsyncClient * _tspk_client;
  28. bool _tspk_connecting = false;
  29. bool _tspk_connected = false;
  30. #endif
  31. // -----------------------------------------------------------------------------
  32. #if BROKER_SUPPORT
  33. void _tspkBrokerCallback(const unsigned char type, const char * topic, unsigned char id, const char * payload) {
  34. // Process status messages
  35. if (BROKER_MSG_TYPE_STATUS == type) {
  36. tspkEnqueueRelay(id, (char *) payload);
  37. tspkFlush();
  38. }
  39. // Porcess sensor messages
  40. if (BROKER_MSG_TYPE_SENSOR == type) {
  41. //tspkEnqueueMeasurement(id, (char *) payload);
  42. //tspkFlush();
  43. }
  44. }
  45. #endif // BROKER_SUPPORT
  46. #if WEB_SUPPORT
  47. bool _tspkWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  48. return (strncmp(key, "tspk", 4) == 0);
  49. }
  50. void _tspkWebSocketOnVisible(JsonObject& root) {
  51. root["tspkVisible"] = static_cast<unsigned char>(haveRelaysOrSensors());
  52. }
  53. void _tspkWebSocketOnConnected(JsonObject& root) {
  54. root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
  55. root["tspkKey"] = getSetting("tspkKey");
  56. root["tspkClear"] = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
  57. JsonArray& relays = root.createNestedArray("tspkRelays");
  58. for (byte i=0; i<relayCount(); i++) {
  59. relays.add(getSetting("tspkRelay", i, 0).toInt());
  60. }
  61. #if SENSOR_SUPPORT
  62. _sensorWebSocketMagnitudes(root, "tspk");
  63. #endif
  64. }
  65. #endif
  66. void _tspkConfigure() {
  67. _tspk_clear = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
  68. _tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
  69. if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) {
  70. _tspk_enabled = false;
  71. setSetting("tspkEnabled", 0);
  72. }
  73. if (_tspk_enabled && !_tspk_client) _tspkInitClient();
  74. }
  75. #if THINGSPEAK_USE_ASYNC
  76. enum class tspk_state_t : uint8_t {
  77. NONE,
  78. HEADERS,
  79. BODY
  80. };
  81. tspk_state_t _tspk_client_state = tspk_state_t::NONE;
  82. unsigned long _tspk_client_ts = 0;
  83. constexpr const unsigned long THINGSPEAK_CLIENT_TIMEOUT = 5000;
  84. void _tspkInitClient() {
  85. _tspk_client = new AsyncClient();
  86. _tspk_client->onDisconnect([](void * s, AsyncClient * client) {
  87. DEBUG_MSG_P(PSTR("[THINGSPEAK] Disconnected\n"));
  88. _tspk_data = "";
  89. _tspk_client_ts = 0;
  90. _tspk_last_flush = millis();
  91. _tspk_connected = false;
  92. _tspk_connecting = false;
  93. _tspk_client_state = tspk_state_t::NONE;
  94. }, nullptr);
  95. _tspk_client->onTimeout([](void * s, AsyncClient * client, uint32_t time) {
  96. DEBUG_MSG_P(PSTR("[THINGSPEAK] Network timeout after %ums\n"), time);
  97. client->close(true);
  98. }, nullptr);
  99. _tspk_client->onPoll([](void * s, AsyncClient * client) {
  100. uint32_t ts = millis() - _tspk_client_ts;
  101. if (ts > THINGSPEAK_CLIENT_TIMEOUT) {
  102. DEBUG_MSG_P(PSTR("[THINGSPEAK] No response after %ums\n"), ts);
  103. client->close(true);
  104. }
  105. }, nullptr);
  106. _tspk_client->onData([](void * arg, AsyncClient * client, void * response, size_t len) {
  107. char * p = nullptr;
  108. do {
  109. p = nullptr;
  110. switch (_tspk_client_state) {
  111. case tspk_state_t::NONE:
  112. {
  113. p = strnstr(reinterpret_cast<const char *>(response), "HTTP/1.1 200 OK", len);
  114. if (!p) {
  115. client->close(true);
  116. return;
  117. }
  118. _tspk_client_state = tspk_state_t::HEADERS;
  119. continue;
  120. }
  121. case tspk_state_t::HEADERS:
  122. {
  123. p = strnstr(reinterpret_cast<const char *>(response), "\r\n\r\n", len);
  124. if (!p) return;
  125. _tspk_client_state = tspk_state_t::BODY;
  126. }
  127. case tspk_state_t::BODY:
  128. {
  129. if (!p) {
  130. p = strnstr(reinterpret_cast<const char *>(response), "\r\n\r\n", len);
  131. if (!p) return;
  132. }
  133. unsigned int code = (p) ? atoi(&p[4]) : 0;
  134. DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %u\n"), code);
  135. if ((0 == code) && _tspk_tries) {
  136. _tspk_flush = true;
  137. DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing %u more time(s)\n"), _tspk_tries);
  138. } else {
  139. _tspkClearQueue();
  140. }
  141. client->close(true);
  142. _tspk_client_state = tspk_state_t::NONE;
  143. }
  144. }
  145. } while (_tspk_client_state != tspk_state_t::NONE);
  146. }, nullptr);
  147. _tspk_client->onConnect([](void * arg, AsyncClient * client) {
  148. _tspk_connected = true;
  149. _tspk_connecting = false;
  150. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%u\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
  151. #if THINGSPEAK_USE_SSL
  152. uint8_t fp[20] = {0};
  153. sslFingerPrintArray(THINGSPEAK_FINGERPRINT, fp);
  154. SSL * ssl = _tspk_client->getSSL();
  155. if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
  156. DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
  157. }
  158. #endif
  159. DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), THINGSPEAK_URL, _tspk_data.c_str());
  160. char headers[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + 1];
  161. snprintf_P(headers, sizeof(headers),
  162. THINGSPEAK_REQUEST_TEMPLATE,
  163. THINGSPEAK_URL,
  164. THINGSPEAK_HOST,
  165. _tspk_data.length()
  166. );
  167. client->write(headers);
  168. client->write(_tspk_data.c_str());
  169. }, nullptr);
  170. }
  171. void _tspkPost() {
  172. if (_tspk_connected || _tspk_connecting) return;
  173. _tspk_client_ts = millis();
  174. #if ASYNC_TCP_SSL_ENABLED
  175. bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT, THINGSPEAK_USE_SSL);
  176. #else
  177. bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT);
  178. #endif
  179. _tspk_connecting = connected;
  180. if (!connected) {
  181. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
  182. _tspk_client->close(true);
  183. }
  184. }
  185. #else // THINGSPEAK_USE_ASYNC
  186. void _tspkPost() {
  187. #if THINGSPEAK_USE_SSL
  188. WiFiClientSecure _tspk_client;
  189. #else
  190. WiFiClient _tspk_client;
  191. #endif
  192. if (_tspk_client.connect(THINGSPEAK_HOST, THINGSPEAK_PORT)) {
  193. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%u\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
  194. if (!_tspk_client.verify(THINGSPEAK_FINGERPRINT, THINGSPEAK_HOST)) {
  195. DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
  196. }
  197. DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), THINGSPEAK_URL, _tspk_data.c_str());
  198. char headers[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + 1];
  199. snprintf_P(headers, sizeof(headers),
  200. THINGSPEAK_REQUEST_TEMPLATE,
  201. THINGSPEAK_URL,
  202. THINGSPEAK_HOST,
  203. _tspk_data.length()
  204. );
  205. _tspk_client.print(headers);
  206. _tspk_client.print(_tspk_data);
  207. nice_delay(100);
  208. String response = _tspk_client.readString();
  209. int pos = response.indexOf("\r\n\r\n");
  210. unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0;
  211. DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %u\n"), code);
  212. _tspk_client.stop();
  213. _tspk_last_flush = millis();
  214. if ((0 == code) && _tspk_tries) {
  215. _tspk_flush = true;
  216. DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing %u more time(s)\n"), _tspk_tries);
  217. } else {
  218. _tspkClearQueue();
  219. }
  220. return;
  221. }
  222. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
  223. }
  224. #endif // THINGSPEAK_USE_ASYNC
  225. void _tspkEnqueue(unsigned char index, char * payload) {
  226. DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%u with value %s\n"), index, payload);
  227. --index;
  228. if (_tspk_queue[index] != NULL) free(_tspk_queue[index]);
  229. _tspk_queue[index] = strdup(payload);
  230. }
  231. void _tspkClearQueue() {
  232. _tspk_tries = THINGSPEAK_TRIES;
  233. if (_tspk_clear) {
  234. for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
  235. if (_tspk_queue[id] != NULL) {
  236. free(_tspk_queue[id]);
  237. _tspk_queue[id] = NULL;
  238. }
  239. }
  240. }
  241. }
  242. void _tspkFlush() {
  243. if (!_tspk_flush) return;
  244. if (millis() - _tspk_last_flush < THINGSPEAK_MIN_INTERVAL) return;
  245. if (_tspk_connected || _tspk_connecting) return;
  246. _tspk_last_flush = millis();
  247. _tspk_flush = false;
  248. _tspk_data.reserve(THINGSPEAK_DATA_BUFFER_SIZE);
  249. // Walk the fields, numbered 1...THINGSPEAK_FIELDS
  250. for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
  251. if (_tspk_queue[id] != NULL) {
  252. if (_tspk_data.length() > 0) _tspk_data.concat("&");
  253. char buf[32] = {0};
  254. snprintf_P(buf, sizeof(buf), PSTR("field%u=%s"), (id + 1), _tspk_queue[id]);
  255. _tspk_data.concat(buf);
  256. }
  257. }
  258. // POST data if any
  259. if (_tspk_data.length()) {
  260. _tspk_data.concat("&api_key=");
  261. _tspk_data.concat(getSetting("tspkKey"));
  262. --_tspk_tries;
  263. _tspkPost();
  264. }
  265. }
  266. // -----------------------------------------------------------------------------
  267. bool tspkEnqueueRelay(unsigned char index, char * payload) {
  268. if (!_tspk_enabled) return true;
  269. unsigned char id = getSetting("tspkRelay", index, 0).toInt();
  270. if (id > 0) {
  271. _tspkEnqueue(id, payload);
  272. return true;
  273. }
  274. return false;
  275. }
  276. bool tspkEnqueueMeasurement(unsigned char index, char * payload) {
  277. if (!_tspk_enabled) return true;
  278. unsigned char id = getSetting("tspkMagnitude", index, 0).toInt();
  279. if (id > 0) {
  280. _tspkEnqueue(id, payload);
  281. return true;
  282. }
  283. return false;
  284. }
  285. void tspkFlush() {
  286. _tspk_flush = true;
  287. }
  288. bool tspkEnabled() {
  289. return _tspk_enabled;
  290. }
  291. void tspkSetup() {
  292. _tspkConfigure();
  293. #if WEB_SUPPORT
  294. wsRegister()
  295. .onVisible(_tspkWebSocketOnVisible)
  296. .onConnected(_tspkWebSocketOnConnected)
  297. .onKeyCheck(_tspkWebSocketOnKeyCheck);
  298. #endif
  299. #if BROKER_SUPPORT
  300. brokerRegister(_tspkBrokerCallback);
  301. #endif
  302. DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),
  303. THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED",
  304. THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED"
  305. );
  306. // Main callbacks
  307. espurnaRegisterLoop(tspkLoop);
  308. espurnaRegisterReload(_tspkConfigure);
  309. }
  310. void tspkLoop() {
  311. if (!_tspk_enabled) return;
  312. if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return;
  313. _tspkFlush();
  314. }
  315. #endif