Browse Source

esp32, www: implement a basic status screen

H. Peter Anvin 2 years ago
parent
commit
10ac1ee0ff

+ 37 - 26
esp32/max80/config.c

@@ -6,13 +6,16 @@
 
 #define CONFIG_FILE	"/spiffs/config.txt"
 
-static const char * const default_config[] = {
-    "TZ=CET-1CEST,M3.5.0,M10.5.0/3", /* Sweden */
-    "tzname=Europe/Stockholm",
-    "hostname=max80",
-    "sntp.enabled=on",
-    "sntp.server=time.max80.abc80.org",
-    "LANG=sv"
+struct env_var {
+    const char *var, *val;
+};
+static const struct env_var default_config[] = {
+    {"LANG","sv"},
+    {"TZ", "CET-1CEST,M3.5.0,M10.5.0/3"}, /* Sweden */
+    {"tzname", "Europe/Stockholm"},
+    {"hostname", "max80"},
+    {"sntp.enabled", "1"},
+    {"sntp.server","time.max80.abc80.org"}
 };
 
 static int save_config(void);
@@ -33,25 +36,30 @@ int setenv_config(const char *name, const char *value)
 
 static int reset_config(void)
 {
-    char **new_environ = malloc((ARRAY_SIZE(default_config)+1)*sizeof(char *));
-
-    if (!new_environ)
-	return -1;
+    while (1) {
+	char **envp;
+
+	for (envp = environ; *envp; envp++) {
+	    if (!strncmp("status.", *envp, 7))
+		continue;
+	    else
+		break;
+	}
 
-    int i;
-    for (i = 0; i < ARRAY_SIZE(default_config); i++) {
-	if (!(new_environ[i] = strdup(default_config[i])))
-	    return -1;
-    }
+	if (!*envp)
+	    break;
 
-    new_environ[i] = NULL;
+	const char *eq = strchr(*envp, '=');
+	char ename[eq - *envp + 1];
+	memcpy(ename, *envp, eq - *envp);
+	ename[eq - *envp] = '\0';
 
-    char **old_environ = environ;
-    environ = new_environ;
+	unsetenv(ename);
+    }
 
-    for (char **envp = old_environ; *envp; envp++)
-	free(*envp);
-    free(old_environ);
+    size_t i;
+    for (i = 0; i < ARRAY_SIZE(default_config); i++)
+	setenv(default_config[i].var, default_config[i].val, 1);
 
     config_changed = true;
 }
@@ -167,11 +175,14 @@ exit:
     return err;
 };
 
-int write_config(FILE *f)
+int write_env(FILE *f, bool status)
 {
+    size_t skip = status ? 7 : 0;
     for (char **var = environ; *var; var++) {
-	fputs(*var, f);
-	putc('\n', f);
+	if (!strncmp(*var, "status.", 7) == status) {
+		fputs(*var + skip, f);
+		putc('\n', f);
+	}
     }
 
     return ferror(f) ? -1 : 0;
@@ -183,7 +194,7 @@ static int save_config(void)
 
     FILE *f = fopen(CONFIG_FILE, "w");
     if (f) {
-	err = write_config(f);
+	err = write_env(f, false);
 	fclose(f);
     }
 

+ 1 - 1
esp32/max80/config.h

@@ -7,7 +7,7 @@
 #define MAX_CONFIG_LINE 256
 
 extern_c int read_config(FILE *, bool save);
-extern_c int write_config(FILE *);
+extern_c int write_env(FILE *, bool status);
 extern_c void init_config(void);
 
 extern_c int set_config_url_string(const char *str);

+ 4 - 1
esp32/max80/fpgasvc.c

@@ -236,7 +236,8 @@ static void fpga_service_task(void *dummy)
     uint32_t status;
 
     printf("[FPGA] Starting FPGA services\n");
-
+    setenv_bool("status.max80.fpga", false);
+    
     /* If the FPGA is already up, need to issue our own active handshake */
     status = fpga_io_status(FPGA_CMD_IRQ(RV_IRQ_HELLO));
     printf("[FPGA] Link status bits = 0x%08x, int = %u\n",
@@ -259,6 +260,7 @@ static void fpga_service_task(void *dummy)
 		    printf("[FPGA] Ready, board = %u.%u fixes %02x fpga %u\n",
 			   head.board.major, head.board.minor,
 			   head.board.fixes, head.board.fpga);
+		    setenv_bool("status.max80.fpga", true);
 
 		    char signature_string[head.signature_len+1];
 		    fpga_io_read(0, (size_t)head.signature,
@@ -270,6 +272,7 @@ static void fpga_service_task(void *dummy)
 		    printf("[FPGA] \"%s\"\n", signature_string);
 		    ok = true;
 		}
+		setenv_bool("status.max80.fpga", ok);
 	    } else {
 		printf("[FPGA] None or invalid FPGA response, offline\n");
 	    }

+ 7 - 4
esp32/max80/httpd.c

@@ -357,7 +357,7 @@ static esp_err_t httpd_set_config(httpd_req_t *req, const char *query)
     return httpd_update_done(req, "Configuration", rv1 ? rv1 : rv2);
 }
 
-static esp_err_t httpd_get_config(httpd_req_t *req)
+static esp_err_t httpd_get_config_status(httpd_req_t *req, bool status)
 {
     FILE *f = httpd_fopen_write(req);
     if (!f)
@@ -366,11 +366,11 @@ static esp_err_t httpd_get_config(httpd_req_t *req)
     httpd_resp_set_type(req, text_plain);
     httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
 
-    int rv = write_config(f);
+    int rv = write_env(f, status);
     fclose(f);
     return rv ? ESP_FAIL : ESP_OK;
 }
-
+    
 static esp_err_t httpd_set_lang(httpd_req_t *req, const char *query)
 {
     if (query) {
@@ -428,8 +428,11 @@ static esp_err_t httpd_sys_handler(httpd_req_t *req)
     if (STRING_MATCHES(file, filelen, "lang"))
 	return httpd_lang_redirect(req);
 
+    if (STRING_MATCHES(file, filelen, "getstatus"))
+	return httpd_get_config_status(req, true);
+    
     if (STRING_MATCHES(file, filelen, "getconfig"))
-	return httpd_get_config(req);
+	return httpd_get_config_status(req, false);
 
     if (req->method == HTTP_POST && STRING_MATCHES(file, filelen, "fwupdate"))
 	return httpd_firmware_update(req);

+ 6 - 2
esp32/max80/max80.ino

@@ -18,7 +18,8 @@
 static void dump_config()
 {
     printf("--- Configuration:\n");
-    write_config(stdout);
+    write_env(stdout, false);
+    write_env(stdout, true);
     printf("--- End configuration\n");
 }
 
@@ -63,12 +64,15 @@ static void init_hw()
     heap_caps_malloc_extmem_enable(2048); // >= 2K allocations in PSRAM
 
     printf("[PCB]  MAX80 board version: %u\n", max80_board_version);
+    setenv_ul("status.max80.hw.ver", max80_board_version);
 }
 
 void setup() {
-    printf("[START] MAX80 firmware compiled on " __DATE__ " " __TIME__ "\n");
+    const char *fwdate = __DATE__ " " __TIME__;
+    printf("[START] MAX80 firmware compiled on %s\n", fwdate);
     init_hw();
     init_config();
+    setenv("status.max80.fw.date", fwdate, 1);
     SetupWiFi();
     fpga_service_start();
     printf("[RDY]\n");

+ 95 - 27
esp32/max80/wifi.cpp

@@ -50,6 +50,7 @@ static void sntp_sync_cb(struct timeval *tv)
 	sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED); // Until first sync
 	if (prev_sync_status != sync_status) {
 	    printf("[SNTP] time synchronization lost\n");
+	    setenv_bool("status.net.sntp.sync", false);
 	}
 	break;
     case SNTP_SYNC_STATUS_COMPLETED:
@@ -59,6 +60,7 @@ static void sntp_sync_cb(struct timeval *tv)
 	    const struct tm *tm = localtime(&tv->tv_sec);
 	    strftime(timebuf, sizeof timebuf, "%a %Y-%m-%d %H:%M:%S %z", tm);
 	    printf("[SNTP] Time synchronized: %s\n", timebuf);
+	    setenv_bool("status.net.sntp.sync", true);
 	}
 	break;
     default:
@@ -70,6 +72,8 @@ static void sntp_sync_cb(struct timeval *tv)
 
 static void my_sntp_start()
 {
+    setenv_bool("status.net.sntp.sync", false);
+
     if (getenv_bool("sntp.enabled")) {
 	sntp_set_time_sync_notification_cb(sntp_sync_cb);
 	sntp_setoperatingmode(SNTP_OPMODE_POLL);
@@ -112,6 +116,7 @@ static void start_services(void)
 
     dns_ip = dns_getserver(0);
     printf("[DNS]  DNS server: %s\n", inet_ntoa(*dns_ip));
+    setenv("status.net.dns.server", inet_ntoa(*dns_ip), 1);
 
     // If Arduino supported both of these at the same that would be
     // awesome, but it requires ESP-IDF reconfiguration...
@@ -125,8 +130,13 @@ static void start_services(void)
 	    }
 	}
 
-	if (!invalid_ip(sntp_ip))
-	    printf("[SNTP] Time server: %s\n", inet_ntoa(*sntp_ip));
+	if (!invalid_ip(sntp_ip)) {
+	    const char *sntp_server = inet_ntoa(*sntp_ip);
+	    printf("[SNTP] Time server: %s\n", sntp_server);
+	    setenv("status.net.sntp.server", sntp_server, 1);
+	} else {
+	    unsetenv("status.net.sntp.server");
+	}
     }
 
     /* Only run on first start */
@@ -137,11 +147,20 @@ static void start_services(void)
     }
 }
 
-static String wifi_local_ip(void)
+static const char *ip_str(const IPAddress &ip)
+{
+    static char ip_buf[4*4];
+    const IPAddress ip_none(0,0,0,0);
+    return strcpy(ip_buf, ip == ip_none ? "" : ip.toString().c_str());
+}
+
+static void setenv_ip(const char *var, const IPAddress &ip)
 {
-    return WiFi.localIP().toString();
+    setenv_config(var, ip_str(ip));
 }
 
+static bool force_conn_update;
+
 static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 {
     bool retry_sta  = false;
@@ -151,9 +170,13 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 	CON_ETH = 2,
 	CON_AP  = 4
     };
-    static int connected;
+    static unsigned int connected;
+    unsigned int prev_connected = connected;
     static int ap_clients = 0;
-
+    int prev_ap_clients = ap_clients;
+    IPAddress wifi_local_ip = WiFi.localIP();
+    const char *local_ip = ip_str(wifi_local_ip);
+    
     switch (event) {
     case ARDUINO_EVENT_WIFI_READY:
 	printf("[WIFI] Interface ready\n");
@@ -180,15 +203,19 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 	printf("[WIFI] Authentication mode of access point has changed\n");
 	break;
     case ARDUINO_EVENT_WIFI_STA_GOT_IP:
-	printf("[WIFI] Obtained IP address: %s\n", wifi_local_ip().c_str());
+    {
+	printf("[WIFI] Obtained IP address: %s\n", local_ip);
 	connected |= CON_STA;
 	is_connect = true;
 	break;
+    }
     case ARDUINO_EVENT_WIFI_STA_LOST_IP:
-	printf("[WIFI] Lost IP address and IP address is reset to 0\n");
+    {
+	printf("[WIFI] Lost IP address\n");
 	connected &= ~CON_STA;
 	retry_sta = true;
 	break;
+    }
     case ARDUINO_EVENT_WPS_ER_SUCCESS:
 	printf("[WIFI] WiFi Protected Setup (WPS): succeeded in enrollee mode\n");
 	break;
@@ -254,9 +281,10 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 	retry_sta = true;
 	break;
     case ARDUINO_EVENT_ETH_GOT_IP:
-	printf("[ETH]  Obtained IP address: %s\n", wifi_local_ip().c_str());
+	printf("[ETH]  Obtained IP address: %s\n", local_ip);
 	connected |= CON_ETH;
 	is_connect = true;
+	break;
     default:
 	break;
     }
@@ -269,23 +297,59 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 	sta_timeout_enable();
     }
 
-    if (ssid == "") {
-	// No network configured
-	led_set(LED_GREEN, connected & CON_AP ? LED_FLASH_SLOW : LED_OFF);
-    } else {
-	led_set(LED_GREEN, connected & CON_AP ? LED_FLASH_FAST : LED_ON);
+    unsigned int conn_change = force_conn_update ?
+	-1U : (connected ^ prev_connected);
+
+    if (conn_change) {
+	force_conn_update = false;
+
+	if (conn_change & CON_STA) {
+	    setenv_bool("status.net.sta.conn", connected & CON_STA);
+	    setenv_config("status.net.sta.ip4",
+			  connected & CON_STA ? local_ip : "");
+	    setenv_ip("status.net.sta.ip4.mask", WiFi.subnetMask());
+	    setenv_ip("status.net.sta.ip4.gw", WiFi.gatewayIP());
+	}
+	if (conn_change & CON_AP)
+	    setenv_bool("status.net.ap.conn", connected & CON_AP);
+	if (conn_change & CON_ETH) {
+	    setenv_bool("status.net.eth.conn", connected & CON_ETH);
+	    setenv_config("status.net.eth.ip4",
+			  connected & CON_ETH ? local_ip : "");
+	    setenv_ip("status.net.eth.ip4.mask", WiFi.subnetMask());
+	    setenv_ip("status.net.eth.ip4.gw", WiFi.gatewayIP());
+	}
+
+	if (ssid == "") {
+	    // No network configured
+	    led_set(LED_GREEN, connected & CON_AP ? LED_FLASH_SLOW : LED_OFF);
+	} else {
+	    led_set(LED_GREEN, connected & CON_AP ? LED_FLASH_FAST : LED_ON);
+	}
     }
     
-    /* Maybe need to do these in a different thread? */
     if (is_connect) {
 	start_services();
     }
+
+    if (ap_clients != prev_ap_clients)
+	setenv_ul("status.net.ap.clients", ap_clients);
+
     if (retry_sta) {
 	WiFi.disconnect();
 	WiFi.begin();
     }
 }
 
+static void setenv_mac(const char *var, const uint8_t mac[6])
+{
+    char mac_str[3*6];
+    
+    snprintf(mac_str, sizeof mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
+	     mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+    setenv(var, mac_str, 1);
+}
+
 static void wifi_config_ap(void)
 {
     /* No network configuration set */
@@ -294,38 +358,40 @@ static void wifi_config_ap(void)
     IPAddress AP_Gateway = IPAddress(0,0,0,0); // No gateway
     unsigned int channel = time(NULL) % 11;	   // Pseudo-random
     uint8_t mac[6];
+    char mac_str[6*3];
     char ap_ssid[64];
 
-    WiFi.macAddress(mac);
+    WiFi.softAPmacAddress(mac);
+    setenv_mac("status.net.ap.mac", mac);
     /* The last two bytes of the MAC */
     snprintf(ap_ssid, sizeof ap_ssid, "MAX80_%02X%02X", mac[4], mac[5]);
-
+    
     printf("[WIFI] AP SSID %s IP %s netmask %s channel %u\n",
 	       ap_ssid, AP_IP.toString(), AP_Netmask.toString(), channel+1);
+    setenv("status.net.ap.ssid", ap_ssid, 1);
+    setenv_ip("status.net.ap.ip4", AP_IP);
+    setenv_ip("status.net.ap.ip4.mask", AP_Netmask);
+    setenv_ul("status.net.ap.clients", 0);
 
-    printf("WiFi.softAP\n");
-    
     WiFi.softAP(ap_ssid, NULL, channel+1, 0, 4, true);
-
-    printf("WiFi.softAPConfig\n");
     WiFi.softAPConfig(AP_IP, AP_Gateway, AP_Netmask);
-
-    printf("WiFi.softAPsetHostname\n");
     WiFi.softAPsetHostname(ap_ssid);
 
     // Conservative setting: 20 MHz (single channel) only; this is for
     // reliability, not performance.
-    printf("esp_wifi_set_bandwidth\n");
     esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_AP, WIFI_BW_HT20);
 
-    printf("WiFi.enableAP\n");
+    // Enable unconditionally if no SSID
     WiFi.enableAP(ssid == "");
-
-    printf("wifi_config_ap done\n");
 }
 
 static void wifi_config_sta(void)
 {
+    uint8_t mac[6];
+    WiFi.macAddress(mac);
+    setenv_mac("status.net.sta.mac", mac);
+
+    setenv("status.net.sta.ssid", ssid.c_str(), 1);
     if (ssid == "") {
 	WiFi.enableSTA(false);
 	return;
@@ -349,6 +415,8 @@ static void wifi_config(void)
     hostname     = getenv("hostname");
     dnsserver    = getenv("ip4.dns");
 
+    force_conn_update = true;
+
     WiFi.persistent(false);
     WiFi.setSleep(false);
 

BIN
esp32/output/max80.ino.bin


+ 1 - 1
esp32/www/_redir

@@ -1 +1 @@
-/config.html
+/status.html

+ 12 - 1
esp32/www/lang/sv

@@ -24,4 +24,15 @@ button .hide=Göm
 .onerr .power=Slå av strömmer (koppla ur båda USB och stäng av ABC)
 .onerr .esp=Uppdatera ESP32 över USB och försök igen
 .onerr .serial=Se serieport för mer detaljerad status
-
+.wifi-sta legend=Wifi-klient
+.wifi-ap legend=Wifi-router
+.hwver span=Hårdvaruversion:
+.fwdate span=Mjukvaran kompilerad:
+.fpgaok span=FPGA aktiv:
+.net-connected span=Ansluten:
+.wifi-ap .net-connected span=Aktiv:
+.net-mac span=MAC-adress:
+.net-ip4 span=IP-adress:
+.net-ip4-mask span=Nätmask:
+.net-ip4-gw span=Router:
+.sntp-sync span=Klockan i synk med NTP:

+ 8 - 0
esp32/www/max80.css

@@ -147,3 +147,11 @@ output {
     text-weight: bold;
     text-decoration: underline;
 }
+.getstatus input {
+    font-family: inherit;
+    font-size: inherit;
+    background: inherit;
+    color: inherit;
+    border: 0;
+    opacity: 1;
+}

+ 14 - 16
esp32/www/max80.js

@@ -51,19 +51,21 @@ function cfgbool(str) {
 // Initialize a form from a map. Checkboxes take a cfgbool string;
 // if in the HTML their value is set to a false cfgbool string then
 // the checkbox is inverted logic.
-function initform(form,map) {
-    var button = null;
-    var clearers = new Set;
+function initform(form,map,ro = false) {
     form = getelem(form);
 
     for (var e of form.elements) {
-	if (e.classList.contains('noinit')) {
+	if (e.classList.contains('noinit'))
 	    continue;
-	} else if (e instanceof HTMLInputElement ||
-		   e instanceof HTMLSelectElement) {
+	if (ro && e.disabled != undefined)
+	    e.disabled = true;
+	if (e instanceof HTMLInputElement ||
+	    e instanceof HTMLSelectElement) {
 	    const val = map.get(e.name);
 	    if (val == null)
 		continue;
+	    if (ro && e.disabled != undefined)
+		e.disabled = true;
 	    if (e.type == 'checkbox') {
 		e.checked = cfgbool(val) == cfgbool(e.value);
 	    } else if (e.type == 'radio') {
@@ -71,20 +73,16 @@ function initform(form,map) {
 	    } else {
 		e.value = val;
 	    }
-	} else if (e instanceof HTMLButtonElement &&
-		   e.type == 'submit') {
-	    button = e;
+	} else if (e instanceof HTMLButtonElement) {
+	    e.disabled = ro;
 	}
     }
-    if (button) {
-	button.disabled = false; // All loaded, enable the submit button
-    }
 }
 
 // Load form initialization data
-function loadform(form, url) {
+function loadform(form,url,ro = false) {
     fetchconfig(url)
-	.then(map => { initform(form, map) })
+	.then((map) => initform(form,map,ro))
 	.catch(() => {});
 }
 
@@ -150,8 +148,8 @@ function upload(form,data) {
 
 	// Automatically reload the page after successful upload
 	const rf = parseInt(xhr.getResponseHeader('Refresh'));
-	//if (rf && ok)
-	//    setTimeout(() => window.location.reload(), rf * 1000);
+	if (rf && ok)
+	    setTimeout(() => window.location.reload(), rf * 1000);
     }, PassiveListener);
 
     xhr.open(form.method, form.action);

+ 83 - 3
esp32/www/status.html

@@ -8,8 +8,88 @@
   <body>
     <script>inc("head.html")</script>
     <h1 class="status">Status</h1>
-    <img src="wip.png" width="72" height="64" alt="WIP" />
-    <p class="notyet">(not implemented yet)</p>
-    <script>load('sys/getstatus');</script>
+    <form class="getstatus" id="getstatus">
+      <fieldset class="status-max80">
+	<legend>MAX80</legend>
+	<label class="hwver">
+	  <span>Hardware version:</span>
+	  <input type="text" name="max80.hw.ver" />
+	</label>
+	<label class="fwdate">
+	  <span>Firmware build date:</span>
+	  <input type="text" name="max80.fw.date" />
+	</label>
+	<label class="fpgaok">
+	  <span>FPGA online:</span>
+	  <input type="checkbox" name="max80.fpga" />
+	</label>
+      </fieldset>
+      <fieldset class="wifi-sta">
+	<legend>Wifi Client</legend>
+	<label class="net-connected">
+	  <span>Connected:</span>
+	  <input type="checkbox" name="net.sta.conn" />
+	</label>
+	<label class="wifi-ssid">
+	  <span>Network name (SSID):</span>
+	  <input type="text" name="net.sta.ssid" />
+	</label>
+	<label class="net-mac">
+	  <span>MAC address:</span>
+	  <input type="text" name="net.sta.mac" />
+	</label>
+	<label class="net-ip4">
+	  <span>IP address:</span>
+	  <input type="text" name="net.sta.ip4" />
+	</label>
+	<label class="net-ip4-mask">
+	  <span>Netmask:</span>
+	  <input type="text" name="net.sta.ip4.mask" />
+	</label>
+	<label class="net-ip4-gw">
+	  <span>Gateway:</span>
+	  <input type="text" name="net.sta.ip4.gw" />
+	</label>
+      </fieldset>
+      <fieldset class="wifi-ap">
+	<legend>Wifi Access Point</legend>
+	<label class="net-connected">
+	  <span>Active:</span>
+	  <input type="checkbox" name="net.ap.conn" />
+	</label>
+	<label class="wifi-ssid">
+	  <span>Network name (SSID):</span>
+	  <input type="text" name="net.ap.ssid" />
+	</label>
+	<label class="net-mac">
+	  <span>MAC address:</span>
+	  <input type="text" name="net.ap.mac" />
+	</label>
+	<label class="net-ip4">
+	  <span>IP address:</span>
+	  <input type="text" name="net.ap.ip4" />
+	</label>
+	<label class="net-ip4-mask">
+	  <span>Netmask:</span>
+	  <input type="text" name="net.ap.ip4.mask" />
+	</label>
+	<label class="net-connected-clients">
+	  <span>Connected clients:</span>
+	  <input type="text" name="net.ap.clients" />
+	</label>
+      </fieldset>
+      <fieldset class="datetime">
+	<legend>Time and Date</legend>
+	<label class="sntp-server">
+	  <span>NTP server:</span>
+	  <input type="text" name="net.sntp.server" />
+	</label>
+	<label class="sntp-sync">
+	  <span>Time synchronized:</span>
+	  <input type="checkbox" name="net.sntp.sync" />
+	</label>
+      </fieldset>
+    </form>
+    <script>loadform('getstatus','sys/getstatus',true)</script>
   </body>
 </html>

BIN
fpga/output/v1.fw


BIN
fpga/output/v2.fw