From 0278ad0a6d4bc49bf6343d5e61b695cf57601c53 Mon Sep 17 00:00:00 2001
From: Hadrien Jouet <hadrien.jouet@gmail.com>
Date: Tue, 12 Mar 2019 22:48:08 -0700
Subject: [PATCH] Add ESP32 WiFi interface (#11209)

---
 .travis.yml                                   |   1 +
 Marlin/Configuration_adv.h                    |   2 +
 Marlin/src/HAL/HAL_ESP32/HAL.cpp              |  20 +-
 Marlin/src/HAL/HAL_ESP32/HAL.h                |   5 +-
 Marlin/src/HAL/HAL_ESP32/WebSocketSerial.cpp  | 232 ++++++++++++++++++
 Marlin/src/HAL/HAL_ESP32/WebSocketSerial.h    |  99 ++++++++
 Marlin/src/HAL/HAL_ESP32/ota.cpp              |  13 +-
 Marlin/src/HAL/HAL_ESP32/web.cpp              |  49 ++++
 Marlin/src/HAL/HAL_ESP32/web.h                |  21 ++
 Marlin/src/HAL/HAL_ESP32/wifi.cpp             |  55 +++++
 Marlin/src/HAL/HAL_ESP32/wifi.h               |  27 ++
 .../src/HAL/HAL_LINUX/hardware/LinearAxis.cpp |   2 +-
 Marlin/src/core/serial.h                      |   6 +-
 Marlin/src/inc/SanityCheck.h                  |   2 +-
 buildroot/share/tests/esp32-tests             |  19 ++
 config/default/Configuration_adv.h            |   2 +
 .../3DFabXYZ/Migbot/Configuration_adv.h       |   2 +
 .../AlephObjects/TAZ4/Configuration_adv.h     |   2 +
 .../AliExpress/UM2pExt/Configuration_adv.h    |   2 +
 config/examples/Anet/A2/Configuration_adv.h   |   2 +
 .../examples/Anet/A2plus/Configuration_adv.h  |   2 +
 config/examples/Anet/A6/Configuration_adv.h   |   2 +
 config/examples/Anet/A8/Configuration_adv.h   |   2 +
 .../examples/AnyCubic/i3/Configuration_adv.h  |   2 +
 config/examples/ArmEd/Configuration_adv.h     |   2 +
 .../BIBO/TouchX/cyclops/Configuration_adv.h   |   2 +
 .../BIBO/TouchX/default/Configuration_adv.h   |   2 +
 .../examples/BQ/Hephestos/Configuration_adv.h |   2 +
 .../BQ/Hephestos_2/Configuration_adv.h        |   2 +
 config/examples/BQ/WITBOX/Configuration_adv.h |   2 +
 config/examples/Cartesio/Configuration_adv.h  |   2 +
 .../Creality/CR-10/Configuration_adv.h        |   2 +
 .../Creality/CR-10S/Configuration_adv.h       |   2 +
 .../Creality/CR-10_5S/Configuration_adv.h     |   2 +
 .../Creality/CR-10mini/Configuration_adv.h    |   2 +
 .../Creality/CR-8/Configuration_adv.h         |   2 +
 .../Creality/Ender-2/Configuration_adv.h      |   2 +
 .../Creality/Ender-3/Configuration_adv.h      |   2 +
 .../Creality/Ender-4/Configuration_adv.h      |   2 +
 .../examples/Einstart-S/Configuration_adv.h   |   2 +
 config/examples/Felix/Configuration_adv.h     |   2 +
 .../FlashForge/CreatorPro/Configuration_adv.h |   2 +
 .../FolgerTech/i3-2020/Configuration_adv.h    |   2 +
 .../Formbot/Raptor/Configuration_adv.h        |   2 +
 .../Formbot/T_Rex_2+/Configuration_adv.h      |   2 +
 .../Formbot/T_Rex_3/Configuration_adv.h       |   2 +
 .../Geeetech/A10M/Configuration_adv.h         |   2 +
 .../Geeetech/A20M/Configuration_adv.h         |   2 +
 .../Geeetech/MeCreator2/Configuration_adv.h   |   2 +
 .../Prusa i3 Pro C/Configuration_adv.h        |   2 +
 .../Prusa i3 Pro W/Configuration_adv.h        |   2 +
 .../Infitary/i3-M508/Configuration_adv.h      |   2 +
 .../examples/JGAurora/A5/Configuration_adv.h  |   2 +
 .../examples/MakerParts/Configuration_adv.h   |   2 +
 .../examples/Malyan/M150/Configuration_adv.h  |   2 +
 .../examples/Malyan/M200/Configuration_adv.h  |   2 +
 .../Micromake/C1/enhanced/Configuration_adv.h |   2 +
 config/examples/Mks/Robin/Configuration_adv.h |   2 +
 config/examples/Mks/Sbase/Configuration_adv.h |   2 +
 .../RapideLite/RL200/Configuration_adv.h      |   2 +
 config/examples/RigidBot/Configuration_adv.h  |   2 +
 config/examples/SCARA/Configuration_adv.h     |   2 +
 .../examples/Sanguinololu/Configuration_adv.h |   2 +
 config/examples/TheBorg/Configuration_adv.h   |   2 +
 config/examples/TinyBoy2/Configuration_adv.h  |   2 +
 .../examples/Tronxy/X3A/Configuration_adv.h   |   2 +
 .../Tronxy/X5S-2E/Configuration_adv.h         |   2 +
 .../UltiMachine/Archim1/Configuration_adv.h   |   2 +
 .../UltiMachine/Archim2/Configuration_adv.h   |   2 +
 .../examples/VORONDesign/Configuration_adv.h  |   2 +
 .../Velleman/K8200/Configuration_adv.h        |   2 +
 .../Velleman/K8400/Configuration_adv.h        |   2 +
 .../WASP/PowerWASP/Configuration_adv.h        |   2 +
 .../Wanhao/Duplicator 6/Configuration_adv.h   |   2 +
 .../delta/Anycubic/Kossel/Configuration_adv.h |   2 +
 .../FLSUN/auto_calibrate/Configuration_adv.h  |   2 +
 .../delta/FLSUN/kossel/Configuration_adv.h    |   2 +
 .../FLSUN/kossel_mini/Configuration_adv.h     |   2 +
 .../Geeetech/Rostock 301/Configuration_adv.h  |   2 +
 .../delta/MKS/SBASE/Configuration_adv.h       |   2 +
 .../Tevo Little Monster/Configuration_adv.h   |   2 +
 .../delta/generic/Configuration_adv.h         |   2 +
 .../delta/kossel_mini/Configuration_adv.h     |   2 +
 .../delta/kossel_xl/Configuration_adv.h       |   2 +
 .../gCreate/gMax1.5+/Configuration_adv.h      |   2 +
 config/examples/makibox/Configuration_adv.h   |   2 +
 .../tvrrug/Round2/Configuration_adv.h         |   2 +
 config/examples/wt150/Configuration_adv.h     |   2 +
 data/www/index.html                           |  37 +++
 data/www/marlin-logo.png                      | Bin 0 -> 2349 bytes
 data/www/marlin.css                           | 166 +++++++++++++
 data/www/marlin.js                            |  24 ++
 platformio.ini                                |  11 +-
 93 files changed, 915 insertions(+), 22 deletions(-)
 create mode 100644 Marlin/src/HAL/HAL_ESP32/WebSocketSerial.cpp
 create mode 100644 Marlin/src/HAL/HAL_ESP32/WebSocketSerial.h
 create mode 100644 Marlin/src/HAL/HAL_ESP32/web.cpp
 create mode 100644 Marlin/src/HAL/HAL_ESP32/web.h
 create mode 100644 Marlin/src/HAL/HAL_ESP32/wifi.cpp
 create mode 100644 Marlin/src/HAL/HAL_ESP32/wifi.h
 create mode 100755 buildroot/share/tests/esp32-tests
 create mode 100644 data/www/index.html
 create mode 100644 data/www/marlin-logo.png
 create mode 100644 data/www/marlin.css
 create mode 100644 data/www/marlin.js

diff --git a/.travis.yml b/.travis.yml
index 5b22fc2b3c..dec966c923 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,6 +23,7 @@ env:
   #- TEST_PLATFORM="STM32F1"
   - TEST_PLATFORM="teensy35"
   - TEST_PLATFORM="linux_native"
+  - TEST_PLATFORM="esp32"
 
 addons:
   apt:
diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 0f1b0218c9..c74153409f 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/Marlin/src/HAL/HAL_ESP32/HAL.cpp b/Marlin/src/HAL/HAL_ESP32/HAL.cpp
index 1ef2798553..76b961b7b9 100644
--- a/Marlin/src/HAL/HAL_ESP32/HAL.cpp
+++ b/Marlin/src/HAL/HAL_ESP32/HAL.cpp
@@ -34,7 +34,14 @@
 #include "../../inc/MarlinConfigPre.h"
 
 #if ENABLED(WIFISUPPORT)
-  #include "ota.h"
+  #include <ESPAsyncWebServer.h>
+  #include "wifi.h"
+  #if ENABLED(OTASUPPORT)
+    #include "ota.h"
+  #endif
+  #if ENABLED(WEBSUPPORT)
+    #include "web.h"
+  #endif
 #endif
 
 // --------------------------------------------------------------------------
@@ -83,14 +90,21 @@ esp_adc_cal_characteristics_t characteristics;
 
 void HAL_init(void) {
   #if ENABLED(WIFISUPPORT)
-    OTA_init();
+    wifi_init();
+    #if ENABLED(OTASUPPORT)
+      OTA_init();
+    #endif
+    #if ENABLED(WEBSUPPORT)
+      web_init();
+    #endif
+    server.begin();
   #endif
 
   i2s_init();
 }
 
 void HAL_idletask(void) {
-  #if ENABLED(WIFISUPPORT)
+  #if ENABLED(OTASUPPORT)
     OTA_handle();
   #endif
 }
diff --git a/Marlin/src/HAL/HAL_ESP32/HAL.h b/Marlin/src/HAL/HAL_ESP32/HAL.h
index e1249c71ee..f13c3798de 100644
--- a/Marlin/src/HAL/HAL_ESP32/HAL.h
+++ b/Marlin/src/HAL/HAL_ESP32/HAL.h
@@ -47,14 +47,17 @@
 
 #include "HAL_timers_ESP32.h"
 
+#include "WebSocketSerial.h"
+
 // --------------------------------------------------------------------------
 // Defines
 // --------------------------------------------------------------------------
 
 extern portMUX_TYPE spinlock;
 
-#define NUM_SERIAL 1
+#define NUM_SERIAL 2
 #define MYSERIAL0 Serial
+#define MYSERIAL1 webSocketSerial
 
 #define CRITICAL_SECTION_START portENTER_CRITICAL(&spinlock)
 #define CRITICAL_SECTION_END   portEXIT_CRITICAL(&spinlock)
diff --git a/Marlin/src/HAL/HAL_ESP32/WebSocketSerial.cpp b/Marlin/src/HAL/HAL_ESP32/WebSocketSerial.cpp
new file mode 100644
index 0000000000..450cf68307
--- /dev/null
+++ b/Marlin/src/HAL/HAL_ESP32/WebSocketSerial.cpp
@@ -0,0 +1,232 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifdef ARDUINO_ARCH_ESP32
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(WIFISUPPORT)
+
+#include "WebSocketSerial.h"
+#include "wifi.h"
+
+#include <AsyncTCP.h>
+#include <ESPAsyncWebServer.h>
+
+struct ring_buffer_r {
+  unsigned char buffer[RX_BUFFER_SIZE];
+  volatile ring_buffer_pos_t head, tail;
+};
+
+struct ring_buffer_t {
+  unsigned char buffer[256];
+  volatile uint8_t head, tail;
+};
+
+ring_buffer_r rx_buffer = { { 0 }, 0, 0 };
+ring_buffer_t tx_buffer = { { 0 }, 0, 0 };
+
+static bool _written;
+
+#if ENABLED(EMERGENCY_PARSER)
+  static EmergencyParser::State emergency_state; // = EP_RESET
+#endif
+
+AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
+
+FORCE_INLINE int next_rx_index(const int i) { return (ring_buffer_pos_t)(i + 1) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1); }
+FORCE_INLINE int next_tx_index(const int i) { return (ring_buffer_pos_t)(i + 1) & (ring_buffer_pos_t)(TX_BUFFER_SIZE - 1); }
+
+static void addToBuffer(uint8_t * const data, const size_t len) {
+  for (size_t i = 0; i < len; i++) {
+    ring_buffer_pos_t h = rx_buffer.head;
+    const ring_buffer_pos_t t = rx_buffer.tail, n = next_rx_index(h);
+
+    if (n != t) { rx_buffer.buffer[h] = data[i]; h = n; }
+
+    // TODO: buffer is full, handle?
+
+    rx_buffer.head = h;
+  }
+}
+
+// Handle WebSocket event
+static void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
+  switch (type) {
+    case WS_EVT_CONNECT: client->ping(); break; // client connected
+    case WS_EVT_DISCONNECT:                     // client disconnected
+    case WS_EVT_ERROR:                          // error was received from the other end
+    case WS_EVT_PONG: break;                    // pong message was received (in response to a ping request maybe)
+    case WS_EVT_DATA: {                         // data packet
+      AwsFrameInfo * info = (AwsFrameInfo*)arg;
+      if (info->opcode == WS_TEXT || info->message_opcode == WS_TEXT)
+        addToBuffer(data, len);
+    }
+  }
+}
+
+// Public Methods
+void WebSocketSerial::begin(const long baud_setting) {
+  ws.onEvent(onEvent);
+  server.addHandler(&ws); // attach AsyncWebSocket
+}
+
+void WebSocketSerial::end() { }
+
+int WebSocketSerial::peek(void) {
+  const int v = rx_buffer.head == rx_buffer.tail ? -1 : rx_buffer.buffer[rx_buffer.tail];
+  return v;
+}
+
+int WebSocketSerial::read(void) {
+  const ring_buffer_pos_t h = rx_buffer.head, t = rx_buffer.tail;
+  if (h == t) return -1;  // Nothing to read? Return now
+
+  const int v = rx_buffer.buffer[t];
+
+  rx_buffer.tail = (ring_buffer_pos_t)(t + 1) & (RX_BUFFER_SIZE - 1); // Advance tail
+
+  return v;
+}
+
+bool WebSocketSerial::available(void) {
+  const ring_buffer_pos_t h = rx_buffer.head, t = rx_buffer.tail;
+  return (ring_buffer_pos_t)(RX_BUFFER_SIZE + h - t) & (RX_BUFFER_SIZE - 1);
+}
+
+void WebSocketSerial::flush(void) {
+  ws.textAll("flush");
+  rx_buffer.tail = rx_buffer.head;
+}
+
+#if TX_BUFFER_SIZE
+
+  void WebSocketSerial::write(const uint8_t c) {
+    _written = true;
+
+    const uint8_t i = (tx_buffer.head + 1) & (TX_BUFFER_SIZE - 1);
+
+    // Store new char. head is always safe to move
+    tx_buffer.buffer[tx_buffer.head] = c;
+    tx_buffer.head = i;
+
+    if (c == '\n') {
+      ws.textAll(tx_buffer.buffer, tx_buffer.head);
+      tx_buffer.head = 0;
+    }
+  }
+
+  void WebSocketSerial::flushTx(void) {
+    ws.textAll("flushTx");
+    if (!_written) return;
+  }
+
+#else
+
+ //void WebSocketSerial::write(const uint8_t c) { _written = true; }
+ //void WebSocketSerial::flushTx(void) { if (!_written) return; }
+
+#endif
+
+/**
+ * Imports from print.h
+ */
+
+void WebSocketSerial::print(char c, int base) { print((long)c, base); }
+void WebSocketSerial::print(unsigned char b, int base) { print((unsigned long)b, base); }
+void WebSocketSerial::print(int n, int base) { print((long)n, base); }
+void WebSocketSerial::print(unsigned int n, int base) { print((unsigned long)n, base); }
+void WebSocketSerial::print(long n, int base) {
+  if (base == 0)
+    write(n);
+  else if (base == 10) {
+    if (n < 0) { print('-'); n = -n; }
+    printNumber(n, 10);
+  }
+  else
+    printNumber(n, base);
+}
+
+void WebSocketSerial::print(unsigned long n, int base) {
+  if (base == 0) write(n); else printNumber(n, base);
+}
+
+void WebSocketSerial::print(double n, int digits)         { printFloat(n, digits); }
+
+void WebSocketSerial::println(void)                       { print('\r'); print('\n'); }
+void WebSocketSerial::println(const String& s)            { print(s); println(); }
+void WebSocketSerial::println(const char c[])             { print(c); println(); }
+void WebSocketSerial::println(char c, int base)           { print(c, base); println(); }
+void WebSocketSerial::println(unsigned char b, int base)  { print(b, base); println(); }
+void WebSocketSerial::println(int n, int base)            { print(n, base); println(); }
+void WebSocketSerial::println(unsigned int n, int base)   { print(n, base); println(); }
+void WebSocketSerial::println(long n, int base)           { print(n, base); println(); }
+void WebSocketSerial::println(unsigned long n, int base)  { print(n, base); println(); }
+void WebSocketSerial::println(double n, int digits)       { print(n, digits); println(); }
+
+// Private Methods
+
+void WebSocketSerial::printNumber(unsigned long n, uint8_t base) {
+  if (n) {
+    unsigned char buf[8 * sizeof(long)]; // Enough space for base 2
+    int8_t i = 0;
+    while (n) {
+      buf[i++] = n % base;
+      n /= base;
+    }
+    while (i--)
+      print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10)));
+  }
+  else
+    print('0');
+}
+
+void WebSocketSerial::printFloat(double number, uint8_t digits) {
+  // Handle negative numbers
+  if (number < 0.0) { print('-'); number = -number; }
+
+  // Round correctly so that print(1.999, 2) prints as "2.00"
+  // Use a lookup table for performance
+  constexpr double rounds[] = { 0.5, 0.05, 0.005, 0.0005, 0.00005, 0.000005, 0.0000005, 0.00000005 };
+  number += rounds[digits];
+
+  //number += pow(10, -(digits + 1)); // slower single-line equivalent
+
+  // Extract the integer part of the number and print it
+  unsigned long int_part = (unsigned long)number;
+  print(int_part);
+
+  // Print the decimal point, but only if there are digits beyond
+  double remainder = number - (double)int_part;
+  if (digits) {
+    print('.');
+    // Extract digits from the remainder one at a time
+    while (digits--) {
+      remainder *= 10.0;
+      const int toPrint = int(remainder);
+      print(toPrint);
+      remainder -= toPrint;
+    }
+  }
+}
+
+#endif // WIFISUPPORT
+#endif // ARDUINO_ARCH_ESP32
diff --git a/Marlin/src/HAL/HAL_ESP32/WebSocketSerial.h b/Marlin/src/HAL/HAL_ESP32/WebSocketSerial.h
new file mode 100644
index 0000000000..43c8044107
--- /dev/null
+++ b/Marlin/src/HAL/HAL_ESP32/WebSocketSerial.h
@@ -0,0 +1,99 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../../inc/MarlinConfig.h"
+
+#include <WString.h>
+
+#define DEC 10
+#define HEX 16
+#define OCT 8
+#define BIN 2
+
+#ifndef RX_BUFFER_SIZE
+  #define RX_BUFFER_SIZE 128
+#endif
+#ifndef TX_BUFFER_SIZE
+  #define TX_BUFFER_SIZE 32
+#endif
+#if TX_BUFFER_SIZE <= 0
+  #error "TX_BUFFER_SIZE is required for the WebSocket."
+#endif
+
+#if RX_BUFFER_SIZE > 256
+  typedef uint16_t ring_buffer_pos_t;
+#else
+  typedef uint8_t ring_buffer_pos_t;
+#endif
+
+class WebSocketSerial {
+public:
+  WebSocketSerial() {};
+  static void begin(const long);
+  static void end();
+  static int peek(void);
+  static int read(void);
+  static void flush(void);
+  static void flushTx(void);
+  static bool available(void);
+  static void write(const uint8_t c);
+
+  #if ENABLED(SERIAL_STATS_DROPPED_RX)
+    FORCE_INLINE static uint32_t dropped() { return 0; }
+  #endif
+
+  #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
+    FORCE_INLINE static int rxMaxEnqueued() { return 0; }
+  #endif
+
+  FORCE_INLINE static void write(const char* str) { while (*str) write(*str++); }
+  FORCE_INLINE static void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); }
+  FORCE_INLINE static void print(const String& s) { for (int i = 0; i < (int)s.length(); i++) write(s[i]); }
+  FORCE_INLINE static void print(const char* str) { write(str); }
+
+  static void print(char, int = 0);
+  static void print(unsigned char, int = 0);
+  static void print(int, int = DEC);
+  static void print(unsigned int, int = DEC);
+  static void print(long, int = DEC);
+  static void print(unsigned long, int = DEC);
+  static void print(double, int = 2);
+
+  static void println(const String& s);
+  static void println(const char[]);
+  static void println(char, int = 0);
+  static void println(unsigned char, int = 0);
+  static void println(int, int = DEC);
+  static void println(unsigned int, int = DEC);
+  static void println(long, int = DEC);
+  static void println(unsigned long, int = DEC);
+  static void println(double, int = 2);
+  static void println(void);
+  operator bool() { return true; }
+
+private:
+  static void printNumber(unsigned long, const uint8_t);
+  static void printFloat(double, uint8_t);
+};
+
+extern WebSocketSerial webSocketSerial;
diff --git a/Marlin/src/HAL/HAL_ESP32/ota.cpp b/Marlin/src/HAL/HAL_ESP32/ota.cpp
index 52129ac92b..40ec1ab875 100644
--- a/Marlin/src/HAL/HAL_ESP32/ota.cpp
+++ b/Marlin/src/HAL/HAL_ESP32/ota.cpp
@@ -21,7 +21,7 @@
 
 #include "../../inc/MarlinConfigPre.h"
 
-#if ENABLED(WIFISUPPORT)
+#if ENABLED(OTASUPPORT)
 
 #include <WiFi.h>
 #include <ESPmDNS.h>
@@ -30,15 +30,6 @@
 #include "driver/timer.h"
 
 void OTA_init() {
-  WiFi.mode(WIFI_STA);
-  WiFi.begin(WIFI_SSID, WIFI_PWD);
-
-  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
-    Serial.println("Connection Failed! Rebooting...");
-    delay(5000);
-    ESP.restart();
-  }
-
   ArduinoOTA
     .onStart([]() {
       timer_pause(TIMER_GROUP_0, TIMER_0);
@@ -76,6 +67,6 @@ void OTA_handle() {
   ArduinoOTA.handle();
 }
 
-#endif // WIFISUPPORT
+#endif // OTASUPPORT
 
 #endif // ARDUINO_ARCH_ESP32
diff --git a/Marlin/src/HAL/HAL_ESP32/web.cpp b/Marlin/src/HAL/HAL_ESP32/web.cpp
new file mode 100644
index 0000000000..a3a6cce729
--- /dev/null
+++ b/Marlin/src/HAL/HAL_ESP32/web.cpp
@@ -0,0 +1,49 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef ARDUINO_ARCH_ESP32
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(WEBSUPPORT)
+
+#include "../../core/serial.h"
+
+#include "FS.h"
+#include "SPIFFS.h"
+#include "wifi.h"
+
+AsyncEventSource events("/events"); // event source (Server-Sent events)
+
+void onNotFound(AsyncWebServerRequest *request){
+  request->send(404);
+}
+
+void web_init() {
+  server.addHandler(&events);       // attach AsyncEventSource
+  if (SPIFFS.begin()) {
+    server.serveStatic("/", SPIFFS, "/www").setDefaultFile("index.html");
+    server.onNotFound(onNotFound);
+  }
+  else
+    SERIAL_ECHO_MSG("SPIFFS Mount Failed");
+}
+
+#endif // WEBSUPPORT
+#endif // ARDUINO_ARCH_ESP32
diff --git a/Marlin/src/HAL/HAL_ESP32/web.h b/Marlin/src/HAL/HAL_ESP32/web.h
new file mode 100644
index 0000000000..7f8796e7d4
--- /dev/null
+++ b/Marlin/src/HAL/HAL_ESP32/web.h
@@ -0,0 +1,21 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+void web_init();
diff --git a/Marlin/src/HAL/HAL_ESP32/wifi.cpp b/Marlin/src/HAL/HAL_ESP32/wifi.cpp
new file mode 100644
index 0000000000..7afc7a87a8
--- /dev/null
+++ b/Marlin/src/HAL/HAL_ESP32/wifi.cpp
@@ -0,0 +1,55 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef ARDUINO_ARCH_ESP32
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(WIFISUPPORT)
+
+#include <WiFi.h>
+#include <ESPmDNS.h>
+#include <ESPAsyncWebServer.h>
+#include "wifi.h"
+
+AsyncWebServer server(80);
+
+#ifndef WIFI_HOSTNAME
+  #define WIFI_HOSTNAME DEFAULT_WIFI_HOSTNAME
+#endif
+
+void wifi_init() {
+  WiFi.mode(WIFI_STA);
+  WiFi.begin(WIFI_SSID, WIFI_PWD);
+
+  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
+    delay(5000);
+    ESP.restart();
+  }
+
+  delay(10);
+
+  // Loop forever (watchdog kill) on failure
+  if (!MDNS.begin(WIFI_HOSTNAME)) for(;;) delay(5000);
+
+  MDNS.addService("http", "tcp", 80);
+}
+
+#endif // WIFISUPPORT
+#endif // ARDUINO_ARCH_ESP32
diff --git a/Marlin/src/HAL/HAL_ESP32/wifi.h b/Marlin/src/HAL/HAL_ESP32/wifi.h
new file mode 100644
index 0000000000..ef35cf14ca
--- /dev/null
+++ b/Marlin/src/HAL/HAL_ESP32/wifi.h
@@ -0,0 +1,27 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <ESPAsyncWebServer.h>
+
+extern AsyncWebServer server;
+
+#define DEFAULT_WIFI_HOSTNAME "marlin"
+
+void wifi_init();
diff --git a/Marlin/src/HAL/HAL_LINUX/hardware/LinearAxis.cpp b/Marlin/src/HAL/HAL_LINUX/hardware/LinearAxis.cpp
index 2682f8be0c..c50390d736 100644
--- a/Marlin/src/HAL/HAL_LINUX/hardware/LinearAxis.cpp
+++ b/Marlin/src/HAL/HAL_LINUX/hardware/LinearAxis.cpp
@@ -58,7 +58,7 @@ void LinearAxis::interrupt(GpioEvent ev) {
       position += -1 + 2 * Gpio::pin_map[dir_pin].value;
       Gpio::pin_map[min_pin].value = (position < min_position);
       //Gpio::pin_map[max_pin].value = (position > max_position);
-      //if(position < min_position) printf("axis(%d) endstop : pos: %d, mm: %f, min: %d\n", step_pin, position, position / 80.0, Gpio::pin_map[min_pin].value);
+      //if (position < min_position) printf("axis(%d) endstop : pos: %d, mm: %f, min: %d\n", step_pin, position, position / 80.0, Gpio::pin_map[min_pin].value);
     }
   }
 }
diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h
index b9af72aea5..a0f3cb36a6 100644
--- a/Marlin/src/core/serial.h
+++ b/Marlin/src/core/serial.h
@@ -49,13 +49,13 @@ extern uint8_t marlin_debug_flags;
   #define _PORT_REDIRECT(n,p)   REMEMBER(n,serial_port_index,p)
   #define _PORT_RESTORE(n)      RESTORE(n)
   #define SERIAL_OUT(WHAT, ...) do{ \
-    if (!serial_port_index || serial_port_index == SERIAL_BOTH) MYSERIAL0.WHAT(__VA_ARGS__); \
-    if ( serial_port_index) MYSERIAL1.WHAT(__VA_ARGS__); \
+    if (!serial_port_index || serial_port_index == SERIAL_BOTH) (void)MYSERIAL0.WHAT(__VA_ARGS__); \
+    if ( serial_port_index) (void)MYSERIAL1.WHAT(__VA_ARGS__); \
   }while(0)
 #else
   #define _PORT_REDIRECT(n,p)   NOOP
   #define _PORT_RESTORE(n)      NOOP
-  #define SERIAL_OUT(WHAT, ...) MYSERIAL0.WHAT(__VA_ARGS__)
+  #define SERIAL_OUT(WHAT, ...) (void)MYSERIAL0.WHAT(__VA_ARGS__)
 #endif
 
 #define PORT_REDIRECT(p)        _PORT_REDIRECT(1,p)
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index d230c1972b..dce3bed907 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -393,7 +393,7 @@
   #elif RX_BUFFER_SIZE && (RX_BUFFER_SIZE < 2 || !IS_POWER_OF_2(RX_BUFFER_SIZE))
     #error "RX_BUFFER_SIZE must be a power of 2 greater than 1."
   #elif TX_BUFFER_SIZE && (TX_BUFFER_SIZE < 2 || TX_BUFFER_SIZE > 256 || !IS_POWER_OF_2(TX_BUFFER_SIZE))
-    #error "TX_BUFFER_SIZE must be 0, a power of 2 greater than 1, and no greater than 256."
+    #error "TX_BUFFER_SIZE must be 0 or a power of 2 between 1 and 256."
   #endif
 #elif ENABLED(SERIAL_XON_XOFF) || ENABLED(SERIAL_STATS_MAX_RX_QUEUED) || ENABLED(SERIAL_STATS_DROPPED_RX)
   #error "SERIAL_XON_XOFF and SERIAL_STATS_* features not supported on USB-native AVR devices."
diff --git a/buildroot/share/tests/esp32-tests b/buildroot/share/tests/esp32-tests
new file mode 100755
index 0000000000..30c1aa6e40
--- /dev/null
+++ b/buildroot/share/tests/esp32-tests
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+#
+# Build tests for esp32
+#
+
+# exit on first failure
+set -e
+
+restore_configs
+opt_set MOTHERBOARD BOARD_ESP32
+opt_enable WIFISUPPORT
+opt_set "WIFI_SSID \"ssid\""
+opt_set "WIFI_PWD \"password\""
+opt_set TX_BUFFER_SIZE 64
+opt_add WEBSUPPORT
+exec_test $1 $2 "ESP32 with WIFISUPPORT and WEBSUPPORT"
+
+# cleanup
+restore_configs
diff --git a/config/default/Configuration_adv.h b/config/default/Configuration_adv.h
index 0f1b0218c9..c74153409f 100644
--- a/config/default/Configuration_adv.h
+++ b/config/default/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/3DFabXYZ/Migbot/Configuration_adv.h b/config/examples/3DFabXYZ/Migbot/Configuration_adv.h
index a886796061..e2f36fedd4 100644
--- a/config/examples/3DFabXYZ/Migbot/Configuration_adv.h
+++ b/config/examples/3DFabXYZ/Migbot/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/AlephObjects/TAZ4/Configuration_adv.h b/config/examples/AlephObjects/TAZ4/Configuration_adv.h
index 5f7ac359be..288e8e94d2 100644
--- a/config/examples/AlephObjects/TAZ4/Configuration_adv.h
+++ b/config/examples/AlephObjects/TAZ4/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/AliExpress/UM2pExt/Configuration_adv.h b/config/examples/AliExpress/UM2pExt/Configuration_adv.h
index a44d0ecdb6..37f4636df9 100644
--- a/config/examples/AliExpress/UM2pExt/Configuration_adv.h
+++ b/config/examples/AliExpress/UM2pExt/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Anet/A2/Configuration_adv.h b/config/examples/Anet/A2/Configuration_adv.h
index d7ba51bfb6..36000e7227 100644
--- a/config/examples/Anet/A2/Configuration_adv.h
+++ b/config/examples/Anet/A2/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Anet/A2plus/Configuration_adv.h b/config/examples/Anet/A2plus/Configuration_adv.h
index d7ba51bfb6..36000e7227 100644
--- a/config/examples/Anet/A2plus/Configuration_adv.h
+++ b/config/examples/Anet/A2plus/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Anet/A6/Configuration_adv.h b/config/examples/Anet/A6/Configuration_adv.h
index 0ec457419b..262462cfc1 100644
--- a/config/examples/Anet/A6/Configuration_adv.h
+++ b/config/examples/Anet/A6/Configuration_adv.h
@@ -2227,6 +2227,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Anet/A8/Configuration_adv.h b/config/examples/Anet/A8/Configuration_adv.h
index 6621d78893..e94c3cd5fc 100644
--- a/config/examples/Anet/A8/Configuration_adv.h
+++ b/config/examples/Anet/A8/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/AnyCubic/i3/Configuration_adv.h b/config/examples/AnyCubic/i3/Configuration_adv.h
index d6b5d1df1e..141c644333 100644
--- a/config/examples/AnyCubic/i3/Configuration_adv.h
+++ b/config/examples/AnyCubic/i3/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/ArmEd/Configuration_adv.h b/config/examples/ArmEd/Configuration_adv.h
index fd646fdea5..0a65423e84 100644
--- a/config/examples/ArmEd/Configuration_adv.h
+++ b/config/examples/ArmEd/Configuration_adv.h
@@ -2232,6 +2232,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/BIBO/TouchX/cyclops/Configuration_adv.h b/config/examples/BIBO/TouchX/cyclops/Configuration_adv.h
index c77e300f14..067c4bceb1 100644
--- a/config/examples/BIBO/TouchX/cyclops/Configuration_adv.h
+++ b/config/examples/BIBO/TouchX/cyclops/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/BIBO/TouchX/default/Configuration_adv.h b/config/examples/BIBO/TouchX/default/Configuration_adv.h
index a02c2d91c0..97f6183502 100644
--- a/config/examples/BIBO/TouchX/default/Configuration_adv.h
+++ b/config/examples/BIBO/TouchX/default/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/BQ/Hephestos/Configuration_adv.h b/config/examples/BQ/Hephestos/Configuration_adv.h
index 63a040b822..f6f2f9c68f 100644
--- a/config/examples/BQ/Hephestos/Configuration_adv.h
+++ b/config/examples/BQ/Hephestos/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/BQ/Hephestos_2/Configuration_adv.h b/config/examples/BQ/Hephestos_2/Configuration_adv.h
index a13e5210fa..5910352588 100644
--- a/config/examples/BQ/Hephestos_2/Configuration_adv.h
+++ b/config/examples/BQ/Hephestos_2/Configuration_adv.h
@@ -2236,6 +2236,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/BQ/WITBOX/Configuration_adv.h b/config/examples/BQ/WITBOX/Configuration_adv.h
index 63a040b822..f6f2f9c68f 100644
--- a/config/examples/BQ/WITBOX/Configuration_adv.h
+++ b/config/examples/BQ/WITBOX/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Cartesio/Configuration_adv.h b/config/examples/Cartesio/Configuration_adv.h
index ef1ff1a40c..4fc220355c 100644
--- a/config/examples/Cartesio/Configuration_adv.h
+++ b/config/examples/Cartesio/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/CR-10/Configuration_adv.h b/config/examples/Creality/CR-10/Configuration_adv.h
index a190c0a81d..411e02ed41 100644
--- a/config/examples/Creality/CR-10/Configuration_adv.h
+++ b/config/examples/Creality/CR-10/Configuration_adv.h
@@ -2231,6 +2231,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/CR-10S/Configuration_adv.h b/config/examples/Creality/CR-10S/Configuration_adv.h
index d2b068805b..de59d8d3d3 100644
--- a/config/examples/Creality/CR-10S/Configuration_adv.h
+++ b/config/examples/Creality/CR-10S/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/CR-10_5S/Configuration_adv.h b/config/examples/Creality/CR-10_5S/Configuration_adv.h
index 25d2869cbc..345b6ae04f 100644
--- a/config/examples/Creality/CR-10_5S/Configuration_adv.h
+++ b/config/examples/Creality/CR-10_5S/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/CR-10mini/Configuration_adv.h b/config/examples/Creality/CR-10mini/Configuration_adv.h
index a5c16e925b..ab9125e10d 100644
--- a/config/examples/Creality/CR-10mini/Configuration_adv.h
+++ b/config/examples/Creality/CR-10mini/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/CR-8/Configuration_adv.h b/config/examples/Creality/CR-8/Configuration_adv.h
index de7200f3a7..d41a87ff61 100644
--- a/config/examples/Creality/CR-8/Configuration_adv.h
+++ b/config/examples/Creality/CR-8/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/Ender-2/Configuration_adv.h b/config/examples/Creality/Ender-2/Configuration_adv.h
index 8e8bbd8e07..3141520b0d 100644
--- a/config/examples/Creality/Ender-2/Configuration_adv.h
+++ b/config/examples/Creality/Ender-2/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/Ender-3/Configuration_adv.h b/config/examples/Creality/Ender-3/Configuration_adv.h
index 41f1f44fd3..d819b8b57e 100644
--- a/config/examples/Creality/Ender-3/Configuration_adv.h
+++ b/config/examples/Creality/Ender-3/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Creality/Ender-4/Configuration_adv.h b/config/examples/Creality/Ender-4/Configuration_adv.h
index cca349c029..6e257a71f2 100644
--- a/config/examples/Creality/Ender-4/Configuration_adv.h
+++ b/config/examples/Creality/Ender-4/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Einstart-S/Configuration_adv.h b/config/examples/Einstart-S/Configuration_adv.h
index a633e75e6d..9dd093d87a 100644
--- a/config/examples/Einstart-S/Configuration_adv.h
+++ b/config/examples/Einstart-S/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Felix/Configuration_adv.h b/config/examples/Felix/Configuration_adv.h
index 1e3ac38f5f..a1cad99156 100644
--- a/config/examples/Felix/Configuration_adv.h
+++ b/config/examples/Felix/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/FlashForge/CreatorPro/Configuration_adv.h b/config/examples/FlashForge/CreatorPro/Configuration_adv.h
index 9297e692b8..bd7a02c398 100644
--- a/config/examples/FlashForge/CreatorPro/Configuration_adv.h
+++ b/config/examples/FlashForge/CreatorPro/Configuration_adv.h
@@ -2227,6 +2227,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/FolgerTech/i3-2020/Configuration_adv.h b/config/examples/FolgerTech/i3-2020/Configuration_adv.h
index 09a38fccc6..c5784ac4ef 100644
--- a/config/examples/FolgerTech/i3-2020/Configuration_adv.h
+++ b/config/examples/FolgerTech/i3-2020/Configuration_adv.h
@@ -2236,6 +2236,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Formbot/Raptor/Configuration_adv.h b/config/examples/Formbot/Raptor/Configuration_adv.h
index 7be041e402..bca10833bd 100644
--- a/config/examples/Formbot/Raptor/Configuration_adv.h
+++ b/config/examples/Formbot/Raptor/Configuration_adv.h
@@ -2232,6 +2232,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Formbot/T_Rex_2+/Configuration_adv.h b/config/examples/Formbot/T_Rex_2+/Configuration_adv.h
index ecb295d107..ad77482931 100644
--- a/config/examples/Formbot/T_Rex_2+/Configuration_adv.h
+++ b/config/examples/Formbot/T_Rex_2+/Configuration_adv.h
@@ -2242,6 +2242,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Formbot/T_Rex_3/Configuration_adv.h b/config/examples/Formbot/T_Rex_3/Configuration_adv.h
index 21c509397e..42bacf17e8 100644
--- a/config/examples/Formbot/T_Rex_3/Configuration_adv.h
+++ b/config/examples/Formbot/T_Rex_3/Configuration_adv.h
@@ -2238,6 +2238,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Geeetech/A10M/Configuration_adv.h b/config/examples/Geeetech/A10M/Configuration_adv.h
index b05466d327..8d2bc17db9 100644
--- a/config/examples/Geeetech/A10M/Configuration_adv.h
+++ b/config/examples/Geeetech/A10M/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Geeetech/A20M/Configuration_adv.h b/config/examples/Geeetech/A20M/Configuration_adv.h
index 162132edac..e513edd0b0 100644
--- a/config/examples/Geeetech/A20M/Configuration_adv.h
+++ b/config/examples/Geeetech/A20M/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Geeetech/MeCreator2/Configuration_adv.h b/config/examples/Geeetech/MeCreator2/Configuration_adv.h
index 8c971ed797..e43430f347 100644
--- a/config/examples/Geeetech/MeCreator2/Configuration_adv.h
+++ b/config/examples/Geeetech/MeCreator2/Configuration_adv.h
@@ -2213,6 +2213,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Geeetech/Prusa i3 Pro C/Configuration_adv.h b/config/examples/Geeetech/Prusa i3 Pro C/Configuration_adv.h
index cbdea1140f..a637a609bb 100644
--- a/config/examples/Geeetech/Prusa i3 Pro C/Configuration_adv.h	
+++ b/config/examples/Geeetech/Prusa i3 Pro C/Configuration_adv.h	
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Geeetech/Prusa i3 Pro W/Configuration_adv.h b/config/examples/Geeetech/Prusa i3 Pro W/Configuration_adv.h
index cbdea1140f..a637a609bb 100644
--- a/config/examples/Geeetech/Prusa i3 Pro W/Configuration_adv.h	
+++ b/config/examples/Geeetech/Prusa i3 Pro W/Configuration_adv.h	
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Infitary/i3-M508/Configuration_adv.h b/config/examples/Infitary/i3-M508/Configuration_adv.h
index 98cc997763..561f59b3c8 100644
--- a/config/examples/Infitary/i3-M508/Configuration_adv.h
+++ b/config/examples/Infitary/i3-M508/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/JGAurora/A5/Configuration_adv.h b/config/examples/JGAurora/A5/Configuration_adv.h
index 47b89b5b90..fbcb390632 100644
--- a/config/examples/JGAurora/A5/Configuration_adv.h
+++ b/config/examples/JGAurora/A5/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/MakerParts/Configuration_adv.h b/config/examples/MakerParts/Configuration_adv.h
index 0af872388c..1c7dcd945c 100644
--- a/config/examples/MakerParts/Configuration_adv.h
+++ b/config/examples/MakerParts/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Malyan/M150/Configuration_adv.h b/config/examples/Malyan/M150/Configuration_adv.h
index fb39d0decf..7d50523b9f 100644
--- a/config/examples/Malyan/M150/Configuration_adv.h
+++ b/config/examples/Malyan/M150/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Malyan/M200/Configuration_adv.h b/config/examples/Malyan/M200/Configuration_adv.h
index f54d733baa..aaf83a2b97 100644
--- a/config/examples/Malyan/M200/Configuration_adv.h
+++ b/config/examples/Malyan/M200/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Micromake/C1/enhanced/Configuration_adv.h b/config/examples/Micromake/C1/enhanced/Configuration_adv.h
index 0f1b0218c9..c74153409f 100644
--- a/config/examples/Micromake/C1/enhanced/Configuration_adv.h
+++ b/config/examples/Micromake/C1/enhanced/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Mks/Robin/Configuration_adv.h b/config/examples/Mks/Robin/Configuration_adv.h
index d7cfa24924..781fae34d3 100644
--- a/config/examples/Mks/Robin/Configuration_adv.h
+++ b/config/examples/Mks/Robin/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Mks/Sbase/Configuration_adv.h b/config/examples/Mks/Sbase/Configuration_adv.h
index 9d1c7dfb26..ba4c3686de 100644
--- a/config/examples/Mks/Sbase/Configuration_adv.h
+++ b/config/examples/Mks/Sbase/Configuration_adv.h
@@ -2229,6 +2229,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/RapideLite/RL200/Configuration_adv.h b/config/examples/RapideLite/RL200/Configuration_adv.h
index 79d042a786..2fa8de50b7 100644
--- a/config/examples/RapideLite/RL200/Configuration_adv.h
+++ b/config/examples/RapideLite/RL200/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/RigidBot/Configuration_adv.h b/config/examples/RigidBot/Configuration_adv.h
index 7ef8f8683c..eef286c6ec 100644
--- a/config/examples/RigidBot/Configuration_adv.h
+++ b/config/examples/RigidBot/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/SCARA/Configuration_adv.h b/config/examples/SCARA/Configuration_adv.h
index f92d49506d..31776eb3a6 100644
--- a/config/examples/SCARA/Configuration_adv.h
+++ b/config/examples/SCARA/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Sanguinololu/Configuration_adv.h b/config/examples/Sanguinololu/Configuration_adv.h
index 914d044525..ba865f2f0d 100644
--- a/config/examples/Sanguinololu/Configuration_adv.h
+++ b/config/examples/Sanguinololu/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/TheBorg/Configuration_adv.h b/config/examples/TheBorg/Configuration_adv.h
index 8cd64e3583..193ef628a6 100644
--- a/config/examples/TheBorg/Configuration_adv.h
+++ b/config/examples/TheBorg/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/TinyBoy2/Configuration_adv.h b/config/examples/TinyBoy2/Configuration_adv.h
index 34282804bf..564dd6298e 100644
--- a/config/examples/TinyBoy2/Configuration_adv.h
+++ b/config/examples/TinyBoy2/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Tronxy/X3A/Configuration_adv.h b/config/examples/Tronxy/X3A/Configuration_adv.h
index 8b96625a7e..7aaad25094 100644
--- a/config/examples/Tronxy/X3A/Configuration_adv.h
+++ b/config/examples/Tronxy/X3A/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Tronxy/X5S-2E/Configuration_adv.h b/config/examples/Tronxy/X5S-2E/Configuration_adv.h
index 149367edbd..19ecd1a768 100644
--- a/config/examples/Tronxy/X5S-2E/Configuration_adv.h
+++ b/config/examples/Tronxy/X5S-2E/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/UltiMachine/Archim1/Configuration_adv.h b/config/examples/UltiMachine/Archim1/Configuration_adv.h
index b757c6a843..4fd2b6a8ef 100644
--- a/config/examples/UltiMachine/Archim1/Configuration_adv.h
+++ b/config/examples/UltiMachine/Archim1/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/UltiMachine/Archim2/Configuration_adv.h b/config/examples/UltiMachine/Archim2/Configuration_adv.h
index 80dc0b7de3..6bc4fb64f7 100644
--- a/config/examples/UltiMachine/Archim2/Configuration_adv.h
+++ b/config/examples/UltiMachine/Archim2/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/VORONDesign/Configuration_adv.h b/config/examples/VORONDesign/Configuration_adv.h
index 26af56d027..592c08f85c 100644
--- a/config/examples/VORONDesign/Configuration_adv.h
+++ b/config/examples/VORONDesign/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Velleman/K8200/Configuration_adv.h b/config/examples/Velleman/K8200/Configuration_adv.h
index 130cdc4eaf..e8a01f9807 100644
--- a/config/examples/Velleman/K8200/Configuration_adv.h
+++ b/config/examples/Velleman/K8200/Configuration_adv.h
@@ -2241,6 +2241,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Velleman/K8400/Configuration_adv.h b/config/examples/Velleman/K8400/Configuration_adv.h
index 4856d33860..51b6a84e25 100644
--- a/config/examples/Velleman/K8400/Configuration_adv.h
+++ b/config/examples/Velleman/K8400/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/WASP/PowerWASP/Configuration_adv.h b/config/examples/WASP/PowerWASP/Configuration_adv.h
index 2df053bf85..aaf7785a4b 100644
--- a/config/examples/WASP/PowerWASP/Configuration_adv.h
+++ b/config/examples/WASP/PowerWASP/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/Wanhao/Duplicator 6/Configuration_adv.h b/config/examples/Wanhao/Duplicator 6/Configuration_adv.h
index 1bbef86e1a..ec1bc5df1a 100644
--- a/config/examples/Wanhao/Duplicator 6/Configuration_adv.h	
+++ b/config/examples/Wanhao/Duplicator 6/Configuration_adv.h	
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/Anycubic/Kossel/Configuration_adv.h b/config/examples/delta/Anycubic/Kossel/Configuration_adv.h
index 984bcc303c..f2710f7299 100644
--- a/config/examples/delta/Anycubic/Kossel/Configuration_adv.h
+++ b/config/examples/delta/Anycubic/Kossel/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/FLSUN/auto_calibrate/Configuration_adv.h b/config/examples/delta/FLSUN/auto_calibrate/Configuration_adv.h
index 1d46b4b2fa..e7edc63e6c 100644
--- a/config/examples/delta/FLSUN/auto_calibrate/Configuration_adv.h
+++ b/config/examples/delta/FLSUN/auto_calibrate/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/FLSUN/kossel/Configuration_adv.h b/config/examples/delta/FLSUN/kossel/Configuration_adv.h
index 1d46b4b2fa..e7edc63e6c 100644
--- a/config/examples/delta/FLSUN/kossel/Configuration_adv.h
+++ b/config/examples/delta/FLSUN/kossel/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/FLSUN/kossel_mini/Configuration_adv.h b/config/examples/delta/FLSUN/kossel_mini/Configuration_adv.h
index 11880ff13d..53a5a22227 100644
--- a/config/examples/delta/FLSUN/kossel_mini/Configuration_adv.h
+++ b/config/examples/delta/FLSUN/kossel_mini/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/Geeetech/Rostock 301/Configuration_adv.h b/config/examples/delta/Geeetech/Rostock 301/Configuration_adv.h
index 11880ff13d..53a5a22227 100644
--- a/config/examples/delta/Geeetech/Rostock 301/Configuration_adv.h	
+++ b/config/examples/delta/Geeetech/Rostock 301/Configuration_adv.h	
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/MKS/SBASE/Configuration_adv.h b/config/examples/delta/MKS/SBASE/Configuration_adv.h
index 631cc42075..97e1381b04 100644
--- a/config/examples/delta/MKS/SBASE/Configuration_adv.h
+++ b/config/examples/delta/MKS/SBASE/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/Tevo Little Monster/Configuration_adv.h b/config/examples/delta/Tevo Little Monster/Configuration_adv.h
index 3417cbdecd..5ca3715081 100644
--- a/config/examples/delta/Tevo Little Monster/Configuration_adv.h	
+++ b/config/examples/delta/Tevo Little Monster/Configuration_adv.h	
@@ -2218,6 +2218,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/generic/Configuration_adv.h b/config/examples/delta/generic/Configuration_adv.h
index 11880ff13d..53a5a22227 100644
--- a/config/examples/delta/generic/Configuration_adv.h
+++ b/config/examples/delta/generic/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/kossel_mini/Configuration_adv.h b/config/examples/delta/kossel_mini/Configuration_adv.h
index 45eac55ba5..2f934cb44b 100644
--- a/config/examples/delta/kossel_mini/Configuration_adv.h
+++ b/config/examples/delta/kossel_mini/Configuration_adv.h
@@ -2229,6 +2229,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/delta/kossel_xl/Configuration_adv.h b/config/examples/delta/kossel_xl/Configuration_adv.h
index 669a064114..b46014df90 100644
--- a/config/examples/delta/kossel_xl/Configuration_adv.h
+++ b/config/examples/delta/kossel_xl/Configuration_adv.h
@@ -2230,6 +2230,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/gCreate/gMax1.5+/Configuration_adv.h b/config/examples/gCreate/gMax1.5+/Configuration_adv.h
index 1fcbf1e44c..344563f448 100644
--- a/config/examples/gCreate/gMax1.5+/Configuration_adv.h
+++ b/config/examples/gCreate/gMax1.5+/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/makibox/Configuration_adv.h b/config/examples/makibox/Configuration_adv.h
index e4ba06ed46..b5d8b48271 100644
--- a/config/examples/makibox/Configuration_adv.h
+++ b/config/examples/makibox/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/tvrrug/Round2/Configuration_adv.h b/config/examples/tvrrug/Round2/Configuration_adv.h
index ca6df57f1f..18072ba322 100644
--- a/config/examples/tvrrug/Round2/Configuration_adv.h
+++ b/config/examples/tvrrug/Round2/Configuration_adv.h
@@ -2228,6 +2228,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/config/examples/wt150/Configuration_adv.h b/config/examples/wt150/Configuration_adv.h
index 81c13772ac..faaee70bf2 100644
--- a/config/examples/wt150/Configuration_adv.h
+++ b/config/examples/wt150/Configuration_adv.h
@@ -2229,6 +2229,8 @@
 #if ENABLED(WIFISUPPORT)
   #define WIFI_SSID "Wifi SSID"
   #define WIFI_PWD  "Wifi Password"
+  //#define WEBSUPPORT        // Start a webserver with auto-discovery
+  //#define OTASUPPORT        // Support over-the-air firmware updates
 #endif
 
 /**
diff --git a/data/www/index.html b/data/www/index.html
new file mode 100644
index 0000000000..d3291de8de
--- /dev/null
+++ b/data/www/index.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang=en>
+<head>
+  <meta charset=utf-8>
+  <title>Marlin</title>
+
+  <link rel="stylesheet" type="text/css" href="marlin.css" />
+
+  <script type="text/javascript" src="marlin.js"></script>
+</head>
+<body>
+  <div class="tabs">
+    <div id="logo"></div>
+    <input class="input" name="tabs" type="radio" id="tab-1" checked="checked"/>
+    <label class="label" for="tab-1">console</label>
+    <div class="panel">
+      <div class="panel-content">
+        <ul id="serial-output"></ul>
+
+        <form id="serial-command-form" autocomplete="off">
+          <div class="form-wrapper">
+            <input type="text" id="serial-command">
+            <input type="submit" value="Send">
+          </div>
+        </form>
+      </div>
+    </div>
+    <input class="input" name="tabs" type="radio" id="tab-2"/>
+    <label class="label" for="tab-2">controls</label>
+    <div class="panel">
+      <div class="panel-content">
+        #controls
+      </div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/data/www/marlin-logo.png b/data/www/marlin-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0ce790141c7ff77bead81d5ba6777692102a242
GIT binary patch
literal 2349
zcmV+|3DWk7P)<h;3K|Lk000e1NJLTq004LZ001Wl1^@s6#Jr5~00006VoOIv0RI60
z0RN!9r;`8x010qNS#tmY14;k@14;pXo;>9M000McNliru;szBG0~IEXf)W4#2)ao`
zK~#9!?VNjz9aSC2KQnvpKD*08L8RN(x^z(x5EaE#t)cPJpr(jV6a^KDKzyWHwJHXD
zRURgSQN#z0FMPls619qoU<(p0iMS|Ki;xBcyDhW}`|9rAx&ATVYflbmX3oscmR@py
z$t2y`Ij`UI`}_UQHsHxr0geaG2UY_MfQY$MRdYYxDA3u$lI9A_-kkaGMZg9L_Ito4
z;1MkvNYJxD%^EwccWG4DH#64QW~X{d^HhPefUnHgMvn*XmSDSR(NjTfz*;FySq}h9
zB;1QyJbucl0(@MavJ?SVfu8{1Zt?b@p(?Omp>NItelhT0puNS{gNi!jQK#l2;5Oi5
z;CH|+EuQ9bqf3D23YabidVw*y)-tFDY?Yu+kyRI>d$iHKU|<n&jK&@f^m&YXCNKmX
z2P_8;Yf=Fp0(=n|0d~j(UavFR)T#H23$E1-Tmp<2vET~u3}CnCVqL(?^c*VzRpmw=
z0R95p3_NI!TM4WHP5=(qJQKh}z%cMf;BQSN;85o{!gx2}2y=%5?+0Eb0bK+9KBtvW
z0GBJZTnqH%-R!u8oJG$DE|c(*G^Pgp5?Ets@5g{`8Je~gcuy0hXNLk;TW8oJ&t%d&
zfxX5z{{$8mTyp{N`2v>Al!t(y1N<V#qz?h_1HPUE?(YTGHHnau1pGYfyGvw&=66fx
zux_tH!$g?^J`&hi!2gdF7&8jIw@C$jn~FsyZID+pzZ;t4+P#{0(l-WYl?_h?&T+iI
zOYvlnwDJr~QI2qoxdXUWXmdGmniQz6ee?kr0DslfOcXeH`2et4p{lO<IW@<1o{$xX
zz=xGirP96>3my<UG_@iEt`f-V)dGhBF9iOq-@CNwVqj3Qq(iajwQ_Ctw;yRj3%*?-
z(ni}$fTentQC`EP2e?A=OSRxMI!cJyq#NWS9<h|<G2lGl4_5k92Yv&5NtGFM%uPzU
zzN-|fK`)YJ?@Tz@X~4<aU#Eb_cIES13l<gLAf$h!gfJ!xyjh;7hy^c{U_&E+9`QiY
zuLEA8RJ5w|{YT#K76Tk#q&Y84Q=GIIywaf+>oPWXy#=glzz0&kUkH55TH{s+U;+3j
z@DHVR6U3dVMu+MNfx8=l6Ah+k(Oabyk1DP;x@FT6^23_*Ss51mC*b+k+@DmQJR)VC
zkQYm$6;dYie6!lixc2yKc}r6{zou`UAS=y9pXwNJVFtslwrZ#~X=EB`xZGOfPU{ez
zz)vN_EMO4<{|3(X5bkGzT^@7RfPbm1n<U^nfRn8Gzu+h}9+R+_(*-hp!02{hl~q~$
zrYmC!Gqk+T`gz83vo4AflJz92C#)M9R``8^xCNfBa!|}YGv&JrfDfr);ZxWc(mwhP
zWNfeNI$Pv%-jj$MK7F1cWjS!Ef!b%~C9lQm%V*sls5pd|b-SaU5C-y$G2_|i6mwlM
zW;&$`w^-q1D1kw5PbfN!D)ze0v5dHtH<|GJA{Q9~Rk%75jAblg+{wTjt-8T^zz<Cs
zg&vFBf~Xg;;OPPao{?})ca-PuRru&qI@Tw~+w+_Ve9*DpYKJ@B3%pzN4a!nCI_k=6
z1biY7kmW-wfR|YB_9&BRR|xA@YGm_qjg-!$bATT=_7{@xzLk%F^Il;|vV<w%%??m9
z47^-GEHZM#D}c8+T>UAIIgS(KOnMXW11T23<0`t?sJveSnJ!7dtF50W1v;-Z-0djv
zW#Zd5SlNcF-f$N1vqlV#O19v70inBo-zlKgZM{ENDVb}IO-i|JSN&4s=c@8klePo*
zC+zJ8M@ltBd2PX?9G>Jl$9H1_a{H|J-3jxa4Mce@n0d0`$l>M}%UVpjQ^H81N#HvP
z#fAoTIpW<Y$(IJVi@AHm{Kqxk6;_Jeh_q|(H6eRnUbUwHLn;{p2?_|WUV_jOVz!&y
zBOY^&Ie=oPb#VJ!<0*>1Mee(9;E7$t%@jKvv=uC}*vdVgV|<u}lA|2LDhjgl#`RFH
zsOGT7yb2at2;609QOdb?1HW}BU*O<nwFCjz$-aW@E#NSxfXf16K0?_kcWCE}0|gLy
zTLu`)yWhYO3)>3TnO!dSpcil%7W|m1N9JLBfX}GD>+3-R>s`&W_>e|5A$J>H_ZQ$#
z#UsuhFc&=^0!~BGHxk#1&0OzH9qKxjBqtOXXA~P<+TSPT=w|<wp}Ei$aG&*V5%7BA
zHs^yWR@kMdp?Do7gt??DWHcWFj!F`62XO)aCgOWdrl!7)xYal;H$EwgTJK<NNPH)%
z-vC@E%<-HN@inA9#C7Qgy+g`441CqdsZClZG#+T(LFR`-pq7QA50r~eb3GfBcdl}{
z#FduYyfmQzajc#)X@@FZYb~#{O3!A8fa}1!I55-!%!Xn}RN};CTm25=HsP-_=9tJp
z!)4Q`a_-ww;&fKpSq3G|*$YgEa&3ouPS)dr6ss083^Ve>x+A^lwp{XE#GRi{kbJ$i
zM`MZ>8;UT08*%&eTAticY1flC-gjs4w#yS_4FJ0;0tIIf-?*^pUgDnVLQTGpxZ$2g
zo8=xVhIQ`DK%}=DOll);+&^r6cfaD=CdI9ziR-XkV&s%!VvYFktY$nJS^@q}a@Quy
zTMZL8I)0=uuv7+fpN!{9?XT`>jk{Ak+TG$oyUzD0$+t~j4QvU3R}%LkmpYgdD6VuE
z8a_^Xcc@Ea0?$%lV@$Wfgqqfw&Bz}+*xx&D+{(l=T`_4Uqnecmxi-Aqn11fe=^Y}0
zu%x)!Z7A0i?pAC?NPbO&Q9mcX5zwNi2YrM1>eT;3-yrd=`%kxc)AY1n*kGCMieI3Z
zyOGvk_%|bU3dh_-+$Rofh#e5O=q^)X;2Pqa0lV?*lGIx~o>vMI0O|noMTc`$6#5kV
z|9;FlsH#fO`M_)N`(JiK#q!`FW=_RJQOm9glu5G&2yXmmIE~ij7J1%nHZc7k4;42@
T+d1LH00000NkvXXu0mjf*M?Xq

literal 0
HcmV?d00001

diff --git a/data/www/marlin.css b/data/www/marlin.css
new file mode 100644
index 0000000000..b29ec2e24c
--- /dev/null
+++ b/data/www/marlin.css
@@ -0,0 +1,166 @@
+/* CSS reset */
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-size: 100%;
+  font: inherit;
+  vertical-align: baseline;
+  font-family: Impact, Charcoal, sans-serif;
+  }
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+  display: block;
+  }
+body { line-height: 1; }
+ol, ul { list-style: none; }
+blockquote, q { quotes: none; }
+blockquote:before, blockquote:after,
+q:before, q:after { content: ''; content: none; }
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+  }
+
+/* Custom */
+
+/* Tabs */
+* { box-sizing: border-box; }
+body {
+  display: flex;
+  justify-content: center;
+  padding: 0px;
+  background: #1e1e1e;
+  color: #efefef;
+  }
+h1 {
+  margin: 0;
+  font-size: 2em;
+  }
+.tabs {
+  display: flex;
+  width: 100%;
+  flex-wrap: wrap;
+  background: #e5e5e5;
+  }
+.input {
+  position: absolute;
+  opacity: 0;
+  }
+.label {
+  width: 100%;
+  padding: 18px 28px;
+  background: #e5e5e5;
+  cursor: pointer;
+  font-weight: bold;
+  font-size: 18px;
+  color: #7f7f7f;
+  transition: background 0.1s, color 0.1s;
+  border-style: solid;
+  border-width: 0 0 4px 0;
+  border-color: #acacac;
+  }
+.label:hover {
+  background: #d8d8d8;
+  }
+.label:active {
+  background: #ccc;
+  }
+.input:focus + .label {
+  z-index: 1;
+  }
+.input:checked + .label {
+  background: #1e1e1e;
+  color: #efefef;
+  border-width: 4px 0 0 0;
+  border-color: #65a57d;
+  }
+.panel {
+  display: none;
+  width: 100%;
+  padding: 20px 30px 30px;
+  background: #1e1e1e;
+  color: #e5e5e5;
+  }
+.panel .panel-content {
+  width: 100%;
+  max-width: 800px;
+  }
+
+@media (min-width: 600px) {
+  .label { width: auto; }
+  .panel { order: 99; }
+}
+
+.input:checked + .label + .panel { display: block; }
+
+#logo {
+  width: 130px;
+  height: 58px;
+  margin-right: 20px;
+  background: url(marlin-logo.png) no-repeat center center;
+  }
+
+input[type="text"], textarea {
+  background-color: #2c2c2c;
+  border: solid 2px #314b3b;
+  color: #e5e5e5;
+  outline: none;
+  }
+
+input[type="text"]:focus, textarea:focus {
+  border-color: #4d7a5e;
+  }
+
+ul#serial-output {
+  width: 100%;
+  height: 300px;
+  overflow-y: scroll;
+  background-color: #2c2c2c;
+  border: solid 2px #314b3b;
+  }
+
+ul#serial-output li {
+  padding: 4px;
+  font-family: "Lucida Console", Monaco, monospace;
+  font-size: 0.8em;
+  }
+
+ul#serial-output li:nth-child(odd) {
+  background-color: #3c3c3c;
+  }
+
+div.form-wrapper {
+  display: flex;
+  width: 100%;
+  margin: 6px 0;
+  }
+
+div.form-wrapper input {
+  font-size: 1.2em;
+  padding: 4px 6px;
+  }
+
+div.form-wrapper input[type="text"] {
+  flex: 1 1 auto;
+  }
+
+div.form-wrapper input[type="submit"],
+div.form-wrapper button {
+  border: solid 2px #314b3b;
+  background-color: #4d7a5e;
+  color: #e5e5e5;
+  }
diff --git a/data/www/marlin.js b/data/www/marlin.js
new file mode 100644
index 0000000000..0a95045aff
--- /dev/null
+++ b/data/www/marlin.js
@@ -0,0 +1,24 @@
+document.addEventListener('DOMContentLoaded', () => {
+  const ws = new WebSocket(`ws://${location.host}/ws`);
+
+  ws.onmessage = (e) => {
+    if (typeof e.data === 'string') {
+      let node = document.createElement('li');
+      let text = document.createTextNode(e.data);
+      node.appendChild(text);
+      document.getElementById('serial-output').appendChild(node);
+    }
+  };
+
+  document.getElementById('serial-command-form').addEventListener('submit', (e) => {
+    e.preventDefault();
+
+    let value = document.getElementById('serial-command').value.trim();
+
+    if (!value) return;
+
+    ws.send(`${value}\n`);
+
+    document.getElementById('serial-command').value = '';
+  });
+});
diff --git a/platformio.ini b/platformio.ini
index 5a1372d030..6ca0fe7125 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -286,7 +286,7 @@ src_filter    = ${common.default_src_filter} +<src/HAL/HAL_STM32F4>
 monitor_speed = 250000
 
 #
-# ARMED
+# ARMED (STM32)
 #
 [env:ARMED]
 platform    = ststm32
@@ -331,6 +331,9 @@ lib_ignore    = Adafruit NeoPixel
 src_filter    = ${common.default_src_filter} +<src/HAL/HAL_TEENSY35_36>
 monitor_speed = 250000
 
+#
+# Malyan M200 (STM32F1)
+#
 [env:malyanm200]
 platform    = ststm32
 framework   = arduino
@@ -355,11 +358,15 @@ lib_ignore  =
 # Espressif ESP32
 #
 [env:esp32]
-platform    = https://github.com/platformio/platform-espressif32.git#feature/stage
+platform    = https://github.com/platformio/platform-espressif32.git ; #feature/stage
 board       = esp32dev
 framework   = arduino
 upload_speed = 115200
 monitor_speed = 115200
+upload_port = /dev/ttyUSB0
+lib_deps =
+  https://github.com/me-no-dev/AsyncTCP.git
+  https://github.com/me-no-dev/ESPAsyncWebServer.git
 lib_ignore  =
   LiquidCrystal_I2C
   LiquidCrystal
-- 
GitLab