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.

265 lines
7.5 KiB

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