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.

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