Added info and reboot api into the backend. Created the basics for a backend server.
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
#pragma once
|
// SDK
|
||||||
|
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "esp_app_format.h"
|
#include "esp_app_format.h"
|
||||||
|
#include "esp_chip_info.h"
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
#include "esp_ota_ops.h"
|
#include "esp_ota_ops.h"
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
|
|
||||||
|
// Project
|
||||||
|
#include "appstate.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
|
||||||
static esp_err_t api_system_info_handler(httpd_req_t *req) {
|
internal esp_err_t api_system_info_handler(httpd_req_t *req) {
|
||||||
httpd_resp_set_type(req, "application/json");
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
|
||||||
cJSON *root = cJSON_CreateObject();
|
cJSON *root = cJSON_CreateObject();
|
||||||
@@ -38,14 +41,10 @@ static esp_err_t api_system_info_handler(httpd_req_t *req) {
|
|||||||
const esp_app_desc_t *app_desc = esp_app_get_description();
|
const esp_app_desc_t *app_desc = esp_app_get_description();
|
||||||
cJSON_AddStringToObject(root, "firmware", app_desc->version);
|
cJSON_AddStringToObject(root, "firmware", app_desc->version);
|
||||||
|
|
||||||
// Relying on internal variables from main.cpp due to unity build
|
|
||||||
extern bool ethernetInitialized;
|
|
||||||
extern bool wifiInitialized;
|
|
||||||
|
|
||||||
const char *conn_type = "offline";
|
const char *conn_type = "offline";
|
||||||
if (ethernetInitialized) {
|
if (g_Ethernet_Initialized) {
|
||||||
conn_type = "ethernet";
|
conn_type = "ethernet";
|
||||||
} else if (wifiInitialized) {
|
} else if (g_Wifi_Initialized) {
|
||||||
conn_type = "wifi";
|
conn_type = "wifi";
|
||||||
}
|
}
|
||||||
cJSON_AddStringToObject(root, "connection", conn_type);
|
cJSON_AddStringToObject(root, "connection", conn_type);
|
||||||
@@ -59,7 +58,7 @@ static esp_err_t api_system_info_handler(httpd_req_t *req) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const httpd_uri_t api_system_info_uri = {.uri = "/api/system/info",
|
internal const httpd_uri_t api_system_info_uri = {.uri = "/api/system/info",
|
||||||
.method = HTTP_GET,
|
.method = HTTP_GET,
|
||||||
.handler =
|
.handler =
|
||||||
api_system_info_handler,
|
api_system_info_handler,
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
|
|
||||||
|
#include "types.hpp"
|
||||||
|
|
||||||
static void restart_timer_callback(void *arg) { esp_restart(); }
|
internal void restart_timer_callback(void *arg) { esp_restart(); }
|
||||||
|
|
||||||
static esp_err_t api_system_reboot_handler(httpd_req_t *req) {
|
internal esp_err_t api_system_reboot_handler(httpd_req_t *req) {
|
||||||
httpd_resp_set_type(req, "application/json");
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
|
||||||
cJSON *root = cJSON_CreateObject();
|
cJSON *root = cJSON_CreateObject();
|
||||||
@@ -24,18 +23,20 @@ static esp_err_t api_system_reboot_handler(httpd_req_t *req) {
|
|||||||
.callback = &restart_timer_callback,
|
.callback = &restart_timer_callback,
|
||||||
.arg = (void *)0,
|
.arg = (void *)0,
|
||||||
.dispatch_method = ESP_TIMER_TASK,
|
.dispatch_method = ESP_TIMER_TASK,
|
||||||
.name = "restart_timer"};
|
.name = "restart_timer",
|
||||||
|
.skip_unhandled_events = false};
|
||||||
esp_timer_handle_t restart_timer;
|
esp_timer_handle_t restart_timer;
|
||||||
esp_timer_create(&restart_timer_args, &restart_timer);
|
esp_timer_create(&restart_timer_args, &restart_timer);
|
||||||
|
|
||||||
// Schedule reboot 1 second from now to allow HTTP response to flush
|
// Schedule reboot 1 second (in microseconds) from now to allow HTTP response
|
||||||
esp_timer_start_once(restart_timer, 1000000);
|
// to flush
|
||||||
|
esp_timer_start_once(restart_timer, 1'000'000);
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const httpd_uri_t api_system_reboot_uri = {.uri = "/api/system/reboot",
|
internal const httpd_uri_t api_system_reboot_uri = {
|
||||||
|
.uri = "/api/system/reboot",
|
||||||
.method = HTTP_POST,
|
.method = HTTP_POST,
|
||||||
.handler =
|
.handler = api_system_reboot_handler,
|
||||||
api_system_reboot_handler,
|
|
||||||
.user_ctx = NULL};
|
.user_ctx = NULL};
|
||||||
|
|||||||
7
Provider/main/appstate.hpp
Normal file
7
Provider/main/appstate.hpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.hpp"
|
||||||
|
|
||||||
|
// Shared Application State (Unity Build)
|
||||||
|
internal bool g_Ethernet_Initialized = false;
|
||||||
|
internal bool g_Wifi_Initialized = false;
|
||||||
183
Provider/main/http_server.cpp
Normal file
183
Provider/main/http_server.cpp
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// SDK
|
||||||
|
#include "esp_http_server.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_vfs.h"
|
||||||
|
|
||||||
|
// Project
|
||||||
|
#include "api/system/info.cpp"
|
||||||
|
#include "api/system/reboot.cpp"
|
||||||
|
|
||||||
|
internal const char *TAG = "HTTP_SERVER";
|
||||||
|
|
||||||
|
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
|
||||||
|
#define SCRATCH_BUFSIZE 4096
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char scratch[SCRATCH_BUFSIZE];
|
||||||
|
} http_server_data_t;
|
||||||
|
|
||||||
|
#ifdef CONFIG_CALENDINK_DEPLOY_WEB_PAGES
|
||||||
|
// Set HTTP response content type according to file extension
|
||||||
|
internal esp_err_t set_content_type_from_file(httpd_req_t *req,
|
||||||
|
const char *filepath) {
|
||||||
|
const char *type = "text/plain";
|
||||||
|
if (strstr(filepath, ".html")) {
|
||||||
|
type = "text/html";
|
||||||
|
} else if (strstr(filepath, ".js")) {
|
||||||
|
type = "application/javascript";
|
||||||
|
} else if (strstr(filepath, ".css")) {
|
||||||
|
type = "text/css";
|
||||||
|
} else if (strstr(filepath, ".png")) {
|
||||||
|
type = "image/png";
|
||||||
|
} else if (strstr(filepath, ".ico")) {
|
||||||
|
type = "image/x-icon";
|
||||||
|
} else if (strstr(filepath, ".svg")) {
|
||||||
|
type = "text/xml";
|
||||||
|
}
|
||||||
|
return httpd_resp_set_type(req, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler to serve static files from LittleFS
|
||||||
|
internal esp_err_t static_file_handler(httpd_req_t *req) {
|
||||||
|
char filepath[FILE_PATH_MAX];
|
||||||
|
|
||||||
|
// Construct real file path
|
||||||
|
strlcpy(filepath, "/www", sizeof(filepath));
|
||||||
|
if (req->uri[strlen(req->uri) - 1] == '/') {
|
||||||
|
strlcat(filepath, "/index.html", sizeof(filepath));
|
||||||
|
} else {
|
||||||
|
strlcat(filepath, req->uri, sizeof(filepath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to index.html if file doesn't exist (SPA routing support)
|
||||||
|
struct stat file_stat;
|
||||||
|
if (stat(filepath, &file_stat) == -1) {
|
||||||
|
// Try gzipped first, then fallback to index.html
|
||||||
|
char filepath_gz[FILE_PATH_MAX];
|
||||||
|
snprintf(filepath_gz, sizeof(filepath_gz), "%s.gz", filepath);
|
||||||
|
if (stat(filepath_gz, &file_stat) == 0) {
|
||||||
|
strlcpy(filepath, filepath_gz, sizeof(filepath));
|
||||||
|
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "File not found: %s, falling back to index.html", filepath);
|
||||||
|
snprintf(filepath, sizeof(filepath), "%s/index.html", "/www");
|
||||||
|
if (stat(filepath, &file_stat) == -1) {
|
||||||
|
// If index.html doesn't exist, try index.html.gz
|
||||||
|
snprintf(filepath_gz, sizeof(filepath_gz), "%s/index.html.gz", "/www");
|
||||||
|
if (stat(filepath_gz, &file_stat) == 0) {
|
||||||
|
strlcpy(filepath, filepath_gz, sizeof(filepath));
|
||||||
|
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "index.html not found too.");
|
||||||
|
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File not found");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Since ESP32 handles .gz transparently if we tell it to via headers
|
||||||
|
// If we requested explicitly a .gz file, set the header
|
||||||
|
if (strstr(filepath, ".gz")) {
|
||||||
|
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = open(filepath, O_RDONLY, 0);
|
||||||
|
if (fd == -1) {
|
||||||
|
ESP_LOGE(TAG, "Failed to open file: %s", filepath);
|
||||||
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to read existing file");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_content_type_from_file(req, filepath);
|
||||||
|
|
||||||
|
http_server_data_t *rest_context = (http_server_data_t *)req->user_ctx;
|
||||||
|
char *chunk = rest_context->scratch;
|
||||||
|
ssize_t read_bytes;
|
||||||
|
|
||||||
|
do {
|
||||||
|
read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
|
||||||
|
if (read_bytes == -1) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read file: %s", filepath);
|
||||||
|
} else if (read_bytes > 0) {
|
||||||
|
if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
|
||||||
|
close(fd);
|
||||||
|
ESP_LOGE(TAG, "File sending failed!");
|
||||||
|
httpd_resp_sendstr_chunk(req, NULL); // Abort sending
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (read_bytes > 0);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0); // End response
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Handler for CORS Preflight OPTIONS requests
|
||||||
|
internal esp_err_t cors_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, POST, OPTIONS");
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type");
|
||||||
|
httpd_resp_set_status(req, "204 No Content");
|
||||||
|
httpd_resp_send(req, NULL, 0);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal httpd_handle_t start_webserver(void) {
|
||||||
|
http_server_data_t *rest_context =
|
||||||
|
(http_server_data_t *)calloc(1, sizeof(http_server_data_t));
|
||||||
|
if (rest_context == NULL) {
|
||||||
|
ESP_LOGE(TAG, "No memory for rest context");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||||
|
config.max_uri_handlers = 10; // We have info, reboot, options, and static
|
||||||
|
|
||||||
|
httpd_handle_t server = NULL;
|
||||||
|
ESP_LOGI(TAG, "Starting HTTP Server on port: '%d'", config.server_port);
|
||||||
|
|
||||||
|
if (httpd_start(&server, &config) == ESP_OK) {
|
||||||
|
// Register CORS OPTIONS handler for API routes
|
||||||
|
httpd_uri_t cors_options_uri = {.uri = "/api/*",
|
||||||
|
.method = HTTP_OPTIONS,
|
||||||
|
.handler = cors_options_handler,
|
||||||
|
.user_ctx = NULL};
|
||||||
|
httpd_register_uri_handler(server, &cors_options_uri);
|
||||||
|
|
||||||
|
// Register system API routes
|
||||||
|
httpd_register_uri_handler(server, &api_system_info_uri);
|
||||||
|
httpd_register_uri_handler(server, &api_system_reboot_uri);
|
||||||
|
|
||||||
|
#ifdef CONFIG_CALENDINK_DEPLOY_WEB_PAGES
|
||||||
|
// Register static file handler last as a catch-all wildcard if deployed
|
||||||
|
httpd_uri_t static_get_uri = {.uri = "/*",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = static_file_handler,
|
||||||
|
.user_ctx = rest_context};
|
||||||
|
httpd_register_uri_handler(server, &static_get_uri);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGE(TAG, "Error starting server!");
|
||||||
|
free(rest_context);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void stop_webserver(httpd_handle_t server) {
|
||||||
|
if (server) {
|
||||||
|
httpd_stop(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,26 +10,32 @@
|
|||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "soc/gpio_num.h"
|
#include "soc/gpio_num.h"
|
||||||
|
|
||||||
// Project cpp
|
// Project headers
|
||||||
#include "connect.cpp"
|
#include "appstate.hpp"
|
||||||
#include "led_status.cpp"
|
#include "types.hpp"
|
||||||
|
|
||||||
// TODO : Make it configurable
|
// Project cpp (Unity Build entry)
|
||||||
internal constexpr bool blockUntilEthernetEstablished = false;
|
// clang-format off
|
||||||
internal bool ethernetInitialized = false;
|
#include "connect.cpp"
|
||||||
internal bool wifiInitialized = false;
|
#include "http_server.cpp"
|
||||||
|
#include "led_status.cpp"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
internal constexpr bool kBlockUntilEthernetEstablished = false;
|
||||||
|
|
||||||
extern "C" void app_main() {
|
extern "C" void app_main() {
|
||||||
printf("Hello, Worldi!\n");
|
printf("Hello, Worldi!\n");
|
||||||
|
|
||||||
|
httpd_handle_t web_server = NULL;
|
||||||
|
|
||||||
ESP_ERROR_CHECK(nvs_flash_init());
|
ESP_ERROR_CHECK(nvs_flash_init());
|
||||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
|
|
||||||
setup_led();
|
setup_led();
|
||||||
|
|
||||||
set_led_status(led_status::ConnectingEthernet);
|
set_led_status(led_status::ConnectingEthernet);
|
||||||
ethernetInitialized = true;
|
g_Ethernet_Initialized = true;
|
||||||
esp_err_t result = connect_ethernet(blockUntilEthernetEstablished);
|
esp_err_t result = connect_ethernet(kBlockUntilEthernetEstablished);
|
||||||
if (result != ESP_OK) {
|
if (result != ESP_OK) {
|
||||||
set_led_status(led_status::Failed);
|
set_led_status(led_status::Failed);
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
@@ -37,7 +43,7 @@ extern "C" void app_main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for ethernet connection until its made
|
// Check for ethernet connection until its made
|
||||||
if (!blockUntilEthernetEstablished) {
|
if (!kBlockUntilEthernetEstablished) {
|
||||||
uint8 retries = 1;
|
uint8 retries = 1;
|
||||||
do {
|
do {
|
||||||
set_led_status(led_status::ConnectingEthernet);
|
set_led_status(led_status::ConnectingEthernet);
|
||||||
@@ -56,20 +62,20 @@ extern "C" void app_main() {
|
|||||||
if (result != ESP_OK) {
|
if (result != ESP_OK) {
|
||||||
printf("Ethernet failed, trying wifi\n");
|
printf("Ethernet failed, trying wifi\n");
|
||||||
disconnect_ethernet();
|
disconnect_ethernet();
|
||||||
ethernetInitialized = false;
|
g_Ethernet_Initialized = false;
|
||||||
|
|
||||||
set_led_status(led_status::ConnectingWifi);
|
set_led_status(led_status::ConnectingWifi);
|
||||||
wifiInitialized = true;
|
g_Wifi_Initialized = true;
|
||||||
result =
|
result =
|
||||||
connect_wifi(CONFIG_CALENDINK_WIFI_SSID, CONFIG_CALENDINK_WIFI_PASSWORD,
|
connect_wifi(CONFIG_CALENDINK_WIFI_SSID, CONFIG_CALENDINK_WIFI_PASSWORD,
|
||||||
blockUntilEthernetEstablished);
|
kBlockUntilEthernetEstablished);
|
||||||
if (result != ESP_OK) {
|
if (result != ESP_OK) {
|
||||||
set_led_status(led_status::Failed);
|
set_led_status(led_status::Failed);
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
goto shutdown;
|
goto shutdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!blockUntilEthernetEstablished) {
|
if (!kBlockUntilEthernetEstablished) {
|
||||||
uint8 retries = 1;
|
uint8 retries = 1;
|
||||||
do {
|
do {
|
||||||
set_led_status(led_status::ConnectingWifi);
|
set_led_status(led_status::ConnectingWifi);
|
||||||
@@ -98,20 +104,25 @@ extern "C" void app_main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
printf("Connected!\n");
|
printf("Connected!\n");
|
||||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
|
||||||
|
|
||||||
// TODO Main loop
|
// Start the webserver
|
||||||
|
web_server = start_webserver();
|
||||||
|
|
||||||
shutdown:
|
shutdown:
|
||||||
printf("Shutting down.\n");
|
printf("Shutting down.\n");
|
||||||
|
|
||||||
if (ethernetInitialized) {
|
if (web_server) {
|
||||||
disconnect_ethernet();
|
stop_webserver(web_server);
|
||||||
ethernetInitialized = false;
|
web_server = NULL;
|
||||||
}
|
}
|
||||||
if (wifiInitialized) {
|
|
||||||
|
if (g_Ethernet_Initialized) {
|
||||||
|
disconnect_ethernet();
|
||||||
|
g_Ethernet_Initialized = false;
|
||||||
|
}
|
||||||
|
if (g_Wifi_Initialized) {
|
||||||
disconnect_wifi();
|
disconnect_wifi();
|
||||||
wifiInitialized = false;
|
g_Wifi_Initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy_led();
|
destroy_led();
|
||||||
|
|||||||
Reference in New Issue
Block a user