api.c 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. /*
  2. * SPDX-FileCopyrightText: 2022 Helmut Pozimski <helmut@pozimski.eu>
  3. *
  4. * SPDX-License-Identifier: GPL-2.0-only
  5. */
  6. #include <string.h>
  7. #include <esp_http_server.h>
  8. #include <esp_event.h>
  9. #include <esp_log.h>
  10. #include <esp_err.h>
  11. #include <cJSON.h>
  12. #include "storage.h"
  13. #define TAG "api"
  14. #define BUF_LEN 1000
  15. static TaskHandle_t* alarm_task_handle = NULL;
  16. static char* extract_wakeup(const char* uri) {
  17. return strrchr(uri, '/') +1;
  18. }
  19. static uint8_t contains_weekday(char* param) {
  20. uint8_t result = 0;
  21. for (int i = 1; i<=NUM_WEEK_DAYS; i++) {
  22. if (strcmp(WEEK_DAYS[i], param) == 0) {
  23. result = 1;
  24. break;
  25. }
  26. }
  27. return result;
  28. }
  29. static uint8_t day_is_valid(char* day) {
  30. return strlen(day) == 8 || contains_weekday(day);
  31. }
  32. static esp_err_t wakeup_get_handler(httpd_req_t *req) {
  33. httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  34. char* wakeup_str = extract_wakeup(req->uri);
  35. if (!day_is_valid(wakeup_str)) {
  36. httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid day requested");
  37. return ESP_OK;
  38. }
  39. int16_t wakeup_minutes;
  40. esp_err_t ret = read_wakeup_time_str(wakeup_str, &wakeup_minutes);
  41. if (ret == ESP_OK) {
  42. httpd_resp_set_type(req, "application/json");
  43. cJSON *root = cJSON_CreateObject();
  44. if (wakeup_minutes >= 0) {
  45. cJSON_AddNumberToObject(root, "hour", wakeup_minutes / 60);
  46. cJSON_AddNumberToObject(root, "minute", wakeup_minutes % 60);
  47. }
  48. const char *response = cJSON_Print(root);
  49. httpd_resp_sendstr(req, response);
  50. } else if (ret == ESP_ERR_NVS_NOT_FOUND) {
  51. httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Requested entry not found");
  52. } else {
  53. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Request could not be processed");
  54. }
  55. return ESP_OK;
  56. }
  57. static const httpd_uri_t wakeup_get = {
  58. .uri = "/v1/wakeup/*",
  59. .method = HTTP_GET,
  60. .handler = wakeup_get_handler
  61. };
  62. static uint8_t validate_time(int hour, int minute) {
  63. return (hour >= 0 && hour < 24) && (minute >= 0 && minute < 60);
  64. }
  65. static void notify_alarm_task() {
  66. if (alarm_task_handle != NULL) {
  67. xTaskNotify(*alarm_task_handle, 0, eNoAction);
  68. }
  69. }
  70. static esp_err_t wakeup_put_handler(httpd_req_t *req) {
  71. httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  72. char* wakeup_str = extract_wakeup(req->uri);
  73. char buf[BUF_LEN];
  74. if (!day_is_valid(wakeup_str)) {
  75. httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid day requested");
  76. return ESP_OK;
  77. }
  78. if (req->content_len >= BUF_LEN) {
  79. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Maximum content length exceeded");
  80. return ESP_OK;
  81. }
  82. int bytes_received = httpd_req_recv(req, buf, BUF_LEN);
  83. if (bytes_received == 0) {
  84. httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "No content received");
  85. return ESP_OK;
  86. } else if (bytes_received < 0) {
  87. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Request could not be processed");
  88. return ESP_OK;
  89. }
  90. buf[req->content_len] = '\0';
  91. cJSON *root = cJSON_Parse(buf);
  92. if (root == NULL) {
  93. httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Could not parse object");
  94. return ESP_OK;
  95. }
  96. int16_t minute_of_day;
  97. if (cJSON_GetArraySize(root) == 0) {
  98. minute_of_day = -1;
  99. } else {
  100. int16_t hour = cJSON_GetObjectItem(root, "hour")->valueint;
  101. int16_t minute = cJSON_GetObjectItem(root, "minute")->valueint;
  102. if (validate_time(hour, minute)) {
  103. minute_of_day = hour * 60 + minute;
  104. } else {
  105. httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Time values outside allowed range");
  106. cJSON_Delete(root);
  107. return ESP_OK;
  108. }
  109. }
  110. esp_err_t ret = write_wakeup_time_str(wakeup_str, minute_of_day);
  111. if (ret == ESP_OK) {
  112. httpd_resp_sendstr(req, "");
  113. notify_alarm_task();
  114. } else {
  115. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Error while writing data");
  116. }
  117. cJSON_Delete(root);
  118. return ESP_OK;
  119. }
  120. static const httpd_uri_t wakeup_put = {
  121. .uri = "/v1/wakeup/*",
  122. .method = HTTP_PUT,
  123. .handler = wakeup_put_handler
  124. };
  125. static esp_err_t wakeup_delete_handler(httpd_req_t *req) {
  126. httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  127. char* wakeup_str = extract_wakeup(req->uri);
  128. if (!day_is_valid(wakeup_str)) {
  129. httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid day requested");
  130. return ESP_OK;
  131. }
  132. esp_err_t ret = delete_wakeup_time_str(wakeup_str);
  133. if (ret == ESP_OK) {
  134. httpd_resp_sendstr(req, "");
  135. return ret;
  136. } else if (ret == ESP_ERR_NVS_NOT_FOUND) {
  137. httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Entry does not exist");
  138. }
  139. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Error while deleting data");
  140. return ESP_OK;
  141. }
  142. static const httpd_uri_t wakeup_delete = {
  143. .uri = "/v1/wakeup/*",
  144. .method = HTTP_DELETE,
  145. .handler = wakeup_delete_handler
  146. };
  147. static esp_err_t wakeup_options_handler(httpd_req_t *req) {
  148. httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  149. httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, PUT, DELETE, OPTIONS");
  150. httpd_resp_set_hdr(req, "Access-Control-Max-Age", "86400");
  151. httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type");
  152. httpd_resp_sendstr(req, "");
  153. return ESP_OK;
  154. }
  155. static const httpd_uri_t wakeup_options = {
  156. .uri = "/v1/wakeup/*",
  157. .method = HTTP_OPTIONS,
  158. .handler = wakeup_options_handler
  159. };
  160. httpd_handle_t start_webserver(TaskHandle_t* task_handle) {
  161. httpd_handle_t server;
  162. httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  163. config.lru_purge_enable = true;
  164. config.uri_match_fn = httpd_uri_match_wildcard;
  165. if (task_handle != NULL) {
  166. alarm_task_handle = task_handle;
  167. }
  168. ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
  169. if (httpd_start(&server, &config) == ESP_OK) {
  170. ESP_LOGI(TAG, "Registering URI handlers");
  171. httpd_register_uri_handler(server, &wakeup_get);
  172. httpd_register_uri_handler(server, &wakeup_put);
  173. httpd_register_uri_handler(server, &wakeup_delete);
  174. httpd_register_uri_handler(server, &wakeup_options);
  175. return server;
  176. }
  177. ESP_LOGI(TAG, "Error starting server!");
  178. return NULL;
  179. }
  180. esp_err_t stop_webserver(httpd_handle_t server) {
  181. return httpd_stop(server);
  182. }
  183. void disconnect_handler(void* arg, esp_event_base_t event_base,
  184. int32_t event_id, void* event_data) {
  185. httpd_handle_t* server = (httpd_handle_t*) arg;
  186. if (*server) {
  187. ESP_LOGI(TAG, "Stopping webserver");
  188. if (stop_webserver(*server) == ESP_OK) {
  189. *server = NULL;
  190. } else {
  191. ESP_LOGE(TAG, "Failed to stop http server");
  192. }
  193. }
  194. }
  195. void connect_handler(void* arg, esp_event_base_t event_base,
  196. int32_t event_id, void* event_data) {
  197. httpd_handle_t* server = (httpd_handle_t*) arg;
  198. if (*server == NULL) {
  199. ESP_LOGI(TAG, "Starting webserver");
  200. *server = start_webserver(NULL);
  201. }
  202. }