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.

276 lines
8.1 KiB

  1. /*
  2. INFLUXDB MODULE
  3. Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if INFLUXDB_SUPPORT
  6. #include <ESPAsyncTCP.h>
  7. #include <map>
  8. #include <memory>
  9. #include "broker.h"
  10. const char INFLUXDB_REQUEST_TEMPLATE[] PROGMEM = "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%u\r\nContent-Length: %d\r\n\r\n";
  11. constexpr const unsigned long INFLUXDB_CLIENT_TIMEOUT = 5000;
  12. constexpr const size_t INFLUXDB_DATA_BUFFER_SIZE = 256;
  13. bool _idb_enabled = false;
  14. String _idb_host;
  15. uint16_t _idb_port = 0;
  16. std::map<String, String> _idb_values;
  17. String _idb_data;
  18. bool _idb_flush = false;
  19. std::unique_ptr<AsyncClient> _idb_client = nullptr;
  20. bool _idb_connecting = false;
  21. bool _idb_connected = false;
  22. uint32_t _idb_client_ts = 0;
  23. // -----------------------------------------------------------------------------
  24. void _idbInitClient() {
  25. _idb_client = std::make_unique<AsyncClient>();
  26. _idb_client->onDisconnect([](void * s, AsyncClient * client) {
  27. DEBUG_MSG_P(PSTR("[INFLUXDB] Disconnected\n"));
  28. _idb_flush = false;
  29. _idb_data = "";
  30. _idb_client_ts = 0;
  31. _idb_connected = false;
  32. _idb_connecting = false;
  33. }, nullptr);
  34. _idb_client->onTimeout([](void * s, AsyncClient * client, uint32_t time) {
  35. DEBUG_MSG_P(PSTR("[INFLUXDB] Network timeout after %ums\n"), time);
  36. client->close(true);
  37. }, nullptr);
  38. _idb_client->onData([](void * arg, AsyncClient * client, void * response, size_t len) {
  39. // ref: https://docs.influxdata.com/influxdb/v1.7/tools/api/#summary-table-1
  40. const char idb_success[] = "HTTP/1.1 204";
  41. const bool result = (len > sizeof(idb_success) && (0 == strncmp((char*) response, idb_success, sizeof(idb_success))));
  42. DEBUG_MSG_P(PSTR("[INFLUXDB] %s response after %ums\n"), result ? "Success" : "Failure", millis() - _idb_client_ts);
  43. _idb_client_ts = millis();
  44. client->close();
  45. }, nullptr);
  46. _idb_client->onPoll([](void * arg, AsyncClient * client) {
  47. unsigned long ts = millis() - _idb_client_ts;
  48. if (ts > INFLUXDB_CLIENT_TIMEOUT) {
  49. DEBUG_MSG_P(PSTR("[INFLUXDB] No response after %ums\n"), ts);
  50. client->close(true);
  51. return;
  52. }
  53. if (_idb_data.length()) {
  54. client->write(_idb_data.c_str(), _idb_data.length());
  55. _idb_data = "";
  56. }
  57. });
  58. _idb_client->onConnect([](void * arg, AsyncClient * client) {
  59. _idb_client_ts = millis();
  60. _idb_connected = true;
  61. _idb_connecting = false;
  62. DEBUG_MSG_P(PSTR("[INFLUXDB] Connected to %s:%u\n"),
  63. IPAddress(client->getRemoteAddress()).toString().c_str(),
  64. client->getRemotePort()
  65. );
  66. constexpr const int BUFFER_SIZE = 256;
  67. char headers[BUFFER_SIZE];
  68. int len = snprintf_P(headers, sizeof(headers), INFLUXDB_REQUEST_TEMPLATE,
  69. getSetting("idbDatabase", INFLUXDB_DATABASE).c_str(),
  70. getSetting("idbUsername", INFLUXDB_USERNAME).c_str(),
  71. getSetting("idbPassword", INFLUXDB_PASSWORD).c_str(),
  72. _idb_host.c_str(), _idb_port, _idb_data.length()
  73. );
  74. if ((len < 0) || (len > BUFFER_SIZE - 1)) {
  75. client->close(true);
  76. return;
  77. }
  78. client->write(headers, len);
  79. });
  80. }
  81. // -----------------------------------------------------------------------------
  82. bool _idbWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  83. return (strncmp(key, "idb", 3) == 0);
  84. }
  85. void _idbWebSocketOnVisible(JsonObject& root) {
  86. root["idbVisible"] = 1;
  87. }
  88. void _idbWebSocketOnConnected(JsonObject& root) {
  89. root["idbEnabled"] = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1;
  90. root["idbHost"] = getSetting("idbHost", INFLUXDB_HOST);
  91. root["idbPort"] = getSetting("idbPort", INFLUXDB_PORT).toInt();
  92. root["idbDatabase"] = getSetting("idbDatabase", INFLUXDB_DATABASE);
  93. root["idbUsername"] = getSetting("idbUsername", INFLUXDB_USERNAME);
  94. root["idbPassword"] = getSetting("idbPassword", INFLUXDB_PASSWORD);
  95. }
  96. void _idbConfigure() {
  97. _idb_enabled = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1;
  98. if (_idb_enabled && (getSetting("idbHost", INFLUXDB_HOST).length() == 0)) {
  99. _idb_enabled = false;
  100. setSetting("idbEnabled", 0);
  101. }
  102. if (_idb_enabled && !_idb_client) _idbInitClient();
  103. }
  104. #if BROKER_SUPPORT
  105. void _idbBrokerSensor(const String& topic, unsigned char id, double, const char* value) {
  106. idbSend(topic.c_str(), id, value);
  107. }
  108. void _idbBrokerStatus(const String& topic, unsigned char id, unsigned int value) {
  109. idbSend(topic.c_str(), id, String(int(value)).c_str());
  110. }
  111. #endif // BROKER_SUPPORT
  112. // -----------------------------------------------------------------------------
  113. bool idbSend(const char * topic, const char * payload) {
  114. if (!_idb_enabled) return false;
  115. if (_idb_connecting || _idb_connected) return false;
  116. _idb_values[topic] = payload;
  117. _idb_flush = true;
  118. return true;
  119. }
  120. void _idbSend(const String& host, const uint16_t port) {
  121. if (_idb_connected || _idb_connecting) return;
  122. DEBUG_MSG_P(PSTR("[INFLUXDB] Sending to %s:%u\n"), host.c_str(), port);
  123. // TODO: cache `Host: <host>:<port>` instead of storing things separately?
  124. _idb_host = host;
  125. _idb_port = port;
  126. _idb_client_ts = millis();
  127. _idb_connecting = _idb_client->connect(host.c_str(), port);
  128. if (!_idb_connecting) {
  129. DEBUG_MSG_P(PSTR("[INFLUXDB] Connection to %s:%u failed\n"), host.c_str(), port);
  130. _idb_client->close(true);
  131. }
  132. }
  133. void _idbFlush() {
  134. // Clean-up client object when not in use
  135. if (_idb_client && !_idb_enabled && !_idb_connected && !_idb_connecting) {
  136. _idb_client = nullptr;
  137. }
  138. // Wait until current connection is finished
  139. if (!_idb_flush) return;
  140. if (_idb_connected || _idb_connecting) return;
  141. // Wait until connected
  142. if (!wifiConnected()) return;
  143. // TODO: MDNS_CLIENT_SUPPORT is deprecated
  144. String host = getSetting("idbHost", INFLUXDB_HOST);
  145. #if MDNS_CLIENT_SUPPORT
  146. host = mdnsResolve(host);
  147. #endif
  148. const uint16_t port = getSetting("idbPort", INFLUXDB_PORT).toInt();
  149. // TODO: should we always store specific pairs like tspk keeps relay / sensor readings?
  150. // note that we also send heartbeat data, persistent values should be flagged
  151. const String device = getSetting("hostname");
  152. _idb_data = "";
  153. for (auto& pair : _idb_values) {
  154. if (!isNumber(pair.second.c_str())) {
  155. String quoted;
  156. quoted.reserve(pair.second.length() + 2);
  157. quoted += '"';
  158. quoted += pair.second;
  159. quoted += '"';
  160. pair.second = quoted;
  161. }
  162. char buffer[128] = {0};
  163. snprintf_P(buffer, sizeof(buffer),
  164. PSTR("%s,device=%s value=%s\n"),
  165. pair.first.c_str(), device.c_str(), pair.second.c_str()
  166. );
  167. _idb_data += buffer;
  168. }
  169. _idb_values.clear();
  170. _idbSend(host, port);
  171. }
  172. bool idbSend(const char * topic, unsigned char id, const char * payload) {
  173. char measurement[64];
  174. snprintf(measurement, sizeof(measurement), "%s,id=%d", topic, id);
  175. return idbSend(measurement, payload);
  176. }
  177. bool idbEnabled() {
  178. return _idb_enabled;
  179. }
  180. void idbSetup() {
  181. _idbConfigure();
  182. #if WEB_SUPPORT
  183. wsRegister()
  184. .onVisible(_idbWebSocketOnVisible)
  185. .onConnected(_idbWebSocketOnConnected)
  186. .onKeyCheck(_idbWebSocketOnKeyCheck);
  187. #endif
  188. #if BROKER_SUPPORT
  189. StatusBroker::Register(_idbBrokerStatus);
  190. SensorReportBroker::Register(_idbBrokerSensor);
  191. #endif
  192. espurnaRegisterReload(_idbConfigure);
  193. espurnaRegisterLoop(_idbFlush);
  194. _idb_data.reserve(INFLUXDB_DATA_BUFFER_SIZE);
  195. #if TERMINAL_SUPPORT
  196. terminalRegisterCommand(F("IDB.SEND"), [](Embedis* e) {
  197. if (e->argc != 4) {
  198. terminalError(F("idb.send <topic> <id> <value>"));
  199. return;
  200. }
  201. const String topic = e->argv[1];
  202. const auto id = atoi(e->argv[2]);
  203. const String value = e->argv[3];
  204. idbSend(topic.c_str(), id, value.c_str());
  205. });
  206. #endif
  207. }
  208. #endif