mirror of
https://github.com/HendrikRauh/dmx-interface.git
synced 2026-03-09 13:30:20 +00:00
chore(format): initial formatting
This commit is contained in:
parent
fa08fcfe65
commit
008c79852b
21 changed files with 1021 additions and 1082 deletions
|
|
@ -21,9 +21,9 @@
|
||||||
|
|
||||||
## 📱 Implemented microcontrollers
|
## 📱 Implemented microcontrollers
|
||||||
|
|
||||||
- [x] Lolin S2 mini
|
- [x] Lolin S2 mini
|
||||||
- [ ] ESP 32 WROOM
|
- [ ] ESP 32 WROOM
|
||||||
- [ ] ESP 32 C3
|
- [ ] ESP 32 C3
|
||||||
|
|
||||||
> For other microcontrollers you may need to adjust the `platformio.ini`
|
> For other microcontrollers you may need to adjust the `platformio.ini`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,22 @@
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize and mount LittleFS filesystem
|
* @brief Initialize and mount LittleFS filesystem
|
||||||
*
|
*
|
||||||
* @return ESP_OK on success, error code otherwise
|
* @return ESP_OK on success, error code otherwise
|
||||||
*/
|
*/
|
||||||
esp_err_t storage_init(void);
|
esp_err_t storage_init(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the mount point for the LittleFS filesystem
|
* @brief Get the mount point for the LittleFS filesystem
|
||||||
*
|
*
|
||||||
* @return Pointer to the mount point string (e.g., "/data")
|
* @return Pointer to the mount point string (e.g., "/data")
|
||||||
*/
|
*/
|
||||||
const char *storage_get_mount_point(void);
|
const char *storage_get_mount_point(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@
|
||||||
static const char *TAG = "STORAGE";
|
static const char *TAG = "STORAGE";
|
||||||
static const char *LITTLEFS_MOUNT_POINT = "/data";
|
static const char *LITTLEFS_MOUNT_POINT = "/data";
|
||||||
|
|
||||||
esp_err_t storage_init(void)
|
esp_err_t storage_init(void) {
|
||||||
{
|
|
||||||
esp_vfs_littlefs_conf_t conf = {
|
esp_vfs_littlefs_conf_t conf = {
|
||||||
.base_path = LITTLEFS_MOUNT_POINT,
|
.base_path = LITTLEFS_MOUNT_POINT,
|
||||||
.partition_label = "storage",
|
.partition_label = "storage",
|
||||||
|
|
@ -18,18 +17,12 @@ esp_err_t storage_init(void)
|
||||||
|
|
||||||
esp_err_t ret = esp_vfs_littlefs_register(&conf);
|
esp_err_t ret = esp_vfs_littlefs_register(&conf);
|
||||||
|
|
||||||
if (ret != ESP_OK)
|
if (ret != ESP_OK) {
|
||||||
{
|
if (ret == ESP_FAIL) {
|
||||||
if (ret == ESP_FAIL)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Failed to mount LittleFS or format filesystem");
|
ESP_LOGE(TAG, "Failed to mount LittleFS or format filesystem");
|
||||||
}
|
} else if (ret == ESP_ERR_INVALID_STATE) {
|
||||||
else if (ret == ESP_ERR_INVALID_STATE)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "ESP_ERR_INVALID_STATE");
|
ESP_LOGE(TAG, "ESP_ERR_INVALID_STATE");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Failed to initialize LittleFS: %s", esp_err_to_name(ret));
|
ESP_LOGE(TAG, "Failed to initialize LittleFS: %s", esp_err_to_name(ret));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -37,20 +30,14 @@ esp_err_t storage_init(void)
|
||||||
|
|
||||||
size_t total = 0, used = 0;
|
size_t total = 0, used = 0;
|
||||||
ret = esp_littlefs_info(conf.partition_label, &total, &used);
|
ret = esp_littlefs_info(conf.partition_label, &total, &used);
|
||||||
if (ret == ESP_OK)
|
if (ret == ESP_OK) {
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "LittleFS mounted at %s. Total: %d bytes, Used: %d bytes",
|
ESP_LOGI(TAG, "LittleFS mounted at %s. Total: %d bytes, Used: %d bytes",
|
||||||
LITTLEFS_MOUNT_POINT, total, used);
|
LITTLEFS_MOUNT_POINT, total, used);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Failed to get LittleFS information");
|
ESP_LOGE(TAG, "Failed to get LittleFS information");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *storage_get_mount_point(void)
|
const char *storage_get_mount_point(void) { return LITTLEFS_MOUNT_POINT; }
|
||||||
{
|
|
||||||
return LITTLEFS_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @file web_server.h
|
* @file web_server.h
|
||||||
* @brief Simple HTTP web server component for ESP32 with async FreeRTOS support.
|
* @brief Simple HTTP web server component for ESP32 with async FreeRTOS
|
||||||
|
* support.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
@ -8,50 +9,50 @@
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Web server configuration structure.
|
* @brief Web server configuration structure.
|
||||||
*/
|
*/
|
||||||
typedef struct
|
typedef struct {
|
||||||
{
|
uint16_t port; ///< HTTP server port (default: 80)
|
||||||
uint16_t port; ///< HTTP server port (default: 80)
|
size_t max_uri_handlers; ///< Maximum number of URI handlers
|
||||||
size_t max_uri_handlers; ///< Maximum number of URI handlers
|
size_t stack_size; ///< FreeRTOS task stack size
|
||||||
size_t stack_size; ///< FreeRTOS task stack size
|
UBaseType_t task_priority; ///< FreeRTOS task priority
|
||||||
UBaseType_t task_priority; ///< FreeRTOS task priority
|
} webserver_config_t;
|
||||||
} webserver_config_t;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize and start the HTTP web server.
|
* @brief Initialize and start the HTTP web server.
|
||||||
*
|
*
|
||||||
* This function creates a FreeRTOS task that manages the HTTP 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.
|
* 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.
|
* @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);
|
*/
|
||||||
|
httpd_handle_t webserver_start(const webserver_config_t *config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stop the web server and cleanup resources.
|
* @brief Stop the web server and cleanup resources.
|
||||||
*
|
*
|
||||||
* @param server HTTP server handle returned by webserver_start().
|
* @param server HTTP server handle returned by webserver_start().
|
||||||
* Safe to pass NULL.
|
* Safe to pass NULL.
|
||||||
*/
|
*/
|
||||||
void webserver_stop(httpd_handle_t server);
|
void webserver_stop(httpd_handle_t server);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a custom URI handler.
|
* @brief Register a custom URI handler.
|
||||||
*
|
*
|
||||||
* This allows dynamic registration of API endpoints and other custom handlers.
|
* This allows dynamic registration of API endpoints and other custom handlers.
|
||||||
*
|
*
|
||||||
* @param server HTTP server handle.
|
* @param server HTTP server handle.
|
||||||
* @param uri_handler Pointer to httpd_uri_t structure.
|
* @param uri_handler Pointer to httpd_uri_t structure.
|
||||||
* @return ESP_OK on success, error code otherwise.
|
* @return ESP_OK on success, error code otherwise.
|
||||||
*/
|
*/
|
||||||
esp_err_t webserver_register_handler(httpd_handle_t server, const httpd_uri_t *uri_handler);
|
esp_err_t webserver_register_handler(httpd_handle_t server,
|
||||||
|
const httpd_uri_t *uri_handler);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel, uint8_t max_connections);
|
esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel,
|
||||||
void wifi_stop_ap(void);
|
uint8_t max_connections);
|
||||||
|
void wifi_stop_ap(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,240 +25,219 @@ static TaskHandle_t s_server_task_handle = NULL;
|
||||||
/**
|
/**
|
||||||
* @brief Get MIME type based on file extension
|
* @brief Get MIME type based on file extension
|
||||||
*/
|
*/
|
||||||
static const char *get_mime_type(const char *filename)
|
static const char *get_mime_type(const char *filename) {
|
||||||
{
|
const char *dot = strrchr(filename, '.');
|
||||||
const char *dot = strrchr(filename, '.');
|
if (!dot)
|
||||||
if (!dot)
|
|
||||||
return "application/octet-stream";
|
|
||||||
|
|
||||||
if (strcmp(dot, ".html") == 0)
|
|
||||||
return "text/html";
|
|
||||||
if (strcmp(dot, ".css") == 0)
|
|
||||||
return "text/css";
|
|
||||||
if (strcmp(dot, ".js") == 0)
|
|
||||||
return "application/javascript";
|
|
||||||
if (strcmp(dot, ".json") == 0)
|
|
||||||
return "application/json";
|
|
||||||
if (strcmp(dot, ".png") == 0)
|
|
||||||
return "image/png";
|
|
||||||
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
|
|
||||||
return "image/jpeg";
|
|
||||||
if (strcmp(dot, ".gif") == 0)
|
|
||||||
return "image/gif";
|
|
||||||
if (strcmp(dot, ".svg") == 0)
|
|
||||||
return "image/svg+xml";
|
|
||||||
if (strcmp(dot, ".ico") == 0)
|
|
||||||
return "image/x-icon";
|
|
||||||
if (strcmp(dot, ".txt") == 0)
|
|
||||||
return "text/plain";
|
|
||||||
if (strcmp(dot, ".xml") == 0)
|
|
||||||
return "application/xml";
|
|
||||||
if (strcmp(dot, ".wav") == 0)
|
|
||||||
return "audio/wav";
|
|
||||||
if (strcmp(dot, ".mp3") == 0)
|
|
||||||
return "audio/mpeg";
|
|
||||||
|
|
||||||
return "application/octet-stream";
|
return "application/octet-stream";
|
||||||
|
|
||||||
|
if (strcmp(dot, ".html") == 0)
|
||||||
|
return "text/html";
|
||||||
|
if (strcmp(dot, ".css") == 0)
|
||||||
|
return "text/css";
|
||||||
|
if (strcmp(dot, ".js") == 0)
|
||||||
|
return "application/javascript";
|
||||||
|
if (strcmp(dot, ".json") == 0)
|
||||||
|
return "application/json";
|
||||||
|
if (strcmp(dot, ".png") == 0)
|
||||||
|
return "image/png";
|
||||||
|
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
|
||||||
|
return "image/jpeg";
|
||||||
|
if (strcmp(dot, ".gif") == 0)
|
||||||
|
return "image/gif";
|
||||||
|
if (strcmp(dot, ".svg") == 0)
|
||||||
|
return "image/svg+xml";
|
||||||
|
if (strcmp(dot, ".ico") == 0)
|
||||||
|
return "image/x-icon";
|
||||||
|
if (strcmp(dot, ".txt") == 0)
|
||||||
|
return "text/plain";
|
||||||
|
if (strcmp(dot, ".xml") == 0)
|
||||||
|
return "application/xml";
|
||||||
|
if (strcmp(dot, ".wav") == 0)
|
||||||
|
return "audio/wav";
|
||||||
|
if (strcmp(dot, ".mp3") == 0)
|
||||||
|
return "audio/mpeg";
|
||||||
|
|
||||||
|
return "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief HTTP handler for static files from LittleFS
|
* @brief HTTP handler for static files from LittleFS
|
||||||
*/
|
*/
|
||||||
static esp_err_t static_file_handler(httpd_req_t *req)
|
static esp_err_t static_file_handler(httpd_req_t *req) {
|
||||||
{
|
// Build the file path
|
||||||
// Build the file path
|
char filepath[1024];
|
||||||
char filepath[1024];
|
snprintf(filepath, sizeof(filepath), "%s%s", storage_get_mount_point(),
|
||||||
snprintf(filepath, sizeof(filepath), "%s%s", storage_get_mount_point(), req->uri);
|
req->uri);
|
||||||
|
|
||||||
// Handle root path
|
// Handle root path
|
||||||
if (strcmp(req->uri, "/") == 0)
|
if (strcmp(req->uri, "/") == 0) {
|
||||||
{
|
snprintf(filepath, sizeof(filepath), "%s/index.html",
|
||||||
snprintf(filepath, sizeof(filepath), "%s/index.html", storage_get_mount_point());
|
storage_get_mount_point());
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *f = fopen(filepath, "r");
|
FILE *f = fopen(filepath, "r");
|
||||||
if (!f)
|
if (!f) {
|
||||||
{
|
ESP_LOGW(TAG, "File not found: %s", filepath);
|
||||||
ESP_LOGW(TAG, "File not found: %s", filepath);
|
httpd_resp_send_404(req);
|
||||||
httpd_resp_send_404(req);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get MIME type and set content type
|
|
||||||
const char *mime_type = get_mime_type(filepath);
|
|
||||||
httpd_resp_set_type(req, mime_type);
|
|
||||||
|
|
||||||
// Send file in chunks
|
|
||||||
char buf[1024];
|
|
||||||
size_t read_len;
|
|
||||||
while ((read_len = fread(buf, 1, sizeof(buf), f)) > 0)
|
|
||||||
{
|
|
||||||
if (httpd_resp_send_chunk(req, buf, read_len) != ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGW(TAG, "Failed to send data chunk for %s", filepath);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(f);
|
|
||||||
httpd_resp_send_chunk(req, NULL, 0); // Send end marker
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get MIME type and set content type
|
||||||
|
const char *mime_type = get_mime_type(filepath);
|
||||||
|
httpd_resp_set_type(req, mime_type);
|
||||||
|
|
||||||
|
// Send file in chunks
|
||||||
|
char buf[1024];
|
||||||
|
size_t read_len;
|
||||||
|
while ((read_len = fread(buf, 1, sizeof(buf), f)) > 0) {
|
||||||
|
if (httpd_resp_send_chunk(req, buf, read_len) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Failed to send data chunk for %s", filepath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0); // Send end marker
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief HTTP handler for API health check (GET /api/health)
|
* @brief HTTP handler for API health check (GET /api/health)
|
||||||
*/
|
*/
|
||||||
static esp_err_t health_check_handler(httpd_req_t *req)
|
static esp_err_t health_check_handler(httpd_req_t *req) {
|
||||||
{
|
httpd_resp_set_type(req, "application/json");
|
||||||
httpd_resp_set_type(req, "application/json");
|
httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
|
||||||
httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
|
return ESP_OK;
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief FreeRTOS task function for the HTTP server.
|
* @brief FreeRTOS task function for the HTTP server.
|
||||||
* Allows non-blocking server operation and future extensibility.
|
* Allows non-blocking server operation and future extensibility.
|
||||||
*/
|
*/
|
||||||
static void webserver_task(void *arg)
|
static void webserver_task(void *arg) {
|
||||||
{
|
(void)arg; // Unused parameter
|
||||||
(void)arg; // Unused parameter
|
ESP_LOGI(TAG, "Web server task started");
|
||||||
ESP_LOGI(TAG, "Web server task started");
|
|
||||||
|
|
||||||
// Keep task alive - the server runs in the background
|
// Keep task alive - the server runs in the background
|
||||||
while (s_server_handle != NULL)
|
while (s_server_handle != NULL) {
|
||||||
{
|
vTaskDelay(pdMS_TO_TICKS(10000)); // 10 second check interval
|
||||||
vTaskDelay(pdMS_TO_TICKS(10000)); // 10 second check interval
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Web server task ending");
|
ESP_LOGI(TAG, "Web server task ending");
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_handle_t webserver_start(const webserver_config_t *config)
|
httpd_handle_t webserver_start(const webserver_config_t *config) {
|
||||||
{
|
if (s_server_handle != NULL) {
|
||||||
if (s_server_handle != NULL)
|
ESP_LOGW(TAG, "Web server already running");
|
||||||
{
|
|
||||||
ESP_LOGW(TAG, "Web server already running");
|
|
||||||
return s_server_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize LittleFS
|
|
||||||
esp_err_t ret = storage_init();
|
|
||||||
if (ret != ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Failed to initialize storage");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Wildcard handler for static files from LittleFS (must be last)
|
|
||||||
httpd_uri_t file_uri = {
|
|
||||||
.uri = "/*",
|
|
||||||
.method = HTTP_GET,
|
|
||||||
.handler = static_file_handler,
|
|
||||||
.user_ctx = NULL,
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(s_server_handle, &file_uri);
|
|
||||||
|
|
||||||
// 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;
|
return s_server_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void webserver_stop(httpd_handle_t server)
|
// Initialize LittleFS
|
||||||
{
|
esp_err_t ret = storage_init();
|
||||||
if (server == NULL)
|
if (ret != ESP_OK) {
|
||||||
{
|
ESP_LOGE(TAG, "Failed to initialize storage");
|
||||||
return;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_stop(server);
|
// 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
|
||||||
|
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;
|
s_server_handle = NULL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for task to finish
|
ESP_LOGI(TAG, "HTTP server started on port %d", port);
|
||||||
if (s_server_task_handle != NULL)
|
|
||||||
{
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
s_server_task_handle = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Web server stopped");
|
// 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);
|
||||||
|
|
||||||
|
// Wildcard handler for static files from LittleFS (must be last)
|
||||||
|
httpd_uri_t file_uri = {
|
||||||
|
.uri = "/*",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = static_file_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(s_server_handle, &file_uri);
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t webserver_register_handler(httpd_handle_t server, const httpd_uri_t *uri_handler)
|
void webserver_stop(httpd_handle_t server) {
|
||||||
{
|
if (server == NULL) {
|
||||||
if (server == NULL || uri_handler == NULL)
|
return;
|
||||||
{
|
}
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t ret = httpd_register_uri_handler(server, uri_handler);
|
httpd_stop(server);
|
||||||
if (ret == ESP_OK)
|
s_server_handle = NULL;
|
||||||
{
|
|
||||||
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;
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,32 +11,28 @@
|
||||||
static const char *TAG = "WIFI";
|
static const char *TAG = "WIFI";
|
||||||
static bool s_wifi_started = false;
|
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)
|
esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel,
|
||||||
{
|
uint8_t max_connections) {
|
||||||
if (s_wifi_started)
|
if (s_wifi_started) {
|
||||||
{
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ssid || strlen(ssid) == 0 || strlen(ssid) > 32)
|
if (!ssid || strlen(ssid) == 0 || strlen(ssid) > 32) {
|
||||||
{
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool has_password = password && strlen(password) > 0;
|
const bool has_password = password && strlen(password) > 0;
|
||||||
if (has_password && strlen(password) < 8)
|
if (has_password && strlen(password) < 8) {
|
||||||
{
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t err = nvs_flash_init();
|
esp_err_t err = nvs_flash_init();
|
||||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||||
{
|
err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||||
err = nvs_flash_init();
|
err = nvs_flash_init();
|
||||||
}
|
}
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK) {
|
||||||
{
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,22 +44,24 @@ esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel,
|
||||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
|
||||||
wifi_config_t wifi_config = {
|
wifi_config_t wifi_config = {
|
||||||
.ap = {
|
.ap =
|
||||||
.channel = channel,
|
{
|
||||||
.max_connection = max_connections,
|
.channel = channel,
|
||||||
.authmode = has_password ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN,
|
.max_connection = max_connections,
|
||||||
.pmf_cfg = {
|
.authmode = has_password ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN,
|
||||||
.required = false,
|
.pmf_cfg =
|
||||||
|
{
|
||||||
|
.required = false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
strlcpy((char *)wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
|
strlcpy((char *)wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
|
||||||
wifi_config.ap.ssid_len = strlen(ssid);
|
wifi_config.ap.ssid_len = strlen(ssid);
|
||||||
|
|
||||||
if (has_password)
|
if (has_password) {
|
||||||
{
|
strlcpy((char *)wifi_config.ap.password, password,
|
||||||
strlcpy((char *)wifi_config.ap.password, password, sizeof(wifi_config.ap.password));
|
sizeof(wifi_config.ap.password));
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||||
|
|
@ -75,10 +73,8 @@ esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel,
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wifi_stop_ap(void)
|
void wifi_stop_ap(void) {
|
||||||
{
|
if (!s_wifi_started) {
|
||||||
if (!s_wifi_started)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
607
data/index.html
607
data/index.html
|
|
@ -1,328 +1,313 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Konfiguration</title>
|
<title>Konfiguration</title>
|
||||||
<link rel="stylesheet" href="/style.css" />
|
<link rel="stylesheet" href="/style.css" />
|
||||||
<script type="module" src="/input-visibility.js" defer></script>
|
<script type="module" src="/input-visibility.js" defer></script>
|
||||||
<script type="module" src="/loading-screen.js" defer></script>
|
<script type="module" src="/loading-screen.js" defer></script>
|
||||||
<script type="module" src="/load-data.js" defer></script>
|
<script type="module" src="/load-data.js" defer></script>
|
||||||
<script type="module" src="/networks.js" defer></script>
|
<script type="module" src="/networks.js" defer></script>
|
||||||
<script type="module" src="/submit.js" defer></script>
|
<script type="module" src="/submit.js" defer></script>
|
||||||
<script type="module" src="/reset.js" defer></script>
|
<script type="module" src="/reset.js" defer></script>
|
||||||
<script type="module" src="/range-input.js" defer></script>
|
<script type="module" src="/range-input.js" defer></script>
|
||||||
<script type="module" src="/status.js" defer></script>
|
<script type="module" src="/status.js" defer></script>
|
||||||
<script type="module" src="/websocket.js" defer></script>
|
<script type="module" src="/websocket.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<section class="loading-screen">
|
<section class="loading-screen">
|
||||||
<div class="spinner-container">
|
<div class="spinner-container">
|
||||||
<!-- h2 is filled dynamically -->
|
<!-- h2 is filled dynamically -->
|
||||||
<h2></h2>
|
<h2></h2>
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<button
|
<button
|
||||||
class="reload"
|
class="reload"
|
||||||
type="button"
|
type="button"
|
||||||
onclick="window.location.reload()"
|
onclick="window.location.reload()"
|
||||||
>
|
>
|
||||||
Seite neu laden
|
Seite neu laden
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="content hidden">
|
<section class="content hidden">
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<!-- placeholder for wifi icon -->
|
<!-- placeholder for wifi icon -->
|
||||||
<img class="connection-icon" src="" alt="" />
|
<img class="connection-icon" src="" alt="" />
|
||||||
<span>Temp.: <span class="cpu-temp"></span> °C</span>
|
<span>Temp.: <span class="cpu-temp"></span> °C</span>
|
||||||
<span>Heap: <span class="heap-percentage"></span> %</span>
|
<span>Heap: <span class="heap-percentage"></span> %</span>
|
||||||
<span>PSRAM: <span class="psram-percentage"></span> %</span>
|
<span>PSRAM: <span class="psram-percentage"></span> %</span>
|
||||||
<span>Uptime: <span class="uptime"></span></span>
|
<span>Uptime: <span class="uptime"></span></span>
|
||||||
|
|
||||||
<button type="button" class="expand-status icon-button">
|
<button type="button" class="expand-status icon-button">
|
||||||
<img src="/icons/open.svg" alt="Mehr" />
|
<img src="/icons/open.svg" alt="Mehr" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dialog class="dialog-status">
|
<dialog class="dialog-status">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<h2 class="model"></h2>
|
<h2 class="model"></h2>
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
<button type="submit" class="icon-button">
|
<button type="submit" class="icon-button">
|
||||||
<img src="/icons/close.svg" alt="Schließen" />
|
<img src="/icons/close.svg" alt="Schließen" />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-status-content">
|
<div class="dialog-status-content">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>Signalstärke</span>
|
<span>Signalstärke</span>
|
||||||
<span class="centered-vertical">
|
<span class="centered-vertical">
|
||||||
<img
|
<img class="connection-icon small" src="" alt="" />
|
||||||
class="connection-icon small"
|
<span><span class="rssi"></span> dBm</span>
|
||||||
src=""
|
</span>
|
||||||
alt=""
|
</div>
|
||||||
/>
|
|
||||||
<span><span class="rssi"></span> dBm</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>Uptime</span>
|
<span>Uptime</span>
|
||||||
<span class="uptime"></span>
|
<span class="uptime"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>CPU-Temperatur</span>
|
<span>CPU-Temperatur</span>
|
||||||
<span><span class="cpu-temp"></span> °C</span>
|
<span><span class="cpu-temp"></span> °C</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>CPU Cycle Count</span>
|
<span>CPU Cycle Count</span>
|
||||||
<span class="cpu-cycle-count"></span>
|
<span class="cpu-cycle-count"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>Heap</span>
|
<span>Heap</span>
|
||||||
<span><span class="heap-percentage"></span> %</span>
|
<span><span class="heap-percentage"></span> %</span>
|
||||||
<span
|
<span
|
||||||
><span class="heap-used"></span> /
|
><span class="heap-used"></span> /
|
||||||
<span class="heap-total"></span
|
<span class="heap-total"></span
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>PSRAM</span>
|
<span>PSRAM</span>
|
||||||
<span
|
<span><span class="psram-percentage"></span> %</span>
|
||||||
><span class="psram-percentage"></span> %</span
|
<span
|
||||||
>
|
><span class="psram-used"></span> /
|
||||||
<span
|
<span class="psram-total"></span
|
||||||
><span class="psram-used"></span> /
|
></span>
|
||||||
<span class="psram-total"></span
|
</div>
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>CPU-Taktfrequenz</span>
|
<span>CPU-Taktfrequenz</span>
|
||||||
<span><span class="cpu-freq"></span> MHz</span>
|
<span><span class="cpu-freq"></span> MHz</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>MAC-Adresse</span>
|
<span>MAC-Adresse</span>
|
||||||
<span class="mac"></span>
|
<span class="mac"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>Script-Hash</span>
|
<span>Script-Hash</span>
|
||||||
<span class="hash"></span>
|
<span class="hash"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span>SDK-Version</span>
|
<span>SDK-Version</span>
|
||||||
<span class="sdk-version"></span>
|
<span class="sdk-version"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<form class="config">
|
<form class="config">
|
||||||
<h1>Konfiguration</h1>
|
<h1>Konfiguration</h1>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Verbindung</legend>
|
<legend>Verbindung</legend>
|
||||||
<label>
|
<label>
|
||||||
<span>IP-Zuweisung:</span>
|
<span>IP-Zuweisung:</span>
|
||||||
<select
|
<select
|
||||||
name="ip-method"
|
name="ip-method"
|
||||||
id="input-ip-method"
|
id="input-ip-method"
|
||||||
title="IP-"
|
title="IP-"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option value="0">Statisch</option>
|
<option value="0">Statisch</option>
|
||||||
<option value="1">DHCP</option>
|
<option value="1">DHCP</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<div data-field="input-ip-method" data-values="0">
|
<div data-field="input-ip-method" data-values="0">
|
||||||
<label>
|
<label>
|
||||||
<span>IP-Adresse:</span>
|
<span>IP-Adresse:</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="ip"
|
name="ip"
|
||||||
id="input-ip"
|
id="input-ip"
|
||||||
placeholder="IP-Adresse"
|
placeholder="IP-Adresse"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Subnetzmaske:</span>
|
<span>Subnetzmaske:</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="subnet"
|
name="subnet"
|
||||||
id="input-subnet"
|
id="input-subnet"
|
||||||
placeholder="Subnetzmaske"
|
placeholder="Subnetzmaske"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Gateway:</span>
|
<span>Gateway:</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="gateway"
|
name="gateway"
|
||||||
id="input-gateway"
|
id="input-gateway"
|
||||||
placeholder="Gateway"
|
placeholder="Gateway"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<span>Verbindungsmethode:</span>
|
<span>Verbindungsmethode:</span>
|
||||||
<select
|
<select
|
||||||
name="connection"
|
name="connection"
|
||||||
id="input-connection"
|
id="input-connection"
|
||||||
title="Verbindung"
|
title="Verbindung"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option value="0">WiFi-Station</option>
|
<option value="0">WiFi-Station</option>
|
||||||
<option value="1">WiFi-AccessPoint</option>
|
<option value="1">WiFi-AccessPoint</option>
|
||||||
<option value="2">Ethernet</option>
|
<option value="2">Ethernet</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<div data-field="input-connection" data-values="1">
|
<div data-field="input-connection" data-values="1">
|
||||||
<label>
|
<label>
|
||||||
<span>SSID:</span>
|
<span>SSID:</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="ssid"
|
name="ssid"
|
||||||
id="input-ssid"
|
id="input-ssid"
|
||||||
placeholder="SSID"
|
placeholder="SSID"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div data-field="input-connection" data-values="0">
|
<div data-field="input-connection" data-values="0">
|
||||||
<label>
|
<label>
|
||||||
<span>Netzwerk:</span>
|
<span>Netzwerk:</span>
|
||||||
<select
|
<select
|
||||||
name="ssid"
|
name="ssid"
|
||||||
id="select-network"
|
id="select-network"
|
||||||
title="Netzwerk"
|
title="Netzwerk"
|
||||||
required
|
required
|
||||||
></select>
|
></select>
|
||||||
<button
|
<button type="button" id="refresh-networks" class="icon-button">
|
||||||
type="button"
|
<img src="/icons/refresh.svg" alt="Neu laden" />
|
||||||
id="refresh-networks"
|
</button>
|
||||||
class="icon-button"
|
</label>
|
||||||
>
|
</div>
|
||||||
<img
|
<div data-field="input-connection" data-values="0|1">
|
||||||
src="/icons/refresh.svg"
|
<label>
|
||||||
alt="Neu laden"
|
<span>Password:</span>
|
||||||
/>
|
<input
|
||||||
</button>
|
type="password"
|
||||||
</label>
|
name="password"
|
||||||
</div>
|
id="input-password"
|
||||||
<div data-field="input-connection" data-values="0|1">
|
placeholder="Passwort"
|
||||||
<label>
|
/>
|
||||||
<span>Password:</span>
|
</label>
|
||||||
<input
|
</div>
|
||||||
type="password"
|
</fieldset>
|
||||||
name="password"
|
<fieldset>
|
||||||
id="input-password"
|
<legend>Input/Output 1</legend>
|
||||||
placeholder="Passwort"
|
<label class="switch">
|
||||||
/>
|
<span>Output</span>
|
||||||
</label>
|
<input
|
||||||
</div>
|
type="checkbox"
|
||||||
</fieldset>
|
name="direction-1"
|
||||||
<fieldset>
|
id="input-direction-1"
|
||||||
<legend>Input/Output 1</legend>
|
data-value-not-checked="0"
|
||||||
<label class="switch">
|
data-value-checked="1"
|
||||||
<span>Output</span>
|
/>
|
||||||
<input
|
<span class="slider"></span>
|
||||||
type="checkbox"
|
<span>Input</span>
|
||||||
name="direction-1"
|
</label>
|
||||||
id="input-direction-1"
|
<label>
|
||||||
data-value-not-checked="0"
|
ArtNet-Universe:
|
||||||
data-value-checked="1"
|
<input
|
||||||
/>
|
type="number"
|
||||||
<span class="slider"></span>
|
name="universe-1"
|
||||||
<span>Input</span>
|
id="universe-1"
|
||||||
</label>
|
placeholder="Universe"
|
||||||
<label>
|
min="0"
|
||||||
ArtNet-Universe:
|
max="15"
|
||||||
<input
|
/>
|
||||||
type="number"
|
</label>
|
||||||
name="universe-1"
|
</fieldset>
|
||||||
id="universe-1"
|
<fieldset>
|
||||||
placeholder="Universe"
|
<legend>Input/Output 2</legend>
|
||||||
min="0"
|
<label class="switch">
|
||||||
max="15"
|
<span>Output</span>
|
||||||
/>
|
<input
|
||||||
</label>
|
type="checkbox"
|
||||||
</fieldset>
|
name="direction-2"
|
||||||
<fieldset>
|
id="input-direction-2"
|
||||||
<legend>Input/Output 2</legend>
|
data-value-not-checked="0"
|
||||||
<label class="switch">
|
data-value-checked="1"
|
||||||
<span>Output</span>
|
/>
|
||||||
<input
|
<span class="slider"></span>
|
||||||
type="checkbox"
|
<span>Input</span>
|
||||||
name="direction-2"
|
</label>
|
||||||
id="input-direction-2"
|
<label>
|
||||||
data-value-not-checked="0"
|
ArtNet-Universe:
|
||||||
data-value-checked="1"
|
<input
|
||||||
/>
|
type="number"
|
||||||
<span class="slider"></span>
|
name="universe-2"
|
||||||
<span>Input</span>
|
id="universe-2"
|
||||||
</label>
|
placeholder="Universe"
|
||||||
<label>
|
min="0"
|
||||||
ArtNet-Universe:
|
max="15"
|
||||||
<input
|
/>
|
||||||
type="number"
|
</label>
|
||||||
name="universe-2"
|
</fieldset>
|
||||||
id="universe-2"
|
<fieldset>
|
||||||
placeholder="Universe"
|
<legend>Sonstiges</legend>
|
||||||
min="0"
|
<label>
|
||||||
max="15"
|
LED-Helligkeit
|
||||||
/>
|
<div>
|
||||||
</label>
|
<input
|
||||||
</fieldset>
|
type="range"
|
||||||
<fieldset>
|
name="led-brightness"
|
||||||
<legend>Sonstiges</legend>
|
id="led-brightness"
|
||||||
<label>
|
min="0"
|
||||||
LED-Helligkeit
|
max="255"
|
||||||
<div>
|
class="range"
|
||||||
<input
|
/>
|
||||||
type="range"
|
<span class="range-value"></span>
|
||||||
name="led-brightness"
|
</div>
|
||||||
id="led-brightness"
|
</label>
|
||||||
min="0"
|
<label>
|
||||||
max="255"
|
<span>Aktion bei Knopfdruck:</span>
|
||||||
class="range"
|
<select
|
||||||
/>
|
name="button-action"
|
||||||
<span class="range-value"></span>
|
id="input-button-action"
|
||||||
</div>
|
title="Aktion bei Knopfdruck"
|
||||||
</label>
|
required
|
||||||
<label>
|
>
|
||||||
<span>Aktion bei Knopfdruck:</span>
|
<option value="0">Nichts</option>
|
||||||
<select
|
<option value="1">Konfiguration zurücksetzen</option>
|
||||||
name="button-action"
|
<option value="2">Neustart</option>
|
||||||
id="input-button-action"
|
</select>
|
||||||
title="Aktion bei Knopfdruck"
|
</label>
|
||||||
required
|
</fieldset>
|
||||||
>
|
|
||||||
<option value="0">Nichts</option>
|
|
||||||
<option value="1">
|
|
||||||
Konfiguration zurücksetzen
|
|
||||||
</option>
|
|
||||||
<option value="2">Neustart</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="reset">Zurücksetzen</button>
|
<button type="reset">Zurücksetzen</button>
|
||||||
<button type="submit">Speichern</button>
|
<button type="submit">Speichern</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,20 @@ const dynamicInputs = form.querySelectorAll("[data-field][data-values]");
|
||||||
document.addEventListener("change", updateVisibility);
|
document.addEventListener("change", updateVisibility);
|
||||||
|
|
||||||
function updateVisibility() {
|
function updateVisibility() {
|
||||||
dynamicInputs.forEach((element) => {
|
dynamicInputs.forEach(element => {
|
||||||
const input = form.querySelector(`#${element.dataset.field}`);
|
const input = form.querySelector(`#${element.dataset.field}`);
|
||||||
if (element.dataset.values.split("|").includes(input.value)) {
|
if (element.dataset.values.split("|").includes(input.value)) {
|
||||||
element.classList.remove("hidden");
|
element.classList.remove("hidden");
|
||||||
element
|
element
|
||||||
.querySelectorAll("input, select, button, textarea")
|
.querySelectorAll("input, select, button, textarea")
|
||||||
.forEach((childInput) => (childInput.disabled = false));
|
.forEach(childInput => (childInput.disabled = false));
|
||||||
} else {
|
} else {
|
||||||
element.classList.add("hidden");
|
element.classList.add("hidden");
|
||||||
element
|
element
|
||||||
.querySelectorAll("input, select, button, textarea")
|
.querySelectorAll("input, select, button, textarea")
|
||||||
.forEach((childInput) => (childInput.disabled = true));
|
.forEach(childInput => (childInput.disabled = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVisibility();
|
updateVisibility();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
showLoadingScreen,
|
showLoadingScreen,
|
||||||
showError,
|
showError,
|
||||||
hideLoadingScreen,
|
hideLoadingScreen,
|
||||||
} from "./loading-screen.js";
|
} from "./loading-screen.js";
|
||||||
|
|
||||||
const form = document.querySelector("form.config");
|
const form = document.querySelector("form.config");
|
||||||
|
|
@ -9,46 +9,46 @@ const form = document.querySelector("form.config");
|
||||||
export let data = {};
|
export let data = {};
|
||||||
|
|
||||||
export async function loadData(timeout = null) {
|
export async function loadData(timeout = null) {
|
||||||
const req = await fetch("/config", {
|
const req = await fetch("/config", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
signal: timeout !== null ? AbortSignal.timeout(timeout) : undefined,
|
signal: timeout !== null ? AbortSignal.timeout(timeout) : undefined,
|
||||||
});
|
});
|
||||||
if (!req.ok) {
|
if (!req.ok) {
|
||||||
throw new Error(`Response status: ${req.status}`);
|
throw new Error(`Response status: ${req.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = await req.json();
|
const json = await req.json();
|
||||||
console.log(json);
|
console.log(json);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeDataToInput(data) {
|
export function writeDataToInput(data) {
|
||||||
console.log("write data");
|
console.log("write data");
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
const element = document.querySelector(`[name=${key}]`);
|
const element = document.querySelector(`[name=${key}]`);
|
||||||
console.log(key, element);
|
console.log(key, element);
|
||||||
|
|
||||||
if (element.type === "checkbox") {
|
if (element.type === "checkbox") {
|
||||||
element.checked = value;
|
element.checked = value;
|
||||||
} else {
|
} else {
|
||||||
element.value = value;
|
element.value = value;
|
||||||
}
|
|
||||||
|
|
||||||
if (element.type === "range") {
|
|
||||||
// update text next to the slider by sending an event
|
|
||||||
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// send "change" event
|
|
||||||
form.dispatchEvent(new Event("change", { bubbles: true }));
|
if (element.type === "range") {
|
||||||
|
// update text next to the slider by sending an event
|
||||||
|
element.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// send "change" event
|
||||||
|
form.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
showLoadingScreen("Konfiguration wird geladen...");
|
showLoadingScreen("Konfiguration wird geladen...");
|
||||||
try {
|
try {
|
||||||
data = await loadData();
|
data = await loadData();
|
||||||
hideLoadingScreen();
|
hideLoadingScreen();
|
||||||
writeDataToInput(data);
|
writeDataToInput(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error.message);
|
console.log(error.message);
|
||||||
showError("Die Konfiguration konnte nicht geladen werden.");
|
showError("Die Konfiguration konnte nicht geladen werden.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,36 +5,36 @@ const spinner = loadingScreen.querySelector(".spinner");
|
||||||
const reloadBtn = loadingScreen.querySelector(".reload");
|
const reloadBtn = loadingScreen.querySelector(".reload");
|
||||||
|
|
||||||
export function showLoadingScreen(msg) {
|
export function showLoadingScreen(msg) {
|
||||||
hide(content, reloadBtn);
|
hide(content, reloadBtn);
|
||||||
show(loadingScreen, spinner);
|
show(loadingScreen, spinner);
|
||||||
loadingMsg.classList.remove("error");
|
loadingMsg.classList.remove("error");
|
||||||
loadingMsg.textContent = msg;
|
loadingMsg.textContent = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showError(msg) {
|
export function showError(msg) {
|
||||||
showLoadingScreen(msg);
|
showLoadingScreen(msg);
|
||||||
loadingMsg.innerHTML +=
|
loadingMsg.innerHTML +=
|
||||||
"<br/>Stelle sicher, dass du mit dem DMX-Interface verbunden bist und die IP-Adresse stimmt.";
|
"<br/>Stelle sicher, dass du mit dem DMX-Interface verbunden bist und die IP-Adresse stimmt.";
|
||||||
show(reloadBtn);
|
show(reloadBtn);
|
||||||
hide(spinner);
|
hide(spinner);
|
||||||
loadingMsg.classList.add("error");
|
loadingMsg.classList.add("error");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hideLoadingScreen() {
|
export function hideLoadingScreen() {
|
||||||
hide(loadingScreen, reloadBtn);
|
hide(loadingScreen, reloadBtn);
|
||||||
show(content);
|
show(content);
|
||||||
loadingMsg.classList.remove("error");
|
loadingMsg.classList.remove("error");
|
||||||
loadingMsg.textContent = "";
|
loadingMsg.textContent = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function show(...elements) {
|
function show(...elements) {
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
element.classList.remove("hidden");
|
element.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide(...elements) {
|
function hide(...elements) {
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
element.classList.add("hidden");
|
element.classList.add("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,67 +7,67 @@ const refreshIcon = refreshButton.querySelector("img");
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
refreshButton.addEventListener("click", async () => {
|
refreshButton.addEventListener("click", async () => {
|
||||||
// check if interface is in WiFi-AccessPoint mode
|
// check if interface is in WiFi-AccessPoint mode
|
||||||
if (data.connection == 1) {
|
if (data.connection == 1) {
|
||||||
alert(
|
alert(
|
||||||
"Beim WLAN-Scan wird die Verbindung hardwarebedingt kurzzeitig" +
|
"Beim WLAN-Scan wird die Verbindung hardwarebedingt kurzzeitig" +
|
||||||
"unterbrochen.\n" +
|
"unterbrochen.\n" +
|
||||||
"Möglicherweise muss das Interface neu verbunden werden."
|
"Möglicherweise muss das Interface neu verbunden werden."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
updateNetworks();
|
updateNetworks();
|
||||||
});
|
});
|
||||||
|
|
||||||
// check if connected via WiFi-Station
|
// check if connected via WiFi-Station
|
||||||
if (data.connection === 0) {
|
if (data.connection === 0) {
|
||||||
// show currently connected WiFi
|
// show currently connected WiFi
|
||||||
insertNetworks([data.ssid]);
|
insertNetworks([data.ssid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertNetworks(networks) {
|
function insertNetworks(networks) {
|
||||||
networkDropdown.textContent = ""; // clear dropdown
|
networkDropdown.textContent = ""; // clear dropdown
|
||||||
|
|
||||||
for (const ssid of networks) {
|
for (const ssid of networks) {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = ssid;
|
option.value = ssid;
|
||||||
option.innerText = ssid;
|
option.innerText = ssid;
|
||||||
networkDropdown.appendChild(option);
|
networkDropdown.appendChild(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadNetworks() {
|
async function loadNetworks() {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
refreshButton.classList.remove("error-bg");
|
refreshButton.classList.remove("error-bg");
|
||||||
refreshIcon.classList.add("spinning");
|
refreshIcon.classList.add("spinning");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/networks", {
|
const res = await fetch("/networks", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw Error(`response status: ${res.status}`);
|
throw Error(`response status: ${res.status}`);
|
||||||
}
|
|
||||||
|
|
||||||
const networks = await res.json();
|
|
||||||
|
|
||||||
refreshIcon.classList.remove("spinning");
|
|
||||||
isLoading = false;
|
|
||||||
// remove duplicate values
|
|
||||||
return Array.from(new Set(networks));
|
|
||||||
} catch (e) {
|
|
||||||
refreshIcon.classList.remove("spinning");
|
|
||||||
refreshButton.classList.add("error-bg");
|
|
||||||
isLoading = false;
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const networks = await res.json();
|
||||||
|
|
||||||
|
refreshIcon.classList.remove("spinning");
|
||||||
|
isLoading = false;
|
||||||
|
// remove duplicate values
|
||||||
|
return Array.from(new Set(networks));
|
||||||
|
} catch (e) {
|
||||||
|
refreshIcon.classList.remove("spinning");
|
||||||
|
refreshButton.classList.add("error-bg");
|
||||||
|
isLoading = false;
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateNetworks() {
|
async function updateNetworks() {
|
||||||
const networks = await loadNetworks();
|
const networks = await loadNetworks();
|
||||||
if (networks) {
|
if (networks) {
|
||||||
insertNetworks(["", ...networks]);
|
insertNetworks(["", ...networks]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
document.querySelector("form.config").addEventListener("input", (event) => {
|
document.querySelector("form.config").addEventListener("input", event => {
|
||||||
if (event.target.classList.contains("range")) {
|
if (event.target.classList.contains("range")) {
|
||||||
updateValue(event.target);
|
updateValue(event.target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateValue(slider) {
|
function updateValue(slider) {
|
||||||
const percentage = Math.round((slider.value / slider.max) * 100);
|
const percentage = Math.round((slider.value / slider.max) * 100);
|
||||||
slider.nextElementSibling.innerText = `${percentage}%`;
|
slider.nextElementSibling.innerText = `${percentage}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll("input[type='range'].range").forEach((element) => {
|
document.querySelectorAll("input[type='range'].range").forEach(element => {
|
||||||
updateValue(element);
|
updateValue(element);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@ import { updateConfig } from "/submit.js";
|
||||||
|
|
||||||
const form = document.querySelector("form.config");
|
const form = document.querySelector("form.config");
|
||||||
|
|
||||||
form.addEventListener("reset", async (event) => {
|
form.addEventListener("reset", async event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const ok = confirm(
|
const ok = confirm(
|
||||||
"Sicher, dass du alle Einstellungen zurücksetzen möchtest?"
|
"Sicher, dass du alle Einstellungen zurücksetzen möchtest?"
|
||||||
);
|
);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
updateConfig({
|
updateConfig({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
135
data/status.js
135
data/status.js
|
|
@ -5,108 +5,105 @@ const statusDialog = document.querySelector(".dialog-status");
|
||||||
const expandButton = document.querySelector(".expand-status");
|
const expandButton = document.querySelector(".expand-status");
|
||||||
|
|
||||||
expandButton.addEventListener("click", () => {
|
expandButton.addEventListener("click", () => {
|
||||||
statusDialog.showModal();
|
statusDialog.showModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCallback("status", setStatus);
|
registerCallback("status", setStatus);
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
|
|
||||||
function setStatus(status) {
|
function setStatus(status) {
|
||||||
setValue("model", status.chip.model);
|
setValue("model", status.chip.model);
|
||||||
setValue("mac", formatMac(status.chip.mac));
|
setValue("mac", formatMac(status.chip.mac));
|
||||||
setValue("sdk-version", status.sdkVersion);
|
setValue("sdk-version", status.sdkVersion);
|
||||||
|
|
||||||
setValue("rssi", status.connection.signalStrength);
|
setValue("rssi", status.connection.signalStrength);
|
||||||
const icon = selectConnectionIcon(status.connection.signalStrength);
|
const icon = selectConnectionIcon(status.connection.signalStrength);
|
||||||
document.querySelectorAll(".connection-icon").forEach((img) => {
|
document.querySelectorAll(".connection-icon").forEach(img => {
|
||||||
img.src = `/icons/${icon}`;
|
img.src = `/icons/${icon}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
setValue("cpu-freq", status.chip.cpuFreqMHz);
|
setValue("cpu-freq", status.chip.cpuFreqMHz);
|
||||||
setValue("cpu-cycle-count", status.chip.cycleCount);
|
setValue("cpu-cycle-count", status.chip.cycleCount);
|
||||||
setValue("cpu-temp", status.chip.tempC);
|
setValue("cpu-temp", status.chip.tempC);
|
||||||
|
|
||||||
const usedHeap = status.heap.total - status.heap.free;
|
const usedHeap = status.heap.total - status.heap.free;
|
||||||
setValue("heap-used", formatBytes(usedHeap));
|
setValue("heap-used", formatBytes(usedHeap));
|
||||||
setValue("heap-total", formatBytes(status.heap.total));
|
setValue("heap-total", formatBytes(status.heap.total));
|
||||||
setValue(
|
setValue("heap-percentage", Math.round((usedHeap / status.heap.total) * 100));
|
||||||
"heap-percentage",
|
|
||||||
Math.round((usedHeap / status.heap.total) * 100)
|
|
||||||
);
|
|
||||||
|
|
||||||
const usedPsram = status.psram.total - status.psram.free;
|
const usedPsram = status.psram.total - status.psram.free;
|
||||||
setValue("psram-used", formatBytes(usedPsram));
|
setValue("psram-used", formatBytes(usedPsram));
|
||||||
setValue("psram-total", formatBytes(status.psram.total));
|
setValue("psram-total", formatBytes(status.psram.total));
|
||||||
setValue(
|
setValue(
|
||||||
"psram-percentage",
|
"psram-percentage",
|
||||||
Math.round((usedPsram / status.psram.total) * 100)
|
Math.round((usedPsram / status.psram.total) * 100)
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("uptime", parseDuration(status.uptime));
|
setValue("uptime", parseDuration(status.uptime));
|
||||||
|
|
||||||
setValue("hash", parseHash(status.sketch.md5));
|
setValue("hash", parseHash(status.sketch.md5));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setValue(className, value) {
|
function setValue(className, value) {
|
||||||
document.querySelectorAll("." + className).forEach((element) => {
|
document.querySelectorAll("." + className).forEach(element => {
|
||||||
element.innerText = value;
|
element.innerText = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDuration(ms) {
|
function parseDuration(ms) {
|
||||||
const date = new Date(ms);
|
const date = new Date(ms);
|
||||||
const time =
|
const time =
|
||||||
date.getUTCHours().toString().padStart(2, "0") +
|
date.getUTCHours().toString().padStart(2, "0") +
|
||||||
":" +
|
":" +
|
||||||
date.getUTCMinutes().toString().padStart(2, "0") +
|
date.getUTCMinutes().toString().padStart(2, "0") +
|
||||||
" h";
|
" h";
|
||||||
const days = Math.floor(date.getTime() / (1000 * 60 * 60 * 24));
|
const days = Math.floor(date.getTime() / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
return days !== 0 ? `${days} Tage, ${time}` : time;
|
return days !== 0 ? `${days} Tage, ${time}` : time;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseHash(hash) {
|
function parseHash(hash) {
|
||||||
return hash.toUpperCase().substring(0, 16);
|
return hash.toUpperCase().substring(0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
const units = ["Bytes", "KB", "MB", "GB"];
|
const units = ["Bytes", "KB", "MB", "GB"];
|
||||||
|
|
||||||
let value = bytes;
|
let value = bytes;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
while (value >= 1000) {
|
while (value >= 1000) {
|
||||||
value /= 1000;
|
value /= 1000;
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${Math.round(value * 10) / 10} ${units[index]}`;
|
return `${Math.round(value * 10) / 10} ${units[index]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatMac(decimalMac) {
|
function formatMac(decimalMac) {
|
||||||
const octets = decimalMac.toString(16).toUpperCase().match(/../g) || [];
|
const octets = decimalMac.toString(16).toUpperCase().match(/../g) || [];
|
||||||
return octets.reverse().join(":");
|
return octets.reverse().join(":");
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectConnectionIcon(signalStrength) {
|
function selectConnectionIcon(signalStrength) {
|
||||||
// access point
|
// access point
|
||||||
if (data.connection == 1) {
|
if (data.connection == 1) {
|
||||||
return "hotspot.svg";
|
return "hotspot.svg";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ethernet
|
// ethernet
|
||||||
if (data.connection == 2) {
|
if (data.connection == 2) {
|
||||||
return "lan.svg";
|
return "lan.svg";
|
||||||
}
|
}
|
||||||
|
|
||||||
// station
|
// station
|
||||||
if (signalStrength >= -50) {
|
if (signalStrength >= -50) {
|
||||||
return "signal4.svg";
|
return "signal4.svg";
|
||||||
}
|
}
|
||||||
if (signalStrength >= -60) {
|
if (signalStrength >= -60) {
|
||||||
return "signal3.svg";
|
return "signal3.svg";
|
||||||
}
|
}
|
||||||
if (signalStrength >= -70) {
|
if (signalStrength >= -70) {
|
||||||
return "signal2.svg";
|
return "signal2.svg";
|
||||||
}
|
}
|
||||||
return "signal1.svg";
|
return "signal1.svg";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
352
data/style.css
352
data/style.css
|
|
@ -1,330 +1,330 @@
|
||||||
:root {
|
:root {
|
||||||
--color-primary: #087e8b;
|
--color-primary: #087e8b;
|
||||||
--color-on-primary: white;
|
--color-on-primary: white;
|
||||||
--color-background: #222;
|
--color-background: #222;
|
||||||
--color-surface: #333;
|
--color-surface: #333;
|
||||||
--color-danger: #fa2b58;
|
--color-danger: #fa2b58;
|
||||||
--appended-item-size: 2.5rem;
|
--appended-item-size: 2.5rem;
|
||||||
|
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: linear-gradient(to left, #065760, black, black, #065760);
|
background: linear-gradient(to left, #065760, black, black, #065760);
|
||||||
color: white;
|
color: white;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
padding: 8px max(5%, 8px);
|
padding: 8px max(5%, 8px);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
form > * {
|
form > * {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-color: white;
|
border-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
label span {
|
label span {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
select,
|
select,
|
||||||
label div {
|
label div {
|
||||||
width: clamp(200px, 100%, 400px);
|
width: clamp(200px, 100%, 400px);
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
select {
|
select {
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
color: white;
|
color: white;
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus,
|
input:focus,
|
||||||
select:focus {
|
select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
select:has(+ .icon-button),
|
select:has(+ .icon-button),
|
||||||
label div input[type="range"] {
|
label div input[type="range"] {
|
||||||
width: calc(clamp(200px, 100%, 400px) - var(--appended-item-size) - 8px);
|
width: calc(clamp(200px, 100%, 400px) - var(--appended-item-size) - 8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
accent-color: var(--color-primary);
|
accent-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border: none;
|
border: none;
|
||||||
inset: none;
|
inset: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
color: var(--color-on-primary);
|
color: var(--color-on-primary);
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button[type="reset"] {
|
button[type="reset"] {
|
||||||
background-color: var(--color-danger);
|
background-color: var(--color-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(div:has(:is(input, select)), input, select, label)
|
:is(div:has(:is(input, select)), input, select, label)
|
||||||
+ :is(div:has(:is(input, select)), input, select, label) {
|
+ :is(div:has(:is(input, select)), input, select, label) {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.switch {
|
label.switch {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.switch input {
|
label.switch input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.switch .slider {
|
label.switch .slider {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
width: 2em;
|
width: 2em;
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
border: 4px solid #444;
|
border: 4px solid #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.switch .slider::before {
|
label.switch .slider::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
transition: all 0.1s linear;
|
transition: all 0.1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.switch:active .slider::before {
|
label.switch:active .slider::before {
|
||||||
transform: scale(1.3);
|
transform: scale(1.3);
|
||||||
transform-origin: 50% 50%;
|
transform-origin: 50% 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.switch input:not(:checked) + .slider::before {
|
label.switch input:not(:checked) + .slider::before {
|
||||||
left: 0%;
|
left: 0%;
|
||||||
translate: 0 -50%;
|
translate: 0 -50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.switch input:checked + .slider::before {
|
label.switch input:checked + .slider::before {
|
||||||
left: 100%;
|
left: 100%;
|
||||||
translate: -100% -50%;
|
translate: -100% -50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog {
|
dialog {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog::backdrop {
|
dialog::backdrop {
|
||||||
background-color: #000a;
|
background-color: #000a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
& button {
|
& button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card > * {
|
.card > * {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card > :first-child {
|
.card > :first-child {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-screen {
|
.loading-screen {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-bg {
|
.error-bg {
|
||||||
background-color: var(--color-danger) !important;
|
background-color: var(--color-danger) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.reload {
|
button.reload {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner-container {
|
.spinner-container {
|
||||||
width: min(max-content, 100%);
|
width: min(max-content, 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
background: conic-gradient(transparent 150deg, var(--color-primary));
|
background: conic-gradient(transparent 150deg, var(--color-primary));
|
||||||
--outer-diameter: 50px;
|
--outer-diameter: 50px;
|
||||||
width: var(--outer-diameter);
|
width: var(--outer-diameter);
|
||||||
height: var(--outer-diameter);
|
height: var(--outer-diameter);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
animation-name: spin;
|
animation-name: spin;
|
||||||
animation-duration: 1s;
|
animation-duration: 1s;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner::after {
|
.spinner::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
--spinner-border: 5px;
|
--spinner-border: 5px;
|
||||||
top: var(--spinner-border);
|
top: var(--spinner-border);
|
||||||
left: var(--spinner-border);
|
left: var(--spinner-border);
|
||||||
|
|
||||||
--inner-diameter: calc(var(--outer-diameter) - 2 * var(--spinner-border));
|
--inner-diameter: calc(var(--outer-diameter) - 2 * var(--spinner-border));
|
||||||
width: var(--inner-diameter);
|
width: var(--inner-diameter);
|
||||||
height: var(--inner-diameter);
|
height: var(--inner-diameter);
|
||||||
|
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
width: var(--appended-item-size);
|
width: var(--appended-item-size);
|
||||||
height: var(--appended-item-size);
|
height: var(--appended-item-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinning {
|
.spinning {
|
||||||
animation-name: spin;
|
animation-name: spin;
|
||||||
animation-duration: 0.5s;
|
animation-duration: 0.5s;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
div:has(.range-value) {
|
div:has(.range-value) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.range-value {
|
.range-value {
|
||||||
width: var(--appended-item-size);
|
width: var(--appended-item-size);
|
||||||
height: var(--appended-item-size);
|
height: var(--appended-item-size);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: var(--appended-item-size);
|
line-height: var(--appended-item-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-status-content {
|
.dialog-status-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connection-icon {
|
.connection-icon {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connection-icon.small {
|
.connection-icon.small {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centered-vertical {
|
.centered-vertical {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
data/submit.js
104
data/submit.js
|
|
@ -1,74 +1,74 @@
|
||||||
import { loadData, writeDataToInput } from "./load-data.js";
|
import { loadData, writeDataToInput } from "./load-data.js";
|
||||||
import {
|
import {
|
||||||
showLoadingScreen,
|
showLoadingScreen,
|
||||||
hideLoadingScreen,
|
hideLoadingScreen,
|
||||||
showError,
|
showError,
|
||||||
} from "./loading-screen.js";
|
} from "./loading-screen.js";
|
||||||
|
|
||||||
const form = document.querySelector("form.config");
|
const form = document.querySelector("form.config");
|
||||||
|
|
||||||
function parseValue(input) {
|
function parseValue(input) {
|
||||||
if (input.type === "checkbox") {
|
if (input.type === "checkbox") {
|
||||||
return input.checked
|
return input.checked
|
||||||
? input.dataset.valueChecked
|
? input.dataset.valueChecked
|
||||||
: input.dataset.valueNotChecked;
|
: input.dataset.valueNotChecked;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.value === "") {
|
if (input.value === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.type === "number" || input.type === "range") {
|
if (input.type === "number" || input.type === "range") {
|
||||||
const number = Number(input.value);
|
const number = Number(input.value);
|
||||||
return Number.isNaN(number) ? null : number;
|
return Number.isNaN(number) ? null : number;
|
||||||
}
|
}
|
||||||
|
|
||||||
return input.value;
|
return input.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.addEventListener("submit", (event) => {
|
form.addEventListener("submit", event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const inputFields = document.querySelectorAll(
|
const inputFields = document.querySelectorAll(
|
||||||
"form :is(input, select, textarea):not(:disabled)"
|
"form :is(input, select, textarea):not(:disabled)"
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = Array.from(inputFields).reduce((data, input) => {
|
const data = Array.from(inputFields).reduce((data, input) => {
|
||||||
data[input.name] = parseValue(input);
|
data[input.name] = parseValue(input);
|
||||||
return data;
|
return data;
|
||||||
}, {});
|
}, {});
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
updateConfig({
|
updateConfig({
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function updateConfig(fetchOptions) {
|
export async function updateConfig(fetchOptions) {
|
||||||
showLoadingScreen("Konfiguration anwenden und ESP neustarten...");
|
showLoadingScreen("Konfiguration anwenden und ESP neustarten...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("/config", fetchOptions);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Response status: ${res.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
showError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/config", fetchOptions);
|
const data = await loadData(5000);
|
||||||
if (!res.ok) {
|
writeDataToInput(data);
|
||||||
throw new Error(`Response status: ${res.status}`);
|
hideLoadingScreen();
|
||||||
}
|
|
||||||
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message);
|
// retry loading config until successful
|
||||||
showError(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
try {
|
|
||||||
const data = await loadData(5000);
|
|
||||||
writeDataToInput(data);
|
|
||||||
hideLoadingScreen();
|
|
||||||
|
|
||||||
break;
|
|
||||||
} catch (error) {
|
|
||||||
// retry loading config until successful
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,34 +4,34 @@ let ws;
|
||||||
let callbacks = {};
|
let callbacks = {};
|
||||||
|
|
||||||
export function initWebSocket() {
|
export function initWebSocket() {
|
||||||
if (ws) return;
|
if (ws) return;
|
||||||
|
|
||||||
ws = new WebSocket(gateway);
|
ws = new WebSocket(gateway);
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.info("WebSocket connection opened");
|
console.info("WebSocket connection opened");
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = (event) => {
|
ws.onclose = event => {
|
||||||
console.info("WebSocket connection closed, reason:", event.reason);
|
console.info("WebSocket connection closed, reason:", event.reason);
|
||||||
ws = null;
|
ws = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = (event) => {
|
ws.onerror = event => {
|
||||||
console.warn("WebSocket encountered error, closing socket.", event);
|
console.warn("WebSocket encountered error, closing socket.", event);
|
||||||
ws.close();
|
ws.close();
|
||||||
ws = null;
|
ws = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = event => {
|
||||||
const message = JSON.parse(event.data);
|
const message = JSON.parse(event.data);
|
||||||
console.log("received websocket data", message);
|
console.log("received websocket data", message);
|
||||||
if (message.type in callbacks) {
|
if (message.type in callbacks) {
|
||||||
callbacks[message.type](message.data);
|
callbacks[message.type](message.data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerCallback(type, callback) {
|
export function registerCallback(type, callback) {
|
||||||
callbacks[type] = callback;
|
callbacks[type] = callback;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,18 @@
|
||||||
|
|
||||||
static const char *TAG = "MAIN";
|
static const char *TAG = "MAIN";
|
||||||
|
|
||||||
void app_main(void)
|
void app_main(void) {
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "DMX Interface starting...");
|
ESP_LOGI(TAG, "DMX Interface starting...");
|
||||||
|
|
||||||
esp_err_t wifi_err = wifi_start_ap("DMX", "mbgmbgmbg", 1, 4);
|
esp_err_t wifi_err = wifi_start_ap("DMX", "mbgmbgmbg", 1, 4);
|
||||||
if (wifi_err != ESP_OK)
|
if (wifi_err != ESP_OK) {
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Failed to start WiFi AP: %s", esp_err_to_name(wifi_err));
|
ESP_LOGE(TAG, "Failed to start WiFi AP: %s", esp_err_to_name(wifi_err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start HTTP web server
|
// Start HTTP web server
|
||||||
httpd_handle_t server = webserver_start(NULL);
|
httpd_handle_t server = webserver_start(NULL);
|
||||||
if (server == NULL)
|
if (server == NULL) {
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Failed to start web server!");
|
ESP_LOGE(TAG, "Failed to start web server!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -32,8 +29,7 @@ void app_main(void)
|
||||||
ESP_LOGI(TAG, "Open http://192.168.4.1 in your browser");
|
ESP_LOGI(TAG, "Open http://192.168.4.1 in your browser");
|
||||||
|
|
||||||
// Keep the app running
|
// Keep the app running
|
||||||
while (1)
|
while (1) {
|
||||||
{
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
## IDF Component Manager Manifest File
|
## IDF Component Manager Manifest File
|
||||||
dependencies:
|
dependencies:
|
||||||
idf:
|
idf:
|
||||||
version: '>=4.1.0'
|
version: ">=4.1.0"
|
||||||
joltwallet/littlefs: ==1.20.2
|
joltwallet/littlefs: ==1.20.2
|
||||||
someweisguy/esp_dmx:
|
someweisguy/esp_dmx:
|
||||||
git: https://github.com/davispolito/esp_dmx.git
|
git: https://github.com/davispolito/esp_dmx.git
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue