Pārlūkot izejas kodu

httpd, config: numerous improvements

H. Peter Anvin 2 gadi atpakaļ
vecāks
revīzija
2d901ea341

+ 27 - 16
esp32/Makefile

@@ -1,23 +1,34 @@
-MAKEFLAGS += -R -r
-
-ARDUINO_CLI = arduino-cli
-ZIP         = zip
-
-SKETCH   = max80
-TARGET   = output/$(SKETCH).ino.bin
-GENFILES = www.zip
-WWW      = www
-PORT	?= /dev/ttyACM2
-
-BOARD    = esp32:esp32:esp32s2usb
+MAKEFLAGS    += -R -r
+
+ARDUINO_CLI   = arduino-cli
+ZIP	      = zip
+
+SKETCH	      = max80
+TARGET	      = output/$(SKETCH).ino.bin
+GENFILES      = www.zip
+WWW	      = www
+PORT	     ?= /dev/ttyACM2
+
+BOARD	      = esp32:esp32:esp32s2usb
+ARDUINO_OPTS  = -b $(BOARD) \
+		--warnings all \
+		--build-path       ../build \
+		--output-dir       ../output \
+		--build-cache-path ../cache \
+		--build-property 'build.flash_size=4MB' \
+		--build-property 'build.defines=-DBOARD_HAS_PSRAM' \
+		--build-property 'build.partitions=min_spiffs'
 
 all: $(TARGET)
 
+properties:
+	cd $(SKETCH) && \
+		$(ARDUINO_CLI) compile --show-properties $(ARDUINO_OPTS)
+
 $(TARGET): $(shell find $(SKETCH) -type f) $(GENFILES)
-	mkdir -p build output
+	mkdir -p build output cache
 	cd $(SKETCH) && \
-		$(ARDUINO_CLI) compile -b $(BOARD) --warnings all \
-			--build-path ../build --output-dir ../output
+		$(ARDUINO_CLI) compile $(ARDUINO_OPTS)
 
 .PHONY: zip
 zip:
@@ -38,7 +49,7 @@ upload: $(TARGET)
 	$(ARDUINO_CLI) upload -i $(TARGET) -p $(PORT) -b $(BOARD) $(SKETCH)
 
 clean:
-	rm -rf build zip $(GENFILES)
+	rm -rf build cache zip $(GENFILES)
 
 spotless: clean
 	rm -rf output

+ 144 - 64
esp32/max80/config.c

@@ -4,38 +4,55 @@
 #include <esp_spiffs.h>
 #include <ctype.h>
 
-#define MAX_CONFIG_LINE 256
 #define CONFIG_FILE	"/spiffs/config.txt"
 
-struct configvar {
-    const char *name;
-    const char *val;
+static const char * const default_config[] = {
+    "TZ=CET-1CEST,M3.5.0,M10.5.0/3", /* Sweden */
+    "hostname=max80",
+    "sntp.enabled=on",
+    "sntp.server=time.max80.abc80.org",
+    "LANG=sv"
 };
 
-static const struct configvar default_config[] = {
-    { "TZ", "CET-1CEST,M3.5.0,M10.5.0/3" }, /* Sweden */
-    { "hostname", "max80" }
-};
+static int save_config(void);
 
-#ifndef HAVE_CLEARENV
-static void clearenv(void)
+static bool config_changed;
+int setenv_config(const char *name, const char *value)
 {
-    char **new_environ = calloc(ARRAY_SIZE(default_config)+1, sizeof(char *));
-    char **old_environ = environ;
+    config_changed = true;
+    if (name[0] == '-') {
+	name++;
+	value = NULL;
+    }
+    if (value)
+	return setenv(name, value, 1);
+    else
+	return unsetenv(name);
+}
+
+static int reset_config(void)
+{
+    char **new_environ = malloc((ARRAY_SIZE(default_config)+1)*sizeof(char *));
+
+    if (!new_environ)
+	return -1;
 
+    int i;
+    for (i = 0; i < ARRAY_SIZE(default_config); i++) {
+	if (!(new_environ[i] = strdup(default_config[i])))
+	    return -1;
+    }
+
+    new_environ[i] = NULL;
+
+    char **old_environ = environ;
     environ = new_environ;
 
     for (char **envp = old_environ; *envp; envp++)
 	free(*envp);
     free(old_environ);
-}
-#endif /* HAVE_CLEARENV */
 
-static void reset_config(void)
-{
-    clearenv();
-    for (size_t i = 0; i < ARRAY_SIZE(default_config); i++)
-	setenv(default_config[i].name, default_config[i].val, 1);
+    config_changed = true;
 }
 
 static bool is_end_of_string(int c)
@@ -43,34 +60,25 @@ static bool is_end_of_string(int c)
     return c <= 0 || c == '\n' || c == '\r';
 }
 
-static void skip_rest_of_line(FILE *f)
-{
-    int c;
-    do {
-	c = getc(f);
-    } while (!is_end_of_string(c));
-}
-
-int read_config(FILE *f)
+/*
+ * Note: the string must be mutable; use strdup() if necessary.
+ * The "separator" allows the string to be separated into multiple
+ * arguments; no other decoding is done.
+ */
+static int set_config_string(char *str, unsigned int separator)
 {
-    char *linebuf = NULL;
-    int err = -1;
-
-    linebuf = malloc(MAX_CONFIG_LINE);
-    if (!linebuf)
-	goto exit;
-
-    while (fgets(linebuf, MAX_CONFIG_LINE, f)) {
-	char *p, *q;
-	unsigned char c;
+    char *p, *q;
+    unsigned char c;
 
-	p = linebuf;
+    p = str;
+    do {
+	const char *var = p;
 
 	do {
 	    c = *p++;
 	} while (isalnum(c) || c == '.' || c == '-');
 	if (c != '=')
-	    continue;		/* Invalid config line (blank, comment...) */
+	    return -EINVAL; /* Invalid config line (blank, comment...) */
 
 	p[-1] = '\0';
 
@@ -78,27 +86,83 @@ int read_config(FILE *f)
 
 	do {
 	    c = *q++;
-	} while (!is_end_of_string(c));
-
-	if (q >= linebuf + MAX_CONFIG_LINE) {
-	    /* Overlong line, discard rest and drop */
-	    skip_rest_of_line(f);
-	} else {
-	    q[-1] = '\0';
-	    if (linebuf[0] == '-')
-		unsetenv(linebuf+1);
-	    else
-		setenv(linebuf, p, 1);
+	} while (!is_end_of_string(c) && c != separator);
+
+	/* Overlong line */
+	if (q >= str + MAX_CONFIG_LINE)
+	    return -EOVERFLOW;
+
+	q[-1] = '\0';
+	setenv_config(var, p);
+
+	p = q;
+    } while (c == separator);
+
+    return 0;
+}
+
+static void finish_config_update(bool save)
+{
+    if (config_changed) {
+	if (save)
+	    save_config();
+	tzset();
+	config_changed = false;
+    }
+}
+
+/*
+ * At the moment "url" just allows values to be separated by semicolons;
+ * no other decoding is done, and '&' is not supported.
+ */
+int set_config_url_string(const char *str)
+{
+    char *wstr = strdup(str);
+
+    if (!wstr)
+	return -ENOMEM;
+
+    int err = set_config_string(wstr, ';');
+    free(wstr);
+
+    finish_config_update(true);
+    return err;
+}
+
+static void skip_rest_of_line(FILE *f)
+{
+    int c;
+    do {
+	c = getc(f);
+    } while (!is_end_of_string(c));
+}
+
+/* Calling with with f == NULL just finalizes the update */
+int read_config(FILE *f, bool save)
+{
+    char *linebuf = NULL;
+    int err = -1;
+
+    if (f) {
+	linebuf = malloc(MAX_CONFIG_LINE);
+	if (!linebuf) {
+	    err = -ENOMEM;
+	    goto exit;
+	}
+
+	while (fgets(linebuf, MAX_CONFIG_LINE, f)) {
+	    if (set_config_string(linebuf, -1) == -EOVERFLOW)
+		skip_rest_of_line(f);
 	}
     }
 
     err = 0;
 
-exit:
     if (linebuf)
 	free(linebuf);
 
-    tzset();
+exit:
+    finish_config_update(save);
     return err;
 };
 
@@ -112,14 +176,18 @@ int write_config(FILE *f)
     return ferror(f) ? -1 : 0;
 }
 
-int save_config(void)
+static int save_config(void)
 {
+    int err = -ENOENT;
+
     FILE *f = fopen(CONFIG_FILE, "w");
-    if (!f)
-	return -1;
+    if (f) {
+	err = write_config(f);
+	fclose(f);
+    }
 
-    int err = write_config(f);
-    fclose(f);
+    if (err)
+	printf("[CONF] Failed to save configuration (error %d)\n", err);
 
     return err;
 }
@@ -133,17 +201,29 @@ static const esp_vfs_spiffs_conf_t spiffs_conf = {
 
 void init_config(void)
 {
-    reset_config();
+    if (!esp_spiffs_mounted(spiffs_conf.partition_label)) {
+	esp_err_t err;
+	err = esp_vfs_spiffs_register(&spiffs_conf);
+	if (err)
+	    printf("[CONF] Failed to mount %s (error 0x%x)\n",
+		   spiffs_conf.base_path, err);
+    }
 
-    if (!esp_spiffs_mounted(spiffs_conf.partition_label))
-	esp_vfs_spiffs_register(&spiffs_conf);
+    reset_config();
 
     FILE *f = fopen(CONFIG_FILE, "r");
     if (!f)
-	return;			/* No config file */
+	printf("[CONF] No configuration file found, using defaults\n");
+
+    read_config(f, false);
+    if (f)
+	fclose(f);
+}
 
-    read_config(f);
-    fclose(f);
+const char *getenv_def(const char *var, const char *def)
+{
+    const char *val = getenv(var);
+    return val ? val : def;
 }
 
 long getenv_l(const char *var, long def)

+ 7 - 6
esp32/max80/config.h

@@ -4,18 +4,19 @@
 
 #include <stdio.h>
 
-extern_c int read_config(FILE *);
+#define MAX_CONFIG_LINE 256
+
+extern_c int read_config(FILE *, bool save);
 extern_c int write_config(FILE *);
-extern_c int save_config(void);
 extern_c void init_config(void);
 
+extern_c int set_config_url_string(const char *str);
+
+extern_c int setenv_config(const char *var, const char *val);
+extern_c const char *getenv_def(const char *var, const char *def);
 extern_c long getenv_l(const char *var, long def);
 extern_c void setenv_l(const char *var, long val);
 extern_c unsigned long getenv_ul(const char *var, unsigned long def);
 extern_c void setenv_ul(const char *var, unsigned long val);
 extern_c bool getenv_bool(const char *var);
 extern_c void setenv_bool(const char *var, bool val);
-
-
-
-

+ 101 - 47
esp32/max80/httpd.c

@@ -137,14 +137,20 @@ static const char *http_dos_date(uint32_t dos_date)
 static const char text_plain[] = "text/plain; charset=\"UTF-8\"";
 
 enum hsp_flags {
-    HSP_CLOSE = 1,
-    HSP_CRLF  = 2
+    HSP_CLOSE        = 1,
+    HSP_CRLF         = 2,
+    HSP_CLOSE_SOCKET = 4
 };
 
+static void httpd_print_request(const httpd_req_t *req)
+{
+    printf("[HTTP] %s %s\n", http_method_str(req->method), req->uri);
+}
+
 static esp_err_t httpd_send_plain(httpd_req_t *req,
 				  unsigned int rcode,
 				  const char *body, size_t blen,
-				  enum hsp_flags flags)
+				  enum hsp_flags flags, unsigned int refresh)
 {
     char *header = NULL;
     esp_err_t err;
@@ -157,6 +163,12 @@ static esp_err_t httpd_send_plain(httpd_req_t *req,
     const char *closer = flags & HSP_CLOSE ? "Connection: close\r\n" : "";
     bool redirect = rcode >= 300 && rcode <= 399;
 
+    static const char refresher[] = "Refresh: %u;url=/";
+    char refresh_str[sizeof(refresher)-2 + sizeof(unsigned int)*3];
+    refresh_str[0] = '\0';
+    if (refresh)
+	snprintf(refresh_str, sizeof refresh_str, refresher, refresh);
+
     if (redirect) {
 	size_t blenadj = sizeof("3xx Redirect \r"); /* \0 -> \n so don't include it */
 	flags |= HSP_CRLF;
@@ -167,11 +179,11 @@ static esp_err_t httpd_send_plain(httpd_req_t *req,
 			"Content-Length: %zu\r\n"
 			"Date %s\r\n"
 			"Location: %.*s\r\n"
-			"%s"
+			"%s%s"
 			"\r\n"
 			"%3u Redirect ",
 			rcode, text_plain, blen + blenadj,
-			now, (int)blen, body, closer, rcode);
+			now, (int)blen, body, closer, refresh_str, rcode);
     } else {
 	size_t blenadj = (flags & HSP_CRLF) ? 2 : 0;
 
@@ -179,10 +191,12 @@ static esp_err_t httpd_send_plain(httpd_req_t *req,
 			"HTTP/1.1 %u\r\n"
 			"Content-Type: %s\r\n"
 			"Content-Length: %zu\r\n"
+			"Cache-Control: no-store\r\n"
 			"Date: %s\r\n"
-			"%s"
+			"%s%s"
 			"\r\n",
-			rcode, text_plain, blen + blenadj, now, closer);
+			rcode, text_plain, blen + blenadj, now,
+			closer, refresh_str);
     }
 
     if (!header)
@@ -199,12 +213,13 @@ static esp_err_t httpd_send_plain(httpd_req_t *req,
     if (header)
 	free(header);
 
-    return err;
+    /* Sending ESP_FAIL causes the socket to be immediately closed */
+    return err ? err : (flags & HSP_CLOSE_SOCKET) ? ESP_FAIL : ESP_OK;
 }
 
-#define HTTP_ERR(r,e,s) httpd_send_plain((r), (e), s, sizeof(s)-1, HSP_CRLF)
+#define HTTP_ERR(r,e,s) httpd_send_plain((r), (e), s, sizeof(s)-1, HSP_CRLF, 0)
 
-static esp_err_t httpd_err_404(httpd_req_t *req)
+static esp_err_t httpd_err_enoent(httpd_req_t *req)
 {
     return HTTP_ERR(req, 404, "URI not found");
 }
@@ -214,6 +229,11 @@ static esp_err_t httpd_send_ok(httpd_req_t *req)
     return HTTP_ERR(req, 200, "OK");
 }
 
+static esp_err_t httpd_err_enomem(httpd_req_t *req)
+{
+    return HTTP_ERR(req, 503, "Out of memory");
+}
+
 static esp_err_t httpd_update_done(httpd_req_t *req, const char *what, int err)
 {
     char *response = NULL;
@@ -236,7 +256,8 @@ static esp_err_t httpd_update_done(httpd_req_t *req, const char *what, int err)
 	len = 0;
 
     esp_err_t rv = httpd_send_plain(req, err ? 400 : 200, response, len,
-				    HSP_CLOSE|HSP_CRLF);
+				    HSP_CLOSE|HSP_CLOSE_SOCKET|HSP_CRLF,
+				    reboot_time+5);
     if (response)
 	free(response);
 
@@ -252,40 +273,69 @@ static esp_err_t httpd_firmware_update(httpd_req_t *req)
     return httpd_update_done(req, "Firmware", rv);
 }
 
-static esp_err_t httpd_set_config(httpd_req_t *req)
+static esp_err_t httpd_set_config(httpd_req_t *req, const char *query)
 {
-    FILE *f = httpd_fopen_read(req);
-    if (!f)
-	return HTTP_ERR(req, 500, "Unable to get request handle");
+    FILE *f;
+    size_t qlen;
 
-    int rv = read_config(f);
-    fclose(f);
-    if (rv) {
-	rv = FWUPDATE_ERR_CONFIG_READ;
-    } else {
-	rv = save_config();
-	if (rv)
-	    rv = FWUPDATE_ERR_CONFIG_SAVE;
+    int rv1 = 0;
+    if (query) {
+	rv1 = set_config_url_string(query);
+    }
+
+    int rv2 = 0;
+    f = NULL;
+    if (req->content_len) {
+	f = httpd_fopen_read(req);
+	if (!f)
+	    return HTTP_ERR(req, 500, "Unable to get request handle");
     }
+    rv2 = read_config(f, true);
+    if (f)
+	fclose(f);
 
-    return httpd_update_done(req, "Configuration", rv);
+    return httpd_update_done(req, "Configuration", rv1 ? rv1 : rv2);
 }
 
+static esp_err_t httpd_set_lang(httpd_req_t *req, const char *query)
+{
+    if (query && *query) {
+	setenv_config("LANG", query);
+	read_config(NULL, true);	/* Save configuration */
+    }
+
+    return httpd_send_plain(req, 302, "/", 1, 0, 0);
+}
+
+#define STRING_MATCHES(str, len, what) \
+    (((len) == sizeof(what)-1) && !memcmp((str), (what), sizeof(what)-1))
+
 static esp_err_t httpd_sys_post_handler(httpd_req_t *req)
 {
-    printf("[POST] len = %zu uri = \"%s\"\n",
-	   req->content_len, req->uri);
+    httpd_print_request(req);
 
-    if (!req->content_len)
+    if (!httpd_req_get_hdr_value_len(req, "Content-Length"))
 	return HTTP_ERR(req, 411, "Length required");
 
-    if (!strcmp(req->uri, "/sys/fwupdate"))
+    if (strncmp(req->uri, "/sys/", 5))
+	return httpd_err_enoent(req); /* This should never happen */
+
+    const char *file = req->uri + 5;
+    size_t filelen = strcspn(file, "?");
+    const char *query = NULL;
+    if (file[filelen] == '?')
+	query = &file[filelen] + 1;
+
+    if (STRING_MATCHES(file, filelen, "fwupdate"))
 	return httpd_firmware_update(req);
 
-    if (!strcmp(req->uri, "/sys/setconfig"))
-	return httpd_set_config(req);
+    if (STRING_MATCHES(file, filelen, "setconfig"))
+	return httpd_set_config(req, query);
+
+    if (STRING_MATCHES(file, filelen, "lang"))
+	return httpd_set_lang(req, query);
 
-    return httpd_err_404(req);
+    return httpd_err_enoent(req);
 }
 
 static esp_err_t httpd_get_config(httpd_req_t *req)
@@ -295,6 +345,7 @@ static esp_err_t httpd_get_config(httpd_req_t *req)
 	return HTTP_ERR(req, 500, "Unable to get request handle");
 
     httpd_resp_set_type(req, text_plain);
+    httpd_resp_set_hdr(req, "Cache-Control", "no-store");
 
     int rv = write_config(f);
     fclose(f);
@@ -303,10 +354,12 @@ static esp_err_t httpd_get_config(httpd_req_t *req)
 
 static esp_err_t httpd_sys_get_handler(httpd_req_t *req)
 {
+    httpd_print_request(req);
+
     if (!strcmp(req->uri, "/sys/getconfig"))
 	return httpd_get_config(req);
 
-    return httpd_err_404(req);
+    return httpd_err_enoent(req);
 }
 
 INCBIN_EXTERN(wwwzip);
@@ -346,7 +399,7 @@ static const struct mime_type mime_types[] = {
 static esp_err_t httpd_static_handler(httpd_req_t *req)
 {
     static const char index_filename[] = "index.html";
-    static const char default_lang[] = "en";
+    static const char fallback_lang[] = "en";
     size_t buffer_size = UNZ_BUFSIZE;
     const char *uri, *enduri;
     bool add_index;
@@ -357,6 +410,8 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
     int err = 0;
     size_t len;
 
+    httpd_print_request(req);
+
     uri = req->uri;
     while (*uri == '/')
 	uri++;			/* Skip leading slashes */
@@ -381,16 +436,10 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 	add_index = false;	/* Try the plain filename first */
     }
 
-    MSG("requesting: /%.*s\n", enduri - uri, uri);
-    if (first_slash)
-	MSG("first_slash = %.*s\n", enduri - first_slash, first_slash);
-    else
-	MSG("first_slash = NULL\n");
-
-    const char *lang = getenv("LANG");
+    const char *lang = getenv_def("LANG", "");
     const size_t lang_size = lang ? strlen(lang)+1 : 0;
-    const size_t lang_space = (lang_size < sizeof default_lang
-			       ? sizeof default_lang : lang_size) + 1;
+    const size_t lang_space = (lang_size < sizeof fallback_lang
+			       ? sizeof fallback_lang : lang_size) + 1;
     const size_t filename_buffer_size =
 	(enduri - uri) + lang_space + 2 + sizeof index_filename;
 
@@ -400,7 +449,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
     buffer = malloc(buffer_size);
     zip = malloc(sizeof *zip);
     if (!buffer || !zip) {
-	err = HTTP_ERR(req, 503, "Out of memory");
+	err = httpd_err_enomem(req);
 	goto out;
     }
 
@@ -440,8 +489,8 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 	    }
 	    break;
 	case 2:
-	    filename = filebase - sizeof default_lang;
-	    memcpy(filename, default_lang, sizeof default_lang - 1);
+	    filename = filebase - sizeof fallback_lang;
+	    memcpy(filename, fallback_lang, sizeof fallback_lang - 1);
 	    break;
 	default:
 	    filename = filebase;
@@ -462,10 +511,10 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
     size_t filelen = endfile - filename;
 
     if (!found) {
-	err = httpd_err_404(req);
+	err = httpd_err_enoent(req);
 	goto out;
     } else if (m) {
-	err = httpd_send_plain(req, 302 - (m == 1), filename-1, filelen+1, 0);
+	err = httpd_send_plain(req, 302 - (m == 1), filename-1, filelen+1, 0, 0);
 	goto out;
     }
 
@@ -508,6 +557,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
     len = snprintf(buffer, buffer_size-2,
 		   "HTTP/1.1 %s\r\n"
 		   "Date: %s\r\n"
+		   "Cache-Control: max-age=10\r\n"
 		   "ETag: %s\r\n",
 		   response,
 		   http_now(),
@@ -521,6 +571,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 			"Content-Type: %s%s\r\n"
 			"Content-Length: %u\r\n"
 			"Allow: GET, HEAD\r\n"
+			"Connection: close\r\n"
 			"Last-Modified: %s\r\n",
 			mime_type->mime, mime_extra,
 			fileinfo.uncompressed_size,
@@ -645,6 +696,7 @@ static const httpd_uri_t uri_handlers[] = {
 void my_httpd_stop(void)
 {
     if (httpd) {
+	esp_unregister_shutdown_handler(my_httpd_stop);
 	httpd_stop(httpd);
 	httpd = NULL;
     }
@@ -670,6 +722,8 @@ void my_httpd_start(void)
     if (httpd_start(&server, &config) != ESP_OK)
       return;
 
+    esp_register_shutdown_handler(my_httpd_stop);
+
     httpd = server;
     for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++) {
 	const httpd_uri_t * const handler = &uri_handlers[i];

+ 13 - 1
esp32/max80/max80.ino

@@ -4,12 +4,14 @@
 
 #include "common.h"
 
-#include <freertos/task_snapshot.h>
 #include "fpga.h"
 #include "wifi.h"
 #include "config.h"
 #include "led.h"
 
+#include <freertos/task_snapshot.h>
+#include <esp_heap_caps.h>
+
 // On board v1, IO7 is N/C.
 // On board v2, IO7 is USB_PWR_EN and has a 36k pulldown.
 static int get_board_version()
@@ -35,6 +37,9 @@ static void init_hw()
     // Configure LEDs
     led_init();
     led_set(LED_BLUE, LED_FLASH);	// ESP32 software initializing
+
+    // Enable external PSRAM for heap
+    heap_caps_malloc_extmem_enable(2048); // >= 2K allocations in PSRAM
 }
 
 void setup() {
@@ -46,6 +51,13 @@ void setup() {
     printf("[RDY]\n");
     dump_config();
     led_set(LED_BLUE, LED_ON);	// Software ready
+
+    printf("Total heap:  %d\n"
+	   "Free heap:   %d\n"
+	   "Total PSRAM: %d\n"
+	   "Free PSRAM:  %d\n",
+	   ESP.getHeapSize(), ESP.getFreeHeap(),
+	   ESP.getPsramSize(), ESP.getFreePsram());
 }
 
 static inline char task_state(eTaskState state)

+ 26 - 18
esp32/max80/wifi.cpp

@@ -70,11 +70,15 @@ static void sntp_sync_cb(struct timeval *tv)
 
 static void my_sntp_start()
 {
-    sntp_set_time_sync_notification_cb(sntp_sync_cb);
-    sntp_setoperatingmode(SNTP_OPMODE_POLL);
-    sntp_servermode_dhcp(1);
-    sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED); // Until first sync
-    sntp_init();
+    if (getenv_bool("sntp.enabled")) {
+	sntp_set_time_sync_notification_cb(sntp_sync_cb);
+	sntp_setoperatingmode(SNTP_OPMODE_POLL);
+	sntp_servermode_dhcp(!getenv_bool("ip4.dhcp.nosntp"));
+	sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED); // Until first sync
+	sntp_init();
+    } else {
+	sntp_stop();
+    }
 }
 
 static bool services_started;
@@ -101,7 +105,8 @@ static void start_services(void)
 	/* Static DNS server configuration */
 	ip_addr_t addr;
 	if (dnsserver != "" && inet_aton(dnsserver.c_str(), &addr)) {
-	    dns_setserver(0, &addr);
+	    if (memcmp(dns_ip, &addr, sizeof addr))
+		dns_setserver(0, &addr);
 	}
     }
 
@@ -110,16 +115,20 @@ static void start_services(void)
 
     // If Arduino supported both of these at the same that would be
     // awesome, but it requires ESP-IDF reconfiguration...
-    const ip_addr_t *sntp_ip = sntp_getserver(0);
+    if (sntp_enabled()) {
+	const ip_addr_t *sntp_ip = sntp_getserver(0);
+
+	if (invalid_ip(sntp_ip)) {
+	    if (sntpserver != "") {
+		sntp_setservername(0, sntpserver.c_str());
+		sntp_ip = sntp_getserver(0);
+	    }
+	}
 
-    if (invalid_ip(sntp_ip) || getenv_bool("ip4.dhcp.nosntp")) {
-	if (sntpserver != "")
-	    sntp_setservername(0, sntpserver.c_str());
+	if (!invalid_ip(sntp_ip))
+	    printf("[SNTP] Time server: %s\n", inet_ntoa(*sntp_ip));
     }
 
-    sntp_ip = sntp_getserver(0);
-    printf("[SNTP] Time server: %s\n", inet_ntoa(*sntp_ip));
-
     /* Only run on first start */
     if (!services_started) {
 	services_started = true;
@@ -335,11 +344,10 @@ static void wifi_config_sta(void)
 
 static void wifi_config(void)
 {
-    ssid       = getenv("wifi.ssid");
-    password   = getenv("wifi.psk");
-    hostname   = getenv("hostname");
-    sntpserver = getenv_bool("sntp") ? getenv("sntp.server") : "";
-    dnsserver  = getenv("ip4.dns");
+    ssid         = getenv("wifi.ssid");
+    password     = getenv("wifi.psk");
+    hostname     = getenv("hostname");
+    dnsserver    = getenv("ip4.dns");
 
     WiFi.persistent(false);
     WiFi.setSleep(false);

BIN
esp32/output/max80.ino.bin


+ 22 - 12
esp32/www/com/max80.css

@@ -10,7 +10,7 @@ body {
 div.title {
     width: 100%;
     white-space: nowrap;
-    padding: 1em 1em;
+    padding: 1em;
     overflow: hidden;
 }
 div.title svg {
@@ -27,34 +27,44 @@ div.title .logo2 {
     font-size: 25px;
 }
 nav {
+    display: flex;
     border-style: solid none solid none;
     border-width: 5px;
     border-color: white;
-}
-    
-nav ul {
-    display: block;
     width: 100%;
     white-space: nowrap;
-    list-style-type: none;
-    margin: 0;
-    padding: 0;
     overflow: hidden;
+    align-items: stretch;
+
 }
-nav ul li {
-    float: left;
+nav * {
+    display: flex;
+    margin: 0;
 }
 nav a {
-    display: block;
     color: black;
     font-weight: 600;
     text-align: center;
-    padding: 1em 1.5em;
     text-decoration: none;
+    padding-left: 1.5em;
+    padding-right: 1.5em;
+    align-items: center;
 }
 nav a:hover {
     background: #af9365;
 }
+nav .pad {
+    flex-grow: 1;
+}
+nav .text {
+    padding-top: 1em;
+    padding-bottom: 1em;
+}
+nav img {
+    align-items: center;
+    border: 1px solid black;
+    object-fit: none;
+}
 form fieldset {
     border: 5px solid white;
     border-radius: 1em;

+ 27 - 7
esp32/www/com/max80.js

@@ -30,16 +30,28 @@ function fetchconfig(url) {
 // Initialize a form from a map
 function initform(form,map) {
     var button = null;
+    var clearers = new Map;
+    form = getelem(form);
 
-    for (const field of getelem(form).elements) {
-	if (field instanceof HTMLInputElement) {
+    for (var field of form.elements) {
+	if (field instanceof HTMLInputElement &&
+	    !field.classList.contains("noload")) {
 	    var val = map.get(field.name);
-	    if (!val)
+	    if (val == undefined)
 		val = '';
 	    if (field.type == 'checkbox') {
-		field.checked = !val.match(/^(0*|[fn].*|of.*)$/i);
+		const checked = !val.match(/^(0*|[fn].*|of.*)$/i);
+		field.checked = checked;
+		if (checked) {
+		    const clearer_name = '-' + field.name;
+		    if (!clearers.has(clearer_name)) {
+			clearers.set(clearer_name, 1);
+		    }
+		}
 	    } else if (field.type == 'radio') {
 		field.checked = (val == field.name);
+	    } else if (field.type == 'hidden') {
+		clearers.set(field.name, 0);
 	    } else {
 		field.value = val;
 	    }
@@ -49,6 +61,15 @@ function initform(form,map) {
 	}
     }
 
+    for (const [what, need] of clearers) {
+	if (need) {
+	    var clearer = document.createElement('INPUT');
+	    clearer.type = 'hidden';
+	    clearer.name = what;
+	    form.prepend(clearer);
+	}
+    }
+
     if (button) {
 	button.disabled = false; // All loaded, enable the submit button
     }
@@ -103,7 +124,7 @@ function uploadfile(event) {
 	    }
 	});
     }
-    
+
     var result = form.querySelector("pre.result");
     if (result != undefined) {
 	result.className = "result hide";
@@ -118,7 +139,7 @@ function uploadfile(event) {
 	    result.innerText = xhr.responseText;
 	}
     });
-    
+
     xhr.open("POST", form.action);
     xhr.responseType = 'text';
     xhr.send(file);
@@ -149,4 +170,3 @@ function inc(url) {
 	.then((response) => response.text())
 	.then((text) => { me.outerHTML = text; });
 }
-

+ 3 - 5
esp32/www/en/config.html

@@ -34,12 +34,10 @@
 	<legend>Date and Time</legend>
 	<label for="TZ">Timezone configuration:</label>
 	<input type="text" id="TZ" name="TZ" size="64"><br />
-	<input type="hidden" name="-sntp" />
-	<label for="sntp">Synchronize time from network:</label>
-	<input type="checkbox" id="sntp" name="sntp" /><br />
-	<label for="sntp.server">NTP server:</label>
+	<label for="sntp.enabled">Synchronize time from network:</label>
+	<input type="checkbox" id="sntp.enabled" name="sntp.enabled" /><br />
+	<label for="sntp.enabled">NTP server:</label>
 	<input type="text" id="sntp.server" name="sntp.server" size="64"><br />
-	<input type="hidden" name="-ip4.dhcp.nosntp" />
 	<label for="ip4.dhcp.nosntp">Ignore DHCP-provided NTP server:</label>
 	<input type="checkbox" id="ip4.dhcp.nosntp" name="ip4.dhcp.nosntp" />
       </fieldset>

+ 6 - 5
esp32/www/en/head.html

@@ -19,9 +19,10 @@
 </div>
 
 <nav class="navbar" role="navigation">
-  <ul>
-    <li><a href="index.html">Status</a></li>
-    <li><a href="config.html">Configuration</a></li>
-    <li><a href="update.html">Update</a></li>
-  </ul>
+  <a href="index.html" class="text">Status</a>
+  <a href="config.html" class="text">Configuration</a>
+  <a href="update.html" class="text">Update</a>
+  <span class="pad"></span>
+  <a href="../sys/lang?sv"><img type="image" src="../flag/sv.png" width="51" height="32" alt="Svenska" /></a>
+  <a href="../sys/lang?en"><img type="image" src="../flag/en.png" width="64" height="32" alt="English" /></a>
 </nav>

BIN
fpga/output/v1.fw


BIN
fpga/output/v2.fw