mirror of
https://github.com/HendrikRauh/dmx-interface.git
synced 2026-03-09 13:30:20 +00:00
add(web_server): init basic webserver
This commit is contained in:
parent
4d8be45e48
commit
7ea7944e91
10 changed files with 447 additions and 1 deletions
|
|
@ -1,6 +1,20 @@
|
||||||
|
# For more information about build system see
|
||||||
|
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||||
# The following five lines of boilerplate have to be in your project's
|
# The following five lines of boilerplate have to be in your project's
|
||||||
# CMakeLists in this exact order for cmake to work correctly
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
# Clear any stale EXTRA_COMPONENT_DIRS entries (e.g. leftover from previous runs or PlatformIO)
|
||||||
|
set(EXTRA_COMPONENT_DIRS "")
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
project(dmx-interface)
|
project(dmx-interface)
|
||||||
|
|
||||||
|
# Disable filesystem image creation for now
|
||||||
|
# Can be enabled later when data/ folder is properly configured
|
||||||
|
if(FALSE)
|
||||||
|
if(COMMAND littlefs_create_partition_image)
|
||||||
|
littlefs_create_partition_image(storage ${CMAKE_SOURCE_DIR}/data FLASH_IN_PROJECT)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
|
||||||
8
components/web_server/CMakeLists.txt
Normal file
8
components/web_server/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
idf_component_register(
|
||||||
|
SRCS "src/web_server.c"
|
||||||
|
"src/wifi.c"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES esp_http_server
|
||||||
|
PRIV_REQUIRES freertos esp_wifi esp_event esp_netif nvs_flash
|
||||||
|
)
|
||||||
|
|
||||||
58
components/web_server/include/web_server.h
Normal file
58
components/web_server/include/web_server.h
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* @file web_server.h
|
||||||
|
* @brief Simple HTTP web server component for ESP32 with async FreeRTOS support.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esp_http_server.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Web server configuration structure.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16_t port; ///< HTTP server port (default: 80)
|
||||||
|
size_t max_uri_handlers; ///< Maximum number of URI handlers
|
||||||
|
size_t stack_size; ///< FreeRTOS task stack size
|
||||||
|
UBaseType_t task_priority; ///< FreeRTOS task priority
|
||||||
|
} webserver_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize and start the HTTP web server.
|
||||||
|
*
|
||||||
|
* This function creates a FreeRTOS task that manages the HTTP server.
|
||||||
|
* It serves static files from the data/ folder and supports dynamic handler registration.
|
||||||
|
*
|
||||||
|
* @param config Configuration structure. If NULL, default values are used.
|
||||||
|
* @return HTTP server handle on success, NULL on failure.
|
||||||
|
*/
|
||||||
|
httpd_handle_t webserver_start(const webserver_config_t *config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop the web server and cleanup resources.
|
||||||
|
*
|
||||||
|
* @param server HTTP server handle returned by webserver_start().
|
||||||
|
* Safe to pass NULL.
|
||||||
|
*/
|
||||||
|
void webserver_stop(httpd_handle_t server);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a custom URI handler.
|
||||||
|
*
|
||||||
|
* This allows dynamic registration of API endpoints and other custom handlers.
|
||||||
|
*
|
||||||
|
* @param server HTTP server handle.
|
||||||
|
* @param uri_handler Pointer to httpd_uri_t structure.
|
||||||
|
* @return ESP_OK on success, error code otherwise.
|
||||||
|
*/
|
||||||
|
esp_err_t webserver_register_handler(httpd_handle_t server, const httpd_uri_t *uri_handler);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
15
components/web_server/include/wifi.h
Normal file
15
components/web_server/include/wifi.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel, uint8_t max_connections);
|
||||||
|
void wifi_stop_ap(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
201
components/web_server/src/web_server.c
Normal file
201
components/web_server/src/web_server.c
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
#include "web_server.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
static const char *TAG = "WEBSERVER";
|
||||||
|
|
||||||
|
// Default configuration values
|
||||||
|
#define WEBSERVER_DEFAULT_PORT 80
|
||||||
|
#define WEBSERVER_DEFAULT_MAX_HANDLERS 32
|
||||||
|
#define WEBSERVER_DEFAULT_STACK_SIZE (8 * 1024)
|
||||||
|
#define WEBSERVER_DEFAULT_TASK_PRIORITY 5
|
||||||
|
|
||||||
|
static httpd_handle_t s_server_handle = NULL;
|
||||||
|
static TaskHandle_t s_server_task_handle = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HTTP handler for root (GET /)
|
||||||
|
*/
|
||||||
|
static esp_err_t root_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
const char *html = "<!DOCTYPE html>"
|
||||||
|
"<html>"
|
||||||
|
"<head><title>DMX Interface</title></head>"
|
||||||
|
"<body>"
|
||||||
|
"<h1>DMX Interface</h1>"
|
||||||
|
"<p>Web server is running!</p>"
|
||||||
|
"<p><a href=\"/api/health\">Check Health</a></p>"
|
||||||
|
"</body>"
|
||||||
|
"</html>";
|
||||||
|
|
||||||
|
httpd_resp_set_type(req, "text/html");
|
||||||
|
httpd_resp_sendstr(req, html);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HTTP handler for API health check (GET /api/health)
|
||||||
|
*/
|
||||||
|
static esp_err_t health_check_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief FreeRTOS task function for the HTTP server.
|
||||||
|
* Allows non-blocking server operation and future extensibility.
|
||||||
|
*/
|
||||||
|
static void webserver_task(void *arg)
|
||||||
|
{
|
||||||
|
httpd_handle_t server = (httpd_handle_t)arg;
|
||||||
|
ESP_LOGI(TAG, "Web server task started");
|
||||||
|
|
||||||
|
// Keep task alive - the server runs in the background
|
||||||
|
while (s_server_handle != NULL)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10000)); // 10 second check interval
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Web server task ending");
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_handle_t webserver_start(const webserver_config_t *config)
|
||||||
|
{
|
||||||
|
if (s_server_handle != NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Web server already running");
|
||||||
|
return s_server_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use provided config or defaults
|
||||||
|
uint16_t port = WEBSERVER_DEFAULT_PORT;
|
||||||
|
size_t max_handlers = WEBSERVER_DEFAULT_MAX_HANDLERS;
|
||||||
|
size_t stack_size = WEBSERVER_DEFAULT_STACK_SIZE;
|
||||||
|
UBaseType_t task_priority = WEBSERVER_DEFAULT_TASK_PRIORITY;
|
||||||
|
|
||||||
|
if (config)
|
||||||
|
{
|
||||||
|
port = config->port;
|
||||||
|
max_handlers = config->max_uri_handlers;
|
||||||
|
stack_size = config->stack_size;
|
||||||
|
task_priority = config->task_priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP server configuration
|
||||||
|
httpd_config_t http_config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
http_config.server_port = port;
|
||||||
|
http_config.max_uri_handlers = max_handlers;
|
||||||
|
http_config.stack_size = stack_size;
|
||||||
|
http_config.uri_match_fn = httpd_uri_match_wildcard;
|
||||||
|
|
||||||
|
// Start HTTP server
|
||||||
|
esp_err_t ret = httpd_start(&s_server_handle, &http_config);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(ret));
|
||||||
|
s_server_handle = NULL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "HTTP server started on port %d", port);
|
||||||
|
|
||||||
|
// Register default handlers
|
||||||
|
// Health check endpoint
|
||||||
|
httpd_uri_t health_uri = {
|
||||||
|
.uri = "/api/health",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = health_check_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(s_server_handle, &health_uri);
|
||||||
|
|
||||||
|
// Root / index.html handler
|
||||||
|
httpd_uri_t root_uri = {
|
||||||
|
.uri = "/",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = root_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(s_server_handle, &root_uri);
|
||||||
|
|
||||||
|
// Wildcard handler for 404 (must be last)
|
||||||
|
httpd_uri_t wildcard_uri = {
|
||||||
|
.uri = "/*",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = NULL, // Let httpd handle as 404
|
||||||
|
.user_ctx = NULL,
|
||||||
|
};
|
||||||
|
// Don't register wildcard - just let httpd default to 404
|
||||||
|
|
||||||
|
// Create FreeRTOS task for the server
|
||||||
|
// This allows other tasks to continue running and makes the server async-ready
|
||||||
|
BaseType_t task_ret = xTaskCreate(
|
||||||
|
webserver_task,
|
||||||
|
"webserver",
|
||||||
|
stack_size,
|
||||||
|
(void *)s_server_handle,
|
||||||
|
task_priority,
|
||||||
|
&s_server_task_handle);
|
||||||
|
|
||||||
|
if (task_ret != pdPASS)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to create web server task");
|
||||||
|
httpd_stop(s_server_handle);
|
||||||
|
s_server_handle = NULL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Web server initialized successfully");
|
||||||
|
return s_server_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void webserver_stop(httpd_handle_t server)
|
||||||
|
{
|
||||||
|
if (server == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_stop(server);
|
||||||
|
s_server_handle = NULL;
|
||||||
|
|
||||||
|
// Wait for task to finish
|
||||||
|
if (s_server_task_handle != NULL)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
s_server_task_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Web server stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t webserver_register_handler(httpd_handle_t server, const httpd_uri_t *uri_handler)
|
||||||
|
{
|
||||||
|
if (server == NULL || uri_handler == NULL)
|
||||||
|
{
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ret = httpd_register_uri_handler(server, uri_handler);
|
||||||
|
if (ret == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Registered handler: %s [%d]", uri_handler->uri, uri_handler->method);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to register handler %s: %s", uri_handler->uri, esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
89
components/web_server/src/wifi.c
Normal file
89
components/web_server/src/wifi.c
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
#include "wifi.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_netif.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
static const char *TAG = "WIFI";
|
||||||
|
static bool s_wifi_started = false;
|
||||||
|
|
||||||
|
esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel, uint8_t max_connections)
|
||||||
|
{
|
||||||
|
if (s_wifi_started)
|
||||||
|
{
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ssid || strlen(ssid) == 0 || strlen(ssid) > 32)
|
||||||
|
{
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool has_password = password && strlen(password) > 0;
|
||||||
|
if (has_password && strlen(password) < 8)
|
||||||
|
{
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t err = nvs_flash_init();
|
||||||
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
||||||
|
{
|
||||||
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||||
|
err = nvs_flash_init();
|
||||||
|
}
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_netif_init());
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
|
esp_netif_create_default_wifi_ap();
|
||||||
|
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
|
||||||
|
wifi_config_t wifi_config = {
|
||||||
|
.ap = {
|
||||||
|
.channel = channel,
|
||||||
|
.max_connection = max_connections,
|
||||||
|
.authmode = has_password ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN,
|
||||||
|
.pmf_cfg = {
|
||||||
|
.required = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
strlcpy((char *)wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
|
||||||
|
wifi_config.ap.ssid_len = strlen(ssid);
|
||||||
|
|
||||||
|
if (has_password)
|
||||||
|
{
|
||||||
|
strlcpy((char *)wifi_config.ap.password, password, sizeof(wifi_config.ap.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_start());
|
||||||
|
|
||||||
|
s_wifi_started = true;
|
||||||
|
ESP_LOGI(TAG, "WiFi AP started: SSID=%s channel=%u", ssid, channel);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wifi_stop_ap(void)
|
||||||
|
{
|
||||||
|
if (!s_wifi_started)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_wifi_stop();
|
||||||
|
esp_wifi_deinit();
|
||||||
|
s_wifi_started = false;
|
||||||
|
ESP_LOGI(TAG, "WiFi AP stopped");
|
||||||
|
}
|
||||||
21
dependencies.lock
Normal file
21
dependencies.lock
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
dependencies:
|
||||||
|
idf:
|
||||||
|
source:
|
||||||
|
type: idf
|
||||||
|
version: 5.5.2
|
||||||
|
joltwallet/littlefs:
|
||||||
|
component_hash: 150ca47225f7c9917f7f610a5f85e5e93fe3c15402234c23496ff045ad558b0b
|
||||||
|
dependencies:
|
||||||
|
- name: idf
|
||||||
|
require: private
|
||||||
|
version: '>=5.0'
|
||||||
|
source:
|
||||||
|
registry_url: https://components.espressif.com/
|
||||||
|
type: service
|
||||||
|
version: 1.20.2
|
||||||
|
direct_dependencies:
|
||||||
|
- idf
|
||||||
|
- joltwallet/littlefs
|
||||||
|
manifest_hash: ff4b0b01cddb86fe710ecb8fe90983fdab6a922a91a7dcfade112bc73ef373e8
|
||||||
|
target: esp32c6
|
||||||
|
version: 2.0.0
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
idf_component_register(SRCS "dmx-interface.c"
|
idf_component_register(SRCS "dmx-interface.c"
|
||||||
INCLUDE_DIRS ".")
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES web_server)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,39 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "web_server.h"
|
||||||
|
#include "wifi.h"
|
||||||
|
|
||||||
|
static const char *TAG = "MAIN";
|
||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
{
|
{
|
||||||
|
ESP_LOGI(TAG, "DMX Interface starting...");
|
||||||
|
|
||||||
|
esp_err_t wifi_err = wifi_start_ap("DMX", "mbgmbgmbg", 1, 4);
|
||||||
|
if (wifi_err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to start WiFi AP: %s", esp_err_to_name(wifi_err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start HTTP web server
|
||||||
|
httpd_handle_t server = webserver_start(NULL);
|
||||||
|
if (server == NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to start web server!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Web server started successfully");
|
||||||
|
ESP_LOGI(TAG, "Open http://192.168.4.1 in your browser");
|
||||||
|
|
||||||
|
// Keep the app running
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
main/idf_component.yml
Normal file
5
main/idf_component.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
## IDF Component Manager Manifest File
|
||||||
|
dependencies:
|
||||||
|
idf:
|
||||||
|
version: ">=4.1.0"
|
||||||
|
joltwallet/littlefs: ==1.20.2
|
||||||
Loading…
Add table
Add a link
Reference in a new issue