/* * SPDX-FileCopyrightText: 2022 Helmut Pozimski * * SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include "storage.h" #define TAG "api" #define BUF_LEN 1000 static TaskHandle_t* alarm_task_handle = NULL; static char* extract_wakeup(const char* uri) { return strrchr(uri, '/') +1; } static uint8_t contains_weekday(char* param) { uint8_t result = 0; for (int i = 1; i<=NUM_WEEK_DAYS; i++) { if (strcmp(WEEK_DAYS[i], param) == 0) { result = 1; break; } } return result; } static uint8_t day_is_valid(char* day) { return strlen(day) == 8 || contains_weekday(day); } static esp_err_t wakeup_get_handler(httpd_req_t *req) { httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); char* wakeup_str = extract_wakeup(req->uri); if (!day_is_valid(wakeup_str)) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid day requested"); return ESP_OK; } int16_t wakeup_minutes; esp_err_t ret = read_wakeup_time_str(wakeup_str, &wakeup_minutes); if (ret == ESP_OK) { httpd_resp_set_type(req, "application/json"); cJSON *root = cJSON_CreateObject(); if (wakeup_minutes >= 0) { cJSON_AddNumberToObject(root, "hour", wakeup_minutes / 60); cJSON_AddNumberToObject(root, "minute", wakeup_minutes % 60); } const char *response = cJSON_Print(root); httpd_resp_sendstr(req, response); } else if (ret == ESP_ERR_NVS_NOT_FOUND) { httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Requested entry not found"); } else { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Request could not be processed"); } return ESP_OK; } static const httpd_uri_t wakeup_get = { .uri = "/v1/wakeup/*", .method = HTTP_GET, .handler = wakeup_get_handler }; static uint8_t validate_time(int hour, int minute) { return (hour >= 0 && hour < 24) && (minute >= 0 && minute < 60); } static void notify_alarm_task() { if (alarm_task_handle != NULL) { xTaskNotify(*alarm_task_handle, 0, eNoAction); } } static esp_err_t wakeup_put_handler(httpd_req_t *req) { httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); char* wakeup_str = extract_wakeup(req->uri); char buf[BUF_LEN]; if (!day_is_valid(wakeup_str)) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid day requested"); return ESP_OK; } if (req->content_len >= BUF_LEN) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Maximum content length exceeded"); return ESP_OK; } int bytes_received = httpd_req_recv(req, buf, BUF_LEN); if (bytes_received == 0) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "No content received"); return ESP_OK; } else if (bytes_received < 0) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Request could not be processed"); return ESP_OK; } buf[req->content_len] = '\0'; cJSON *root = cJSON_Parse(buf); if (root == NULL) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Could not parse object"); return ESP_OK; } int16_t minute_of_day; if (cJSON_GetArraySize(root) == 0) { minute_of_day = -1; } else { int16_t hour = cJSON_GetObjectItem(root, "hour")->valueint; int16_t minute = cJSON_GetObjectItem(root, "minute")->valueint; if (validate_time(hour, minute)) { minute_of_day = hour * 60 + minute; } else { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Time values outside allowed range"); cJSON_Delete(root); return ESP_OK; } } esp_err_t ret = write_wakeup_time_str(wakeup_str, minute_of_day); if (ret == ESP_OK) { httpd_resp_sendstr(req, ""); notify_alarm_task(); } else { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Error while writing data"); } cJSON_Delete(root); return ESP_OK; } static const httpd_uri_t wakeup_put = { .uri = "/v1/wakeup/*", .method = HTTP_PUT, .handler = wakeup_put_handler }; static esp_err_t wakeup_delete_handler(httpd_req_t *req) { httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); char* wakeup_str = extract_wakeup(req->uri); if (!day_is_valid(wakeup_str)) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid day requested"); return ESP_OK; } esp_err_t ret = delete_wakeup_time_str(wakeup_str); if (ret == ESP_OK) { httpd_resp_sendstr(req, ""); return ret; } else if (ret == ESP_ERR_NVS_NOT_FOUND) { httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Entry does not exist"); } httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Error while deleting data"); return ESP_OK; } static const httpd_uri_t wakeup_delete = { .uri = "/v1/wakeup/*", .method = HTTP_DELETE, .handler = wakeup_delete_handler }; static esp_err_t wakeup_options_handler(httpd_req_t *req) { httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, PUT, DELETE, OPTIONS"); httpd_resp_set_hdr(req, "Access-Control-Max-Age", "86400"); httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type"); httpd_resp_sendstr(req, ""); return ESP_OK; } static const httpd_uri_t wakeup_options = { .uri = "/v1/wakeup/*", .method = HTTP_OPTIONS, .handler = wakeup_options_handler }; httpd_handle_t start_webserver(TaskHandle_t* task_handle) { httpd_handle_t server; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.lru_purge_enable = true; config.uri_match_fn = httpd_uri_match_wildcard; if (task_handle != NULL) { alarm_task_handle = task_handle; } ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); if (httpd_start(&server, &config) == ESP_OK) { ESP_LOGI(TAG, "Registering URI handlers"); httpd_register_uri_handler(server, &wakeup_get); httpd_register_uri_handler(server, &wakeup_put); httpd_register_uri_handler(server, &wakeup_delete); httpd_register_uri_handler(server, &wakeup_options); return server; } ESP_LOGI(TAG, "Error starting server!"); return NULL; } esp_err_t stop_webserver(httpd_handle_t server) { return httpd_stop(server); } void disconnect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { httpd_handle_t* server = (httpd_handle_t*) arg; if (*server) { ESP_LOGI(TAG, "Stopping webserver"); if (stop_webserver(*server) == ESP_OK) { *server = NULL; } else { ESP_LOGE(TAG, "Failed to stop http server"); } } } void connect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { httpd_handle_t* server = (httpd_handle_t*) arg; if (*server == NULL) { ESP_LOGI(TAG, "Starting webserver"); *server = start_webserver(NULL); } }