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.

253 lines
6.6 KiB

  1. /*
  2. API MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if API_SUPPORT
  6. #include <ESPAsyncTCP.h>
  7. #include <ESPAsyncWebServer.h>
  8. #include <ArduinoJson.h>
  9. #include <vector>
  10. #include "system.h"
  11. typedef struct {
  12. char * key;
  13. api_get_callback_f getFn = NULL;
  14. api_put_callback_f putFn = NULL;
  15. } web_api_t;
  16. std::vector<web_api_t> _apis;
  17. // -----------------------------------------------------------------------------
  18. bool _apiWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  19. return (strncmp(key, "api", 3) == 0);
  20. }
  21. void _apiWebSocketOnConnected(JsonObject& root) {
  22. root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
  23. root["apiKey"] = getSetting("apiKey", API_KEY);
  24. root["apiRealTime"] = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
  25. root["apiRestFul"] = getSetting("apiRestFul", API_RESTFUL).toInt() == 1;
  26. }
  27. void _apiConfigure() {
  28. // Nothing to do
  29. }
  30. // -----------------------------------------------------------------------------
  31. // API
  32. // -----------------------------------------------------------------------------
  33. bool _authAPI(AsyncWebServerRequest *request) {
  34. const String key = getSetting("apiKey", API_KEY);
  35. if (!key.length() || getSetting("apiEnabled", API_ENABLED).toInt() == 0) {
  36. DEBUG_MSG_P(PSTR("[WEBSERVER] HTTP API is not enabled\n"));
  37. request->send(403);
  38. return false;
  39. }
  40. AsyncWebParameter* p = request->getParam("apikey", (request->method() == HTTP_PUT));
  41. if (!p || !p->value().equals(key)) {
  42. DEBUG_MSG_P(PSTR("[WEBSERVER] Wrong / missing apikey parameter\n"));
  43. request->send(403);
  44. return false;
  45. }
  46. return true;
  47. }
  48. bool _asJson(AsyncWebServerRequest *request) {
  49. bool asJson = false;
  50. if (request->hasHeader("Accept")) {
  51. AsyncWebHeader* h = request->getHeader("Accept");
  52. asJson = h->value().equals("application/json");
  53. }
  54. return asJson;
  55. }
  56. void _onAPIsText(AsyncWebServerRequest *request) {
  57. AsyncResponseStream *response = request->beginResponseStream("text/plain");
  58. String output;
  59. output.reserve(48);
  60. for (unsigned int i=0; i < _apis.size(); i++) {
  61. output = "";
  62. output += _apis[i].key;
  63. output += " -> ";
  64. output += "/api/";
  65. output += _apis[i].key;
  66. output += '\n';
  67. response->write(output.c_str());
  68. }
  69. request->send(response);
  70. }
  71. constexpr const size_t API_JSON_BUFFER_SIZE = 1024;
  72. void _onAPIsJson(AsyncWebServerRequest *request) {
  73. DynamicJsonBuffer jsonBuffer(API_JSON_BUFFER_SIZE);
  74. JsonObject& root = jsonBuffer.createObject();
  75. constexpr const int BUFFER_SIZE = 48;
  76. for (unsigned int i=0; i < _apis.size(); i++) {
  77. char buffer[BUFFER_SIZE] = {0};
  78. int res = snprintf(buffer, sizeof(buffer), "/api/%s", _apis[i].key);
  79. if ((res < 0) || (res > (BUFFER_SIZE - 1))) {
  80. request->send(500);
  81. return;
  82. }
  83. root[_apis[i].key] = buffer;
  84. }
  85. AsyncResponseStream *response = request->beginResponseStream("application/json");
  86. root.printTo(*response);
  87. request->send(response);
  88. }
  89. void _onAPIs(AsyncWebServerRequest *request) {
  90. webLog(request);
  91. if (!_authAPI(request)) return;
  92. bool asJson = _asJson(request);
  93. String output;
  94. if (asJson) {
  95. _onAPIsJson(request);
  96. } else {
  97. _onAPIsText(request);
  98. }
  99. }
  100. void _onRPC(AsyncWebServerRequest *request) {
  101. webLog(request);
  102. if (!_authAPI(request)) return;
  103. //bool asJson = _asJson(request);
  104. int response = 404;
  105. if (request->hasParam("action")) {
  106. AsyncWebParameter* p = request->getParam("action");
  107. String action = p->value();
  108. DEBUG_MSG_P(PSTR("[RPC] Action: %s\n"), action.c_str());
  109. if (action.equals("reboot")) {
  110. response = 200;
  111. deferredReset(100, CUSTOM_RESET_RPC);
  112. }
  113. }
  114. request->send(response);
  115. }
  116. bool _apiRequestCallback(AsyncWebServerRequest *request) {
  117. String url = request->url();
  118. // Main API entry point
  119. if (url.equals("/api") || url.equals("/apis")) {
  120. _onAPIs(request);
  121. return true;
  122. }
  123. // Main RPC entry point
  124. if (url.equals("/rpc")) {
  125. _onRPC(request);
  126. return true;
  127. }
  128. // Not API request
  129. if (!url.startsWith("/api/")) return false;
  130. for (unsigned char i=0; i < _apis.size(); i++) {
  131. // Search API url
  132. web_api_t api = _apis[i];
  133. if (!url.endsWith(api.key)) continue;
  134. // Log and check credentials
  135. webLog(request);
  136. if (!_authAPI(request)) return false;
  137. // Check if its a PUT
  138. if (api.putFn != NULL) {
  139. if ((getSetting("apiRestFul", API_RESTFUL).toInt() != 1) || (request->method() == HTTP_PUT)) {
  140. if (request->hasParam("value", request->method() == HTTP_PUT)) {
  141. AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
  142. (api.putFn)((p->value()).c_str());
  143. }
  144. }
  145. }
  146. // Get response from callback
  147. char value[API_BUFFER_SIZE] = {0};
  148. (api.getFn)(value, API_BUFFER_SIZE);
  149. // The response will be a 404 NOT FOUND if the resource is not available
  150. if (0 == value[0]) {
  151. DEBUG_MSG_P(PSTR("[API] Sending 404 response\n"));
  152. request->send(404);
  153. return false;
  154. }
  155. DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), value);
  156. // Format response according to the Accept header
  157. if (_asJson(request)) {
  158. char buffer[64];
  159. if (isNumber(value)) {
  160. snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
  161. } else {
  162. snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": \"%s\" }"), api.key, value);
  163. }
  164. request->send(200, "application/json", buffer);
  165. } else {
  166. request->send(200, "text/plain", value);
  167. }
  168. return true;
  169. }
  170. return false;
  171. }
  172. // -----------------------------------------------------------------------------
  173. void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn) {
  174. // Store it
  175. web_api_t api;
  176. api.key = strdup(key);
  177. api.getFn = getFn;
  178. api.putFn = putFn;
  179. _apis.push_back(api);
  180. }
  181. void apiSetup() {
  182. _apiConfigure();
  183. wsRegister()
  184. .onVisible([](JsonObject& root) { root["apiVisible"] = 1; })
  185. .onConnected(_apiWebSocketOnConnected)
  186. .onKeyCheck(_apiWebSocketOnKeyCheck);
  187. webRequestRegister(_apiRequestCallback);
  188. espurnaRegisterReload(_apiConfigure);
  189. }
  190. #endif // API_SUPPORT