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