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.

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