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.

320 lines
8.7 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. /*
  2. THINGSPEAK MODULE
  3. Copyright (C) 2018 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. AsyncClient * _tspk_client;
  9. #else
  10. #include <ESP8266WiFi.h>
  11. #endif
  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. "%s\r\n";
  20. bool _tspk_enabled = false;
  21. bool _tspk_clear = false;
  22. char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL};
  23. bool _tspk_flush = false;
  24. unsigned long _tspk_last_flush = 0;
  25. unsigned char _tspk_tries = 0;
  26. // -----------------------------------------------------------------------------
  27. #if WEB_SUPPORT
  28. bool _tspkWebSocketOnReceive(const char * key, JsonVariant& value) {
  29. return (strncmp(key, "tspk", 4) == 0);
  30. }
  31. void _tspkWebSocketOnSend(JsonObject& root) {
  32. unsigned char visible = 0;
  33. root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
  34. root["tspkKey"] = getSetting("tspkKey");
  35. root["tspkClear"] = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
  36. JsonArray& relays = root.createNestedArray("tspkRelays");
  37. for (byte i=0; i<relayCount(); i++) {
  38. relays.add(getSetting("tspkRelay", i, 0).toInt());
  39. }
  40. if (relayCount() > 0) visible = 1;
  41. #if SENSOR_SUPPORT
  42. JsonArray& list = root.createNestedArray("tspkMagnitudes");
  43. for (byte i=0; i<magnitudeCount(); i++) {
  44. JsonObject& element = list.createNestedObject();
  45. element["name"] = magnitudeName(i);
  46. element["type"] = magnitudeType(i);
  47. element["index"] = magnitudeIndex(i);
  48. element["idx"] = getSetting("tspkMagnitude", i, 0).toInt();
  49. }
  50. if (magnitudeCount() > 0) visible = 1;
  51. #endif
  52. root["tspkVisible"] = visible;
  53. }
  54. #endif
  55. void _tspkConfigure() {
  56. _tspk_clear = getSetting("tspkClear", THINGSPEAK_CLEAR_CACHE).toInt() == 1;
  57. _tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
  58. if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) {
  59. _tspk_enabled = false;
  60. setSetting("tspkEnabled", 0);
  61. }
  62. }
  63. #if THINGSPEAK_USE_ASYNC
  64. void _tspkPost(String data) {
  65. if (_tspk_client == NULL) {
  66. _tspk_client = new AsyncClient();
  67. }
  68. _tspk_client->onDisconnect([](void *s, AsyncClient *c) {
  69. DEBUG_MSG_P(PSTR("[THINGSPEAK] Disconnected\n"));
  70. _tspk_client->free();
  71. delete _tspk_client;
  72. _tspk_client = NULL;
  73. }, 0);
  74. _tspk_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) {
  75. _tspk_client->close(true);
  76. }, 0);
  77. _tspk_client->onData([](void * arg, AsyncClient * c, void * response, size_t len) {
  78. char * b = (char *) response;
  79. b[len] = 0;
  80. char * p = strstr((char *)response, "\r\n\r\n");
  81. unsigned int code = (p != NULL) ? atoi(&p[4]) : 0;
  82. DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
  83. _tspk_last_flush = millis();
  84. if ((0 == code) && (--_tspk_tries > 0)) {
  85. _tspk_flush = true;
  86. DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing\n"));
  87. } else {
  88. _tspkClearQueue();
  89. }
  90. _tspk_client->close(true);
  91. }, NULL);
  92. _tspk_client->onConnect([data](void * arg, AsyncClient * client) {
  93. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
  94. #if THINGSPEAK_USE_SSL
  95. uint8_t fp[20] = {0};
  96. sslFingerPrintArray(THINGSPEAK_FINGERPRINT, fp);
  97. SSL * ssl = _tspk_client->getSSL();
  98. if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
  99. DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
  100. }
  101. #endif
  102. DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), THINGSPEAK_URL, data.c_str());
  103. char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
  104. snprintf_P(buffer, sizeof(buffer),
  105. THINGSPEAK_REQUEST_TEMPLATE,
  106. THINGSPEAK_URL,
  107. THINGSPEAK_HOST,
  108. data.length(),
  109. data.c_str()
  110. );
  111. client->write(buffer);
  112. }, NULL);
  113. #if ASYNC_TCP_SSL_ENABLED
  114. bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT, THINGSPEAK_USE_SSL);
  115. #else
  116. bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT);
  117. #endif
  118. if (!connected) {
  119. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
  120. _tspk_client->close(true);
  121. }
  122. }
  123. #else // THINGSPEAK_USE_ASYNC
  124. void _tspkPost(String data) {
  125. #if THINGSPEAK_USE_SSL
  126. WiFiClientSecure _tspk_client;
  127. #else
  128. WiFiClient _tspk_client;
  129. #endif
  130. if (_tspk_client.connect(THINGSPEAK_HOST, THINGSPEAK_PORT)) {
  131. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
  132. if (!_tspk_client.verify(THINGSPEAK_FINGERPRINT, THINGSPEAK_HOST)) {
  133. DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
  134. }
  135. DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), THINGSPEAK_URL, data.c_str());
  136. char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
  137. snprintf_P(buffer, sizeof(buffer),
  138. THINGSPEAK_REQUEST_TEMPLATE,
  139. THINGSPEAK_URL,
  140. THINGSPEAK_HOST,
  141. data.length(),
  142. data.c_str()
  143. );
  144. _tspk_client.print(buffer);
  145. nice_delay(100);
  146. String response = _tspk_client.readString();
  147. int pos = response.indexOf("\r\n\r\n");
  148. unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0;
  149. DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
  150. _tspk_client.stop();
  151. _tspk_last_flush = millis();
  152. if ((0 == code) && (--_tspk_tries > 0)) {
  153. _tspk_flush = true;
  154. DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing\n"));
  155. } else {
  156. _tspkClearQueue();
  157. }
  158. return;
  159. }
  160. DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
  161. }
  162. #endif // THINGSPEAK_USE_ASYNC
  163. void _tspkEnqueue(unsigned char index, char * payload) {
  164. DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%d with value %s\n"), index, payload);
  165. --index;
  166. if (_tspk_queue[index] != NULL) free(_tspk_queue[index]);
  167. _tspk_queue[index] = strdup(payload);
  168. }
  169. void _tspkClearQueue() {
  170. if (_tspk_clear) {
  171. for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
  172. if (_tspk_queue[id] != NULL) {
  173. free(_tspk_queue[id]);
  174. _tspk_queue[id] = NULL;
  175. }
  176. }
  177. }
  178. }
  179. void _tspkFlush() {
  180. _tspk_flush = false;
  181. // Walk the fields
  182. String data;
  183. for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
  184. if (_tspk_queue[id] != NULL) {
  185. if (data.length() > 0) data = data + String("&");
  186. data = data + String("field") + String(id+1) + String("=") + String(_tspk_queue[id]);
  187. }
  188. }
  189. // POST data if any
  190. if (data.length() > 0) {
  191. data = data + String("&api_key=") + getSetting("tspkKey");
  192. _tspk_tries = THINGSPEAK_TRIES;
  193. _tspkPost(data);
  194. }
  195. }
  196. // -----------------------------------------------------------------------------
  197. bool tspkEnqueueRelay(unsigned char index, unsigned char status) {
  198. if (!_tspk_enabled) return true;
  199. unsigned char id = getSetting("tspkRelay", index, 0).toInt();
  200. if (id > 0) {
  201. char payload[3] = {0};
  202. itoa(status ? 1 : 0, payload, 10);
  203. _tspkEnqueue(id, payload);
  204. return true;
  205. }
  206. return false;
  207. }
  208. bool tspkEnqueueMeasurement(unsigned char index, char * payload) {
  209. if (!_tspk_enabled) return true;
  210. unsigned char id = getSetting("tspkMagnitude", index, 0).toInt();
  211. if (id > 0) {
  212. _tspkEnqueue(id, payload);
  213. return true;
  214. }
  215. return false;
  216. }
  217. void tspkFlush() {
  218. _tspk_flush = true;
  219. }
  220. bool tspkEnabled() {
  221. return _tspk_enabled;
  222. }
  223. void tspkSetup() {
  224. _tspkConfigure();
  225. #if WEB_SUPPORT
  226. wsOnSendRegister(_tspkWebSocketOnSend);
  227. wsOnReceiveRegister(_tspkWebSocketOnReceive);
  228. #endif
  229. DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),
  230. THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED",
  231. THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED"
  232. );
  233. // Main callbacks
  234. espurnaRegisterLoop(tspkLoop);
  235. espurnaRegisterReload(_tspkConfigure);
  236. }
  237. void tspkLoop() {
  238. if (!_tspk_enabled) return;
  239. if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return;
  240. if (_tspk_flush && (millis() - _tspk_last_flush > THINGSPEAK_MIN_INTERVAL)) {
  241. _tspkFlush();
  242. }
  243. }
  244. #endif