diff --git a/cores/esp32/IPAddress.cpp b/cores/esp32/IPAddress.cpp
index 0575363f254..b8f7b7aae1b 100644
--- a/cores/esp32/IPAddress.cpp
+++ b/cores/esp32/IPAddress.cpp
@@ -20,68 +20,93 @@
 #include <Arduino.h>
 #include <IPAddress.h>
 #include <Print.h>
+#include <StreamString.h>
 
-IPAddress::IPAddress()
-{
-    _address.dword = 0;
+IPAddress::IPAddress() {
+#if LWIP_IPV6
+  _ip = *IP6_ADDR_ANY;
+#else
+  _ip = *IP_ADDR_ANY;
+#endif
+    // _ip = *IP_ANY_TYPE; // lwIP's v4-or-v6 generic address
 }
 
-IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet)
+IPAddress::IPAddress(const IPAddress& from)
 {
-    _address.bytes[0] = first_octet;
-    _address.bytes[1] = second_octet;
-    _address.bytes[2] = third_octet;
-    _address.bytes[3] = fourth_octet;
+    ip_addr_copy(_ip, from._ip);
 }
 
-IPAddress::IPAddress(uint32_t address)
-{
-    _address.dword = address;
+IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
+    setV4();
+    (*this)[0] = first_octet;
+    (*this)[1] = second_octet;
+    (*this)[2] = third_octet;
+    (*this)[3] = fourth_octet;
 }
 
-IPAddress::IPAddress(const uint8_t *address)
-{
-    memcpy(_address.bytes, address, sizeof(_address.bytes));
+IPAddress::IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16) {
+    setV6();
+    (*this)[0] = o1;
+    (*this)[1] = o2;
+    (*this)[2] = o3;
+    (*this)[3] = o4;
+    (*this)[4] = o5;
+    (*this)[5] = o6;
+    (*this)[6] = o7;
+    (*this)[7] = o8;
+    (*this)[8] = o9;
+    (*this)[9] = o10;
+    (*this)[10] = o11;
+    (*this)[11] = o12;
+    (*this)[12] = o13;
+    (*this)[13] = o14;
+    (*this)[14] = o15;
+    (*this)[15] = o16;
 }
 
-IPAddress& IPAddress::operator=(const uint8_t *address)
-{
-    memcpy(_address.bytes, address, sizeof(_address.bytes));
-    return *this;
+IPAddress::IPAddress(IPType type, const uint8_t *address) {
+    if (type == IPv4) {
+        setV4();
+        memcpy(&this->_ip.u_addr.ip4, address, 4);
+    } else if (type == IPv6) {
+        setV6();
+        memcpy(&this->_ip.u_addr.ip6.addr[0], address, 16);
+    } else {
+#if LWIP_IPV6
+  _ip = *IP6_ADDR_ANY;
+#else
+  _ip = *IP_ADDR_ANY;
+#endif
+    }
+
 }
 
-IPAddress& IPAddress::operator=(uint32_t address)
-{
-    _address.dword = address;
-    return *this;
+void IPAddress::ctor32(uint32_t address) {
+    setV4();
+    v4() = address;
 }
 
-bool IPAddress::operator==(const uint8_t* addr) const
-{
-    return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
+IPAddress::IPAddress(const uint8_t *address) {
+    setV4();
+    (*this)[0] = address[0];
+    (*this)[1] = address[1];
+    (*this)[2] = address[2];
+    (*this)[3] = address[3];
 }
 
-size_t IPAddress::printTo(Print& p) const
-{
-    size_t n = 0;
-    for(int i = 0; i < 3; i++) {
-        n += p.print(_address.bytes[i], DEC);
-        n += p.print('.');
+bool IPAddress::fromString(const char *address) {
+    if (!fromString4(address)) {
+#if LWIP_IPV6
+        return fromString6(address);
+#else
+        return false;
+#endif
     }
-    n += p.print(_address.bytes[3], DEC);
-    return n;
+    return true;
 }
 
-String IPAddress::toString() const
-{
-    char szRet[16];
-    sprintf(szRet,"%u.%u.%u.%u", _address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3]);
-    return String(szRet);
-}
-
-bool IPAddress::fromString(const char *address)
-{
-    // TODO: add support for "a", "a.b", "a.b.c" formats
+bool IPAddress::fromString4(const char *address) {
+    // TODO: (IPv4) add support for "a", "a.b", "a.b.c" formats
 
     uint16_t acc = 0; // Accumulator
     uint8_t dots = 0;
@@ -103,7 +128,7 @@ bool IPAddress::fromString(const char *address)
                 // Too much dots (there must be 3 dots)
                 return false;
             }
-            _address.bytes[dots++] = acc;
+            (*this)[dots++] = acc;
             acc = 0;
         }
         else
@@ -117,9 +142,146 @@ bool IPAddress::fromString(const char *address)
         // Too few dots (there must be 3 dots)
         return false;
     }
-    _address.bytes[3] = acc;
+    (*this)[3] = acc;
+
+    setV4();
+    return true;
+}
+
+IPAddress& IPAddress::operator=(const uint8_t *address) {
+    setV4();
+    v4() = *reinterpret_cast<const uint32_t*>(address);
+    return *this;
+}
+
+IPAddress& IPAddress::operator=(uint32_t address) {
+    setV4();
+    v4() = address;
+    return *this;
+}
+
+bool IPAddress::operator==(const uint8_t* addr) const {
+    return isV4() && v4() == *reinterpret_cast<const uint32_t*>(addr);
+}
+
+size_t IPAddress::printTo(Print& p) const {
+    size_t n = 0;
+
+    // if (!isSet())
+    //     return p.print(F("(IP unset)"));
+
+#if LWIP_IPV6
+    if (isV6()) {
+        int count0 = 0;
+        for (int i = 0; i < 8; i++) {
+            uint16_t bit = PP_NTOHS(raw6()[i]);
+            if (bit || count0 < 0) {
+                n += p.printf("%x", bit);
+                if (count0 > 0)
+                    // no more hiding 0
+                    count0 = -8;
+            } else
+                count0++;
+            if ((i != 7 && count0 < 2) || count0 == 7)
+                n += p.print(':');
+        }
+        return n;
+    }
+#endif
+
+    for(int i = 0; i < 4; i++) {
+        n += p.print((*this)[i], DEC);
+        if (i != 3)
+            n += p.print('.');
+    }
+    return n;
+}
+
+String IPAddress::toString() const
+{
+    StreamString sstr;
+#if LWIP_IPV6
+    if (isV6())
+        sstr.reserve(40); // 8 shorts x 4 chars each + 7 colons + nullterm
+    else
+#endif
+        sstr.reserve(16); // 4 bytes with 3 chars max + 3 dots + nullterm, or '(IP unset)'
+    printTo(sstr);
+    return sstr;
+}
+
+bool IPAddress::isValid(const String& arg) {
+	return IPAddress().fromString(arg);
+}
+
+bool IPAddress::isValid(const char* arg) {
+	return IPAddress().fromString(arg);
+}
+
+const IPAddress INADDR46_ANY; // generic "0.0.0.0" for IPv4 & IPv6
+const IPAddress INADDR46_NONE(255,255,255,255);
+
+void IPAddress::clear() {
+    (*this) = INADDR46_ANY;
+}
+
+/**************************************/
+
+#if LWIP_IPV6
+
+bool IPAddress::fromString6(const char *address) {
+    // TODO: test test test
+
+    uint32_t acc = 0; // Accumulator
+    int dots = 0, doubledots = -1;
+
+    while (*address)
+    {
+        char c = tolower(*address++);
+        if (isalnum(c)) {
+            if (c >= 'a')
+                c -= 'a' - '0' - 10;
+            acc = acc * 16 + (c - '0');
+            if (acc > 0xffff)
+                // Value out of range
+                return false;
+        }
+        else if (c == ':') {
+            if (*address == ':') {
+                if (doubledots >= 0)
+                    // :: allowed once
+                    return false;
+                // remember location
+                doubledots = dots + !!acc;
+                address++;
+            }
+            if (dots == 7)
+                // too many separators
+                return false;
+            raw6()[dots++] = PP_HTONS(acc);
+            acc = 0;
+        }
+        else
+            // Invalid char
+            return false;
+    }
+
+    if (doubledots == -1 && dots != 7)
+        // Too few separators
+        return false;
+    raw6()[dots++] = PP_HTONS(acc);
+
+    if (doubledots != -1) {
+        for (int i = dots - doubledots - 1; i >= 0; i--)
+            raw6()[8 - dots + doubledots + i] = raw6()[doubledots + i];
+        for (int i = doubledots; i < 8 - dots + doubledots; i++)
+            raw6()[i] = 0;
+    }
+
+    setV6();
     return true;
 }
+#endif // LWIP_IPV6
 
 // declared one time - as external in IPAddress.h
-IPAddress INADDR_NONE(0, 0, 0, 0);
+IPAddress INADDR_NONE(0, 0, 0, 0);      // TODO
diff --git a/cores/esp32/IPAddress.h b/cores/esp32/IPAddress.h
index 3bedd4f8749..d6d2e947f67 100644
--- a/cores/esp32/IPAddress.h
+++ b/cores/esp32/IPAddress.h
@@ -20,77 +20,174 @@
 #ifndef IPAddress_h
 #define IPAddress_h
 
-#include <stdint.h>
 #include <WString.h>
 #include <Printable.h>
+#include <lwip/netif.h>
 
-// A class to make it easier to handle and pass around IP addresses
+enum IPType
+{
+    IPv4 = IPADDR_TYPE_V4,
+    IPv6 = IPADDR_TYPE_V6
+};
 
 class IPAddress: public Printable
 {
 private:
-    union {
-        uint8_t bytes[4];  // IPv4 address
-        uint32_t dword;
-    } _address;
+    ip_addr_t _ip;
 
     // Access the raw byte array containing the address.  Because this returns a pointer
     // to the internal structure rather than a copy of the address this function should only
     // be used when you know that the usage of the returned uint8_t* will be transient and not
     // stored.
-    uint8_t* raw_address()
-    {
-        return _address.bytes;
+    uint8_t* raw_address() {
+        return reinterpret_cast<uint8_t*>(&v4());
     }
+    const uint8_t* raw_address() const {
+        return reinterpret_cast<const uint8_t*>(&v4());
+    }
+
+    void ctor32 (uint32_t);
 
 public:
     // Constructors
     IPAddress();
+    IPAddress(const IPAddress& from);
     IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet);
-    IPAddress(uint32_t address);
-    IPAddress(const uint8_t *address);
-    virtual ~IPAddress() {}
+    IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16);
+    IPAddress(uint32_t address) { ctor32(address); }
+    IPAddress(const uint8_t *address);      // v4 only
+    IPAddress(IPType type, const uint8_t *address);
 
     bool fromString(const char *address);
     bool fromString(const String &address) { return fromString(address.c_str()); }
 
     // Overloaded cast operator to allow IPAddress objects to be used where a pointer
     // to a four-byte uint8_t array is expected
-    operator uint32_t() const
-    {
-        return _address.dword;
+    operator uint32_t() const { return isV4()? v4(): (uint32_t)0; }
+    operator uint32_t()       { return isV4()? v4(): (uint32_t)0; }
+
+    // generic IPv4 wrapper to uint32-view like arduino loves to see it
+    const uint32_t& v4() const { return ip_2_ip4(&_ip)->addr; } // for raw_address(const)
+            uint32_t& v4()       { return ip_2_ip4(&_ip)->addr; }
+
+    bool operator==(const IPAddress& addr) const {
+        return ip_addr_cmp(&_ip, &addr._ip);
     }
-    bool operator==(const IPAddress& addr) const
-    {
-        return _address.dword == addr._address.dword;
+    bool operator!=(const IPAddress& addr) const {
+        return !ip_addr_cmp(&_ip, &addr._ip);
+    }
+    bool operator==(uint32_t addr) const {
+        return isV4() && v4() == addr;
     }
+    // bool operator==(unsigned long addr) const {
+    //     return isV4() && v4() == (uint32_t)addr;
+    // }
+    bool operator!=(uint32_t addr) const {
+        return !(isV4() && v4() == addr);
+    }
+    // bool operator!=(unsigned long addr) const {
+    //     return isV4() && v4() != (uint32_t)addr;
+    // }
     bool operator==(const uint8_t* addr) const;
 
+    int operator>>(int n) const {
+        return isV4()? v4() >> n: 0;
+    }
+
     // Overloaded index operator to allow getting and setting individual octets of the address
-    uint8_t operator[](int index) const
-    {
-        return _address.bytes[index];
+    uint8_t operator[](int index) const {
+        return isV4()? *(raw_address() + index): 0;
     }
-    uint8_t& operator[](int index)
-    {
-        return _address.bytes[index];
+    uint8_t& operator[](int index) {
+        setV4();
+        return *(raw_address() + index);
     }
 
     // Overloaded copy operators to allow initialisation of IPAddress objects from other types
     IPAddress& operator=(const uint8_t *address);
     IPAddress& operator=(uint32_t address);
+    IPAddress& operator=(const IPAddress&) = default;
+
+    IPType type() const { return (IPType)_ip.type; }
 
     virtual size_t printTo(Print& p) const;
     String toString() const;
 
+    void clear();
+
+    /*
+            check if input string(arg) is a valid IPV4 address or not.
+            return true on valid.
+            return false on invalid.
+    */
+    static bool isValid(const String& arg);
+    static bool isValid(const char* arg);
+
     friend class EthernetClass;
     friend class UDP;
     friend class Client;
     friend class Server;
     friend class DhcpClass;
     friend class DNSClient;
+
+    operator       ip_addr_t () const { return  _ip; }
+    operator const ip_addr_t*() const { return &_ip; }
+    operator       ip_addr_t*()       { return &_ip; }
+
+    bool isV4() const { return IP_IS_V4_VAL(_ip); }
+    void setV4() { IP_SET_TYPE_VAL(_ip, IPADDR_TYPE_V4); }
+
+    bool isLocal() const { return ip_addr_islinklocal(&_ip); }
+    bool isAny() const { return ip_addr_isany_val(_ip); }
+
+#if LWIP_IPV6
+    IPAddress(const ip_addr_t& lwip_addr) { ip_addr_copy(_ip, lwip_addr); }
+    IPAddress(const ip_addr_t* lwip_addr) { ip_addr_copy(_ip, *lwip_addr); }
+
+    IPAddress& operator=(const ip_addr_t& lwip_addr) { ip_addr_copy(_ip, lwip_addr); return *this; }
+    IPAddress& operator=(const ip_addr_t* lwip_addr) { ip_addr_copy(_ip, *lwip_addr); return *this; }
+
+    uint16_t* raw6()
+    {
+        setV6();
+        return reinterpret_cast<uint16_t*>(ip_2_ip6(&_ip));
+    }
+
+    const uint16_t* raw6() const
+    {
+        return isV6()? reinterpret_cast<const uint16_t*>(ip_2_ip6(&_ip)): nullptr;
+    }
+
+    // when not IPv6, ip_addr_t == ip4_addr_t so this one would be ambiguous
+    // required otherwise
+    operator const ip4_addr_t*() const { return isV4()? ip_2_ip4(&_ip): nullptr; }
+
+    bool isV6() const { return IP_IS_V6_VAL(_ip); }
+    void setV6() {
+        IP_SET_TYPE_VAL(_ip, IPADDR_TYPE_V6);
+        ip6_addr_clear_zone(ip_2_ip6(&_ip));
+    }
+
+protected:
+    bool fromString6(const char *address);
+
+#else
+
+    // allow portable code when IPv6 is not enabled
+
+    uint16_t* raw6() { return nullptr; }
+    const uint16_t* raw6() const { return nullptr; }
+    bool isV6() const { return false; }
+    void setV6() { }
+
+#endif
+
+protected:
+    bool fromString4(const char *address);
 };
 
 // changed to extern because const declaration creates copies in BSS of INADDR_NONE for each CPP unit that includes it
 extern IPAddress INADDR_NONE;
+extern IPAddress IN6ADDR_ANY;
+
 #endif
diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c
index 014b08125f6..4b58c1d9a91 100644
--- a/cores/esp32/esp32-hal-ledc.c
+++ b/cores/esp32/esp32-hal-ledc.c
@@ -166,14 +166,15 @@ void ledcAttachPin(uint8_t pin, uint8_t chan)
         return;
     }
     uint8_t group=(chan/8), channel=(chan%8), timer=((chan/2)%4);
-    
+    uint32_t duty = ledc_get_duty(group,channel);
+
     ledc_channel_config_t ledc_channel = {
         .speed_mode     = group,
         .channel        = channel,
         .timer_sel      = timer,
         .intr_type      = LEDC_INTR_DISABLE,
         .gpio_num       = pin,
-        .duty           = 0,
+        .duty           = duty,
         .hpoint         = 0
     };
     ledc_channel_config(&ledc_channel);
@@ -211,6 +212,8 @@ uint32_t ledcChangeFrequency(uint8_t chan, uint32_t freq, uint8_t bit_num)
 
 static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
 static int cnt_channel = LEDC_CHANNELS;
+static uint8_t analog_resolution = 8;
+static int analog_frequency = 1000;
 void analogWrite(uint8_t pin, int value) {
   // Use ledc hardware for internal pins
   if (pin < SOC_GPIO_PIN_COUNT) {
@@ -220,8 +223,8 @@ void analogWrite(uint8_t pin, int value) {
           return;
       }
       pin_to_channel[pin] = cnt_channel--;
+      ledcSetup(cnt_channel, analog_frequency, analog_resolution);
       ledcAttachPin(pin, cnt_channel);
-      ledcSetup(cnt_channel, 1000, 8);
     }
     ledcWrite(pin_to_channel[pin] - 1, value);
   }
@@ -230,3 +233,25 @@ void analogWrite(uint8_t pin, int value) {
 int8_t analogGetChannel(uint8_t pin) {
     return pin_to_channel[pin] - 1;
 }
+
+void analogWriteFrequency(uint32_t freq) {
+    if (cnt_channel != LEDC_CHANNELS) {
+        for (int channel = LEDC_CHANNELS - 1; channel >= cnt_channel; channel--) {
+            ledcChangeFrequency(channel, freq, analog_resolution);
+        }
+    }
+    analog_frequency = freq;
+}
+
+void analogWriteResolution(uint8_t bits) {
+    if(bits > LEDC_MAX_BIT_WIDTH) {
+        log_w("analogWrite resolution width too big! Setting to maximum %u bits)", LEDC_MAX_BIT_WIDTH);
+        bits = LEDC_MAX_BIT_WIDTH;
+    }
+    if (cnt_channel != LEDC_CHANNELS) {
+        for (int channel = LEDC_CHANNELS - 1; channel >= cnt_channel; channel--) {
+            ledcChangeFrequency(channel, analog_frequency, bits);
+        }
+    }
+    analog_resolution = bits;
+}
diff --git a/cores/esp32/esp32-hal.h b/cores/esp32/esp32-hal.h
index 59dca98cbb9..51ecea405df 100644
--- a/cores/esp32/esp32-hal.h
+++ b/cores/esp32/esp32-hal.h
@@ -93,6 +93,8 @@ void yield(void);
 
 void analogWrite(uint8_t pin, int value);
 int8_t analogGetChannel(uint8_t pin);
+void analogWriteFrequency(uint32_t freq);
+void analogWriteResolution(uint8_t bits);
 
 //returns chip temperature in Celsius
 float temperatureRead();
diff --git a/docs/source/api/rainmaker.rst b/docs/source/api/rainmaker.rst
index ae0c735b00b..1b157a790b7 100644
--- a/docs/source/api/rainmaker.rst
+++ b/docs/source/api/rainmaker.rst
@@ -22,9 +22,6 @@ The key features of ESP RainMaker are:
 
 Additional information about ESP RainMaker can be found `here <https://rainmaker.espressif.com/>`__.
 
-#########################
-Arduino ESP Rainmaker API
-#########################
 
 ESP RainMaker Agent API
 -----------------------
@@ -127,6 +124,21 @@ This function will return
 1. `ESP_OK` : On success
 2. Error in case of failure
 
+RMaker.enableScenes
+*******************
+
+This API enables the Scenes service for the node. It should be called after `RMaker.initNode()` and before `RMaker.start()`.
+For more information, check `here <https://rainmaker.espressif.com/docs/scenes.html>`__.
+
+.. code-block:: arduino
+
+    esp_err_t enableScenes()
+
+This function will return
+
+1. `ESP_OK` : On success
+2. Error in case of failure
+
 RMaker.setTimeZone
 ******************
 
diff --git a/docs/source/esp-idf_component.rst b/docs/source/esp-idf_component.rst
index e46864e011b..fe27d52110f 100644
--- a/docs/source/esp-idf_component.rst
+++ b/docs/source/esp-idf_component.rst
@@ -39,6 +39,21 @@ Installation
 
 .. note:: If you use Arduino with ESP-IDF often, you can place the arduino folder into global components folder.
 
+If you're targeting the ESP32-S2 or ESP32-S3 and you want to use USBHID classes such as ``USBHID``, ``USBHIDConsumerControl``, ``USBHIDGamepad``, ``USBHIDKeyboard``, ``USBHIDMouse``, ``USBHIDSystemControl``, or ``USBHIDVendor``:
+
+1. Clone these nested repos somewhere:
+
+.. code-block:: bash
+
+    git clone https://github.com/espressif/esp32-arduino-lib-builder.git esp32-arduino-lib-builder && \
+    git clone https://github.com/hathach/tinyusb.git esp32-arduino-lib-builder/components/arduino_tinyusb/tinyusb
+
+2. In the project folder, edit ``CMakeLists.txt`` and add the following before the ``project()`` line:
+
+.. code-block:: bash
+
+    set(EXTRA_COMPONENT_DIRS <path to esp32-arduino-lib-builder/components/arduino_tinyusb>)
+
 Configuration
 -------------
 
diff --git a/libraries/RainMaker/examples/RMakerCustom/RMakerCustom.ino b/libraries/RainMaker/examples/RMakerCustom/RMakerCustom.ino
index f10f858cad7..74b0c39bd3d 100644
--- a/libraries/RainMaker/examples/RMakerCustom/RMakerCustom.ino
+++ b/libraries/RainMaker/examples/RMakerCustom/RMakerCustom.ino
@@ -26,7 +26,7 @@ static Device my_device("Dimmer", "custom.device.dimmer", &gpio_dimmer);
 
 void sysProvEvent(arduino_event_t *sys_event)
 {
-    switch (sys_event->event_id) {      
+    switch (sys_event->event_id) {
         case ARDUINO_EVENT_PROV_START:
 #if CONFIG_IDF_TARGET_ESP32S2
         Serial.printf("\nProvisioning Started with name \"%s\" and PoP \"%s\" on SoftAP\n", service_name, pop);
@@ -34,7 +34,7 @@ void sysProvEvent(arduino_event_t *sys_event)
 #else
         Serial.printf("\nProvisioning Started with name \"%s\" and PoP \"%s\" on BLE\n", service_name, pop);
         printQR(service_name, pop, "ble");
-#endif        
+#endif
         break;
         default:;
     }
@@ -63,7 +63,7 @@ void setup()
     pinMode(gpio_dimmer, OUTPUT);
     digitalWrite(gpio_dimmer, DEFAULT_POWER_MODE);
 
-    Node my_node;    
+    Node my_node;
     my_node = RMaker.initNode("ESP RainMaker Node");
 
     //Create custom dimmer device
@@ -78,13 +78,13 @@ void setup()
     my_device.addParam(level_param);
 
     my_device.addCb(write_callback);
-    
-    //Add custom dimmer device to the node   
+
+    //Add custom dimmer device to the node
     my_node.addDevice(my_device);
 
-    //This is optional 
+    //This is optional
     RMaker.enableOTA(OTA_USING_PARAMS);
-    //If you want to enable scheduling, set time zone for your region using setTimeZone(). 
+    //If you want to enable scheduling, set time zone for your region using setTimeZone().
     //The list of available values are provided here https://rainmaker.espressif.com/docs/time-service.html
     // RMaker.setTimeZone("Asia/Shanghai");
     // Alternatively, enable the Timezone service and let the phone apps set the appropriate timezone
@@ -92,6 +92,8 @@ void setup()
 
     RMaker.enableSchedule();
 
+    RMaker.enableScenes();
+
     RMaker.start();
 
     WiFi.onEvent(sysProvEvent);
diff --git a/libraries/RainMaker/examples/RMakerCustomAirCooler/RMakerCustomAirCooler.ino b/libraries/RainMaker/examples/RMakerCustomAirCooler/RMakerCustomAirCooler.ino
index 5b36ecffd06..aa59c060f70 100644
--- a/libraries/RainMaker/examples/RMakerCustomAirCooler/RMakerCustomAirCooler.ino
+++ b/libraries/RainMaker/examples/RMakerCustomAirCooler/RMakerCustomAirCooler.ino
@@ -43,7 +43,7 @@ static Device my_device("Air Cooler", "my.device.air-cooler", NULL);
 
 void sysProvEvent(arduino_event_t *sys_event)
 {
-    switch (sys_event->event_id) {      
+    switch (sys_event->event_id) {
         case ARDUINO_EVENT_PROV_START:
 #if CONFIG_IDF_TARGET_ESP32S2
         Serial.printf("\nProvisioning Started with name \"%s\" and PoP \"%s\" on SoftAP\n", service_name, pop);
@@ -51,7 +51,7 @@ void sysProvEvent(arduino_event_t *sys_event)
 #else
         Serial.printf("\nProvisioning Started with name \"%s\" and PoP \"%s\" on BLE\n", service_name, pop);
         printQR(service_name, pop, "ble");
-#endif        
+#endif
         break;
         default:;
     }
@@ -114,7 +114,7 @@ void setup()
     pinMode(gpio_speed, OUTPUT);
     analogWrite(gpio_speed, DEFAULT_SPEED);
 
-    Node my_node;    
+    Node my_node;
     my_node = RMaker.initNode("ESP RainMaker Node");
 
     //Create custom air cooler device
@@ -138,13 +138,13 @@ void setup()
     my_device.addParam(mode_param);
 
     my_device.addCb(write_callback);
-    
-    //Add custom Air Cooler device to the node   
+
+    //Add custom Air Cooler device to the node
     my_node.addDevice(my_device);
 
-    //This is optional 
+    //This is optional
     // RMaker.enableOTA(OTA_USING_PARAMS);
-    //If you want to enable scheduling, set time zone for your region using setTimeZone(). 
+    //If you want to enable scheduling, set time zone for your region using setTimeZone().
     //The list of available values are provided here https://rainmaker.espressif.com/docs/time-service.html
     // RMaker.setTimeZone("Asia/Shanghai");
     //Alternatively, enable the Timezone service and let the phone apps set the appropriate timezone
@@ -152,6 +152,8 @@ void setup()
 
     RMaker.enableSchedule();
 
+    RMaker.enableScenes();
+
     RMaker.start();
 
     WiFi.onEvent(sysProvEvent);
diff --git a/libraries/RainMaker/examples/RMakerSonoffDualR3/RMakerSonoffDualR3.ino b/libraries/RainMaker/examples/RMakerSonoffDualR3/RMakerSonoffDualR3.ino
index cf9a58b7111..38b459ffd20 100644
--- a/libraries/RainMaker/examples/RMakerSonoffDualR3/RMakerSonoffDualR3.ino
+++ b/libraries/RainMaker/examples/RMakerSonoffDualR3/RMakerSonoffDualR3.ino
@@ -136,6 +136,7 @@ void setup()
     // Alternatively, enable the Timezone service and let the phone apps set the appropriate timezone
     RMaker.enableTZService();
     RMaker.enableSchedule();
+    RMaker.enableScenes();
 
     //Service Name
     for(int i=0; i<17; i=i+8) {
diff --git a/libraries/RainMaker/examples/RMakerSwitch/RMakerSwitch.ino b/libraries/RainMaker/examples/RMakerSwitch/RMakerSwitch.ino
index 2da78ad0744..53d71387b15 100644
--- a/libraries/RainMaker/examples/RMakerSwitch/RMakerSwitch.ino
+++ b/libraries/RainMaker/examples/RMakerSwitch/RMakerSwitch.ino
@@ -81,6 +81,8 @@ void setup()
 
     RMaker.enableSchedule();
 
+    RMaker.enableScenes();
+
     RMaker.start();
 
     WiFi.onEvent(sysProvEvent);
diff --git a/libraries/RainMaker/src/RMaker.cpp b/libraries/RainMaker/src/RMaker.cpp
index fc80e716424..c7e3e921dd5 100644
--- a/libraries/RainMaker/src/RMaker.cpp
+++ b/libraries/RainMaker/src/RMaker.cpp
@@ -3,6 +3,7 @@
 #include "RMaker.h"
 #include <esp_rmaker_schedule.h>
 #include <esp_rmaker_utils.h>
+#include <esp_rmaker_scenes.h>
 bool wifiLowLevelInit(bool persistent);
 static esp_err_t err;
 
@@ -28,12 +29,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
                 log_i("Unhandled RainMaker Event:");
         }
     } else if (event_base == RMAKER_OTA_EVENT) {
-        if(event_data == NULL){
+        if (event_data == NULL) {
             event_data = (void*)"";
         }
         switch(event_id) {
             case RMAKER_OTA_EVENT_STARTING:
-                log_i("Starting OTA : %s", (char*)event_data);
+                log_i("Starting OTA");
                 break;
             case RMAKER_OTA_EVENT_IN_PROGRESS:
                 log_i("OTA in progress : %s", (char*)event_data);
@@ -146,5 +147,13 @@ esp_err_t RMakerClass::enableOTA(ota_type_t type, const char *cert)
     return err;
 }
 
+esp_err_t RMakerClass::enableScenes()
+{
+    err = esp_rmaker_scenes_enable();
+    if (err != ESP_OK) {
+        log_e("Scenes enable failed");
+    }
+    return err;
+}
 RMakerClass RMaker;
 #endif
diff --git a/libraries/RainMaker/src/RMaker.h b/libraries/RainMaker/src/RMaker.h
index dbfbe5c49f5..69930052f6a 100644
--- a/libraries/RainMaker/src/RMaker.h
+++ b/libraries/RainMaker/src/RMaker.h
@@ -25,9 +25,9 @@ class RMakerClass
 {
     private:
         esp_rmaker_config_t rainmaker_cfg = {false};
-      
+
     public:
-    
+
         void setTimeSync(bool val);
         Node initNode(const char *name, const char *type = "ESP RainMaker with Arduino");
         esp_err_t deinitNode(Node node);
@@ -35,6 +35,7 @@ class RMakerClass
         esp_err_t enableSchedule();
         esp_err_t enableTZService();
         esp_err_t enableOTA(ota_type_t type, const char *cert = ESP_RMAKER_OTA_DEFAULT_SERVER_CERT);
+        esp_err_t enableScenes();
         esp_err_t start();
         esp_err_t stop();
 };
diff --git a/libraries/RainMaker/src/RMakerUtils.cpp b/libraries/RainMaker/src/RMakerUtils.cpp
index 1d800b72df8..b8af2187c1a 100644
--- a/libraries/RainMaker/src/RMakerUtils.cpp
+++ b/libraries/RainMaker/src/RMakerUtils.cpp
@@ -1,12 +1,13 @@
 #include "RMakerUtils.h"
 #ifdef CONFIG_ESP_RMAKER_WORK_QUEUE_TASK_STACK
-void RMakerFactoryReset(int seconds)
+#define RESET_DELAY_SEC 2
+void RMakerFactoryReset(int reboot_seconds)
 {
-    esp_rmaker_factory_reset(0, seconds);
+    esp_rmaker_factory_reset(RESET_DELAY_SEC, reboot_seconds);
 }
 
-void RMakerWiFiReset(int seconds)
+void RMakerWiFiReset(int reboot_seconds)
 {
-    esp_rmaker_wifi_reset(0, seconds);
+    esp_rmaker_wifi_reset(RESET_DELAY_SEC, reboot_seconds);    
 }
 #endif
\ No newline at end of file
diff --git a/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino b/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino
new file mode 100644
index 00000000000..05e02771dae
--- /dev/null
+++ b/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino
@@ -0,0 +1,132 @@
+/*
+  WiFiTelnetToSerial - Example Transparent UART to Telnet Server for ESP32
+
+  Copyright (c) 2017 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP32 WiFi library for Arduino environment.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#include <WiFi.h>
+#include <WiFiMulti.h>
+
+WiFiMulti wifiMulti;
+
+//Even this example state IPv6, it is dual stack and compatible with IPv4 too
+
+//how many clients should be able to telnet to this ESP32
+#define MAX_SRV_CLIENTS 1
+const char* ssid = "**********";
+const char* password = "**********";
+
+WiFiServer server(23);
+WiFiClient serverClients[MAX_SRV_CLIENTS];
+
+void setup() {
+  Serial.begin(115200);
+  Serial.println("\nConnecting");
+
+  wifiMulti.IPv6(true);
+  wifiMulti.addAP(ssid, password);
+  wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2");
+  wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3");
+
+  Serial.println("Connecting Wifi ");
+  for (int loops = 10; loops > 0; loops--) {
+    if (wifiMulti.run() == WL_CONNECTED) {
+      Serial.println("");
+      Serial.print("WiFi connected ");
+      Serial.print("IP address: ");
+      Serial.println(WiFi.localIP());
+      break;
+    }
+    else {
+      Serial.println(loops);
+      delay(1000);
+    }
+  }
+  if (wifiMulti.run() != WL_CONNECTED) {
+    Serial.println("WiFi connect failed");
+    delay(1000);
+    ESP.restart();
+  }
+
+  //start UART and the server
+  Serial1.begin(9600);
+  server.begin();
+  server.setNoDelay(true);
+
+  Serial.print("Ready! Use 'telnet ");
+  Serial.print(WiFi.localIP());
+  Serial.println(" 23' to connect");
+}
+
+void loop() {
+  uint8_t i;
+  if (wifiMulti.run() == WL_CONNECTED) {
+    //check if there are any new clients
+    if (server.hasClient()){
+      for(i = 0; i < MAX_SRV_CLIENTS; i++){
+        //find free/disconnected spot
+        if (!serverClients[i] || !serverClients[i].connected()){
+          if(serverClients[i]) serverClients[i].stop();
+          serverClients[i] = server.available();
+          if (!serverClients[i]) Serial.println("available broken");
+          Serial.print("New client: ");
+          Serial.print(i); Serial.print(' ');
+          Serial.println(serverClients[i].remoteIP());
+          break;
+        }
+      }
+      if (i >= MAX_SRV_CLIENTS) {
+        //no free/disconnected spot so reject
+        server.available().stop();
+      }
+    }
+    //check clients for data
+    for(i = 0; i < MAX_SRV_CLIENTS; i++){
+      if (serverClients[i] && serverClients[i].connected()){
+        if(serverClients[i].available()){
+          //get data from the telnet client and push it to the UART
+          while(serverClients[i].available()) Serial1.write(serverClients[i].read());
+        }
+      }
+      else {
+        if (serverClients[i]) {
+          serverClients[i].stop();
+        }
+      }
+    }
+    //check UART for data
+    if(Serial1.available()){
+      size_t len = Serial1.available();
+      uint8_t sbuf[len];
+      Serial1.readBytes(sbuf, len);
+      //push UART data to all connected telnet clients
+      for(i = 0; i < MAX_SRV_CLIENTS; i++){
+        if (serverClients[i] && serverClients[i].connected()){
+          serverClients[i].write(sbuf, len);
+          delay(1);
+        }
+      }
+    }
+  }
+  else {
+    Serial.println("WiFi not connected!");
+    for(i = 0; i < MAX_SRV_CLIENTS; i++) {
+      if (serverClients[i]) serverClients[i].stop();
+    }
+    delay(1000);
+  }
+}
diff --git a/libraries/WiFi/src/WiFiClient.cpp b/libraries/WiFi/src/WiFiClient.cpp
index 6e56e94ac53..ac8577fba80 100644
--- a/libraries/WiFi/src/WiFiClient.cpp
+++ b/libraries/WiFi/src/WiFiClient.cpp
@@ -23,6 +23,11 @@
 #include <lwip/netdb.h>
 #include <errno.h>
 
+#define IN6_IS_ADDR_V4MAPPED(a) \
+        ((((__const uint32_t *) (a))[0] == 0) \
+         && (((__const uint32_t *) (a))[1] == 0) \
+         && (((__const uint32_t *) (a))[2] == htonl (0xffff)))
+
 #define WIFI_CLIENT_DEF_CONN_TIMEOUT_MS  (3000)
 #define WIFI_CLIENT_MAX_WRITE_RETRY      (10)
 #define WIFI_CLIENT_SELECT_TIMEOUT_US    (1000000)
@@ -210,22 +215,32 @@ int WiFiClient::connect(IPAddress ip, uint16_t port)
 {
     return connect(ip,port,_timeout);
 }
+
 int WiFiClient::connect(IPAddress ip, uint16_t port, int32_t timeout)
 {
+    struct sockaddr_storage serveraddr = {};
     _timeout = timeout;
-    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
+    int sockfd = -1;
+
+    if (ip.type() == IPv6) {
+        struct sockaddr_in6 *tmpaddr = (struct sockaddr_in6 *)&serveraddr;
+        sockfd = socket(AF_INET6, SOCK_STREAM, 0);
+        tmpaddr->sin6_family = AF_INET6;
+        memcpy(tmpaddr->sin6_addr.un.u8_addr, &ip[0], 16);
+        tmpaddr->sin6_port = htons(port);
+    } else {
+        struct sockaddr_in *tmpaddr = (struct sockaddr_in *)&serveraddr;
+        sockfd = socket(AF_INET, SOCK_STREAM, 0);
+        tmpaddr->sin_family = AF_INET;
+        tmpaddr->sin_addr.s_addr = ip;
+        tmpaddr->sin_port = htons(port);
+    }
     if (sockfd < 0) {
         log_e("socket: %d", errno);
         return 0;
     }
     fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK );
 
-    uint32_t ip_addr = ip;
-    struct sockaddr_in serveraddr;
-    memset((char *) &serveraddr, 0, sizeof(serveraddr));
-    serveraddr.sin_family = AF_INET;
-    memcpy((void *)&serveraddr.sin_addr.s_addr, (const void *)(&ip_addr), 4);
-    serveraddr.sin_port = htons(port);
     fd_set fdset;
     struct timeval tv;
     FD_ZERO(&fdset);
@@ -294,6 +309,19 @@ int WiFiClient::connect(const char *host, uint16_t port)
 
 int WiFiClient::connect(const char *host, uint16_t port, int32_t timeout)
 {
+    if (WiFiGenericClass::getStatusBits() & WIFI_WANT_IP6_BIT) {
+        ip_addr_t srv6;
+        if(!WiFiGenericClass::hostByName6(host, srv6)){
+            return 0;
+        }
+        if (srv6.type == IPADDR_TYPE_V4) {
+            IPAddress ip(srv6.u_addr.ip4.addr);
+            return connect(ip, port, timeout);
+        } else {
+            IPAddress ip(IPv6, (uint8_t*)&srv6.u_addr.ip6.addr[0]);
+            return connect(ip, port, timeout);
+        }
+    }
     IPAddress srv((uint32_t)0);
     if(!WiFiGenericClass::hostByName(host, srv)){
         return 0;
@@ -562,8 +590,24 @@ IPAddress WiFiClient::remoteIP(int fd) const
     struct sockaddr_storage addr;
     socklen_t len = sizeof addr;
     getpeername(fd, (struct sockaddr*)&addr, &len);
-    struct sockaddr_in *s = (struct sockaddr_in *)&addr;
-    return IPAddress((uint32_t)(s->sin_addr.s_addr));
+
+    // IPv4 socket, old way
+    if (((struct sockaddr*)&addr)->sa_family == AF_INET) {
+        struct sockaddr_in *s = (struct sockaddr_in *)&addr;
+        return IPAddress((uint32_t)(s->sin_addr.s_addr));
+    }
+
+    // IPv6, but it might be IPv4 mapped address
+    if (((struct sockaddr*)&addr)->sa_family == AF_INET6) {
+        struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&addr;
+        if (IN6_IS_ADDR_V4MAPPED(saddr6->sin6_addr.un.u32_addr)) {
+            return IPAddress(IPv4, (uint8_t*)saddr6->sin6_addr.s6_addr+12);
+        } else {
+            return IPAddress(IPv6, (uint8_t*)(saddr6->sin6_addr.s6_addr));
+        }
+    }
+    log_e("WiFiClient::remoteIP Not AF_INET or AF_INET6?");
+    return (IPAddress(0,0,0,0));
 }
 
 uint16_t WiFiClient::remotePort(int fd) const
diff --git a/libraries/WiFi/src/WiFiGeneric.cpp b/libraries/WiFi/src/WiFiGeneric.cpp
index a94b1419414..3f8a9d797ca 100644
--- a/libraries/WiFi/src/WiFiGeneric.cpp
+++ b/libraries/WiFi/src/WiFiGeneric.cpp
@@ -943,8 +943,8 @@ esp_err_t WiFiGenericClass::_eventCallback(arduino_event_t *event)
     } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_CONNECTED) {
         WiFiSTAClass::_setStatus(WL_IDLE_STATUS);
         setStatusBits(STA_CONNECTED_BIT);
-
-        //esp_netif_create_ip6_linklocal(esp_netifs[ESP_IF_WIFI_STA]);
+        if (getStatusBits() & WIFI_WANT_IP6_BIT)
+            esp_netif_create_ip6_linklocal(esp_netifs[ESP_IF_WIFI_STA]);
     } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
         uint8_t reason = event->event_info.wifi_sta_disconnected.reason;
         // Reason 0 causes crash, use reason 1 (UNSPECIFIED) instead
@@ -1446,6 +1446,25 @@ static void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, v
     xEventGroupSetBits(_arduino_event_group, WIFI_DNS_DONE_BIT);
 }
 
+/**
+ * IPv6 compatible DNS callback
+ * @param name
+ * @param ipaddr
+ * @param callback_arg
+ */
+static void wifi_dns6_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
+{
+    struct dns_api_msg *msg = (struct dns_api_msg *)callback_arg;
+
+    if(ipaddr && !msg->result) {
+        msg->ip_addr = *ipaddr;
+        msg->result = 1;
+    } else {
+        msg->result = -1;
+    }
+    xEventGroupSetBits(_arduino_event_group, WIFI_DNS_DONE_BIT);
+}
+
 /**
  * Resolve the given hostname to an IP address.
  * @param aHostname     Name to be resolved
@@ -1473,6 +1492,37 @@ int WiFiGenericClass::hostByName(const char* aHostname, IPAddress& aResult)
     return (uint32_t)aResult != 0;
 }
 
+/**
+ * Resolve the given hostname to an IP6 address.
+ * @param aHostname     Name to be resolved
+ * @param aResult       IPv6Address structure to store the returned IP address
+ * @return 1 if aHostname was successfully converted to an IP address,
+ *          else error code
+ */
+int WiFiGenericClass::hostByName6(const char* aHostname, ip_addr_t& aResult)
+{
+    ip_addr_t addr;
+    struct dns_api_msg arg;
+
+    memset(&arg, 0x0, sizeof(arg));
+    waitStatusBits(WIFI_DNS_IDLE_BIT, 16000);
+    clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT);
+
+    err_t err = dns_gethostbyname_addrtype(aHostname, &addr, &wifi_dns6_found_callback,
+                &arg, LWIP_DNS_ADDRTYPE_IPV6_IPV4);
+    if(err == ERR_OK) {
+        aResult = addr;
+    } else if(err == ERR_INPROGRESS) {
+        waitStatusBits(WIFI_DNS_DONE_BIT, 15000);  //real internal timeout in lwip library is 14[s]
+        clearStatusBits(WIFI_DNS_DONE_BIT);
+        if (arg.result == 1) {
+            aResult = arg.ip_addr;
+        }
+    }
+    setStatusBits(WIFI_DNS_IDLE_BIT);
+    return (uint32_t)err == ERR_OK || (err == ERR_INPROGRESS && arg.result == 1);
+}
+
 IPAddress WiFiGenericClass::calculateNetworkID(IPAddress ip, IPAddress subnet) {
 	IPAddress networkID;
 
diff --git a/libraries/WiFi/src/WiFiGeneric.h b/libraries/WiFi/src/WiFiGeneric.h
index 2f670a34d05..f969cfc7266 100644
--- a/libraries/WiFi/src/WiFiGeneric.h
+++ b/libraries/WiFi/src/WiFiGeneric.h
@@ -138,6 +138,7 @@ static const int WIFI_SCANNING_BIT = BIT11;
 static const int WIFI_SCAN_DONE_BIT= BIT12;
 static const int WIFI_DNS_IDLE_BIT = BIT13;
 static const int WIFI_DNS_DONE_BIT = BIT14;
+static const int WIFI_WANT_IP6_BIT = BIT15;
 
 typedef enum {
 	WIFI_RX_ANT0 = 0,
@@ -151,6 +152,11 @@ typedef enum {
 	WIFI_TX_ANT_AUTO
 } wifi_tx_ant_t;
 
+struct dns_api_msg {
+    ip_addr_t ip_addr;
+    int result;
+};
+
 class WiFiGenericClass
 {
   public:
@@ -212,6 +218,7 @@ class WiFiGenericClass
 
   public:
     static int hostByName(const char *aHostname, IPAddress &aResult);
+    static int hostByName6(const char *aHostname, ip_addr_t& aResult);
 
     static IPAddress calculateNetworkID(IPAddress ip, IPAddress subnet);
     static IPAddress calculateBroadcast(IPAddress ip, IPAddress subnet);
diff --git a/libraries/WiFi/src/WiFiMulti.cpp b/libraries/WiFi/src/WiFiMulti.cpp
index 3d69e481293..9e7f03c6531 100644
--- a/libraries/WiFi/src/WiFiMulti.cpp
+++ b/libraries/WiFi/src/WiFiMulti.cpp
@@ -30,6 +30,7 @@
 
 WiFiMulti::WiFiMulti()
 {
+    ipv6_support = false;
 }
 
 WiFiMulti::~WiFiMulti()
@@ -160,6 +161,8 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
             log_i("[WIFI] Connecting BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)", bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3], bestBSSID[4], bestBSSID[5], bestNetwork.ssid, bestChannel, bestNetworkDb);
 
             WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID);
+            if (ipv6_support == true)
+                WiFi.IPv6(true);
             status = WiFi.status();
 
             auto startTime = millis();
@@ -202,3 +205,7 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
 
     return status;
 }
+
+void WiFiMulti::IPv6(bool state) {
+    ipv6_support = state;
+}
diff --git a/libraries/WiFi/src/WiFiMulti.h b/libraries/WiFi/src/WiFiMulti.h
index 38ddb5d9f95..bbeb78dc860 100644
--- a/libraries/WiFi/src/WiFiMulti.h
+++ b/libraries/WiFi/src/WiFiMulti.h
@@ -42,10 +42,12 @@ class WiFiMulti
 
     bool addAP(const char* ssid, const char *passphrase = NULL);
 
+    void IPv6(bool state);
     uint8_t run(uint32_t connectTimeout=5000);
 
 private:
     std::vector<WifiAPlist_t> APlist;
+    bool ipv6_support;
 };
 
 #endif /* WIFICLIENTMULTI_H_ */
diff --git a/libraries/WiFi/src/WiFiSTA.cpp b/libraries/WiFi/src/WiFiSTA.cpp
index 7bcafea1d3e..d8075868475 100644
--- a/libraries/WiFi/src/WiFiSTA.cpp
+++ b/libraries/WiFi/src/WiFiSTA.cpp
@@ -732,6 +732,19 @@ bool WiFiSTAClass::enableIpV6()
     return esp_netif_create_ip6_linklocal(get_esp_interface_netif(ESP_IF_WIFI_STA)) == ESP_OK;
 }
 
+/**
+ * Enable IPv6 support on the station interface.
+ * @return true on success
+ */
+bool WiFiSTAClass::IPv6(bool state)
+{
+   if (state)
+       WiFiGenericClass::setStatusBits(WIFI_WANT_IP6_BIT);
+   else
+       WiFiGenericClass::clearStatusBits(WIFI_WANT_IP6_BIT);
+   return true;
+}
+
 /**
  * Get the station interface IPv6 address.
  * @return IPv6Address
diff --git a/libraries/WiFi/src/WiFiSTA.h b/libraries/WiFi/src/WiFiSTA.h
index b8bb855c198..6892c996b30 100644
--- a/libraries/WiFi/src/WiFiSTA.h
+++ b/libraries/WiFi/src/WiFiSTA.h
@@ -84,6 +84,7 @@ class WiFiSTAClass
     uint8_t subnetCIDR();
     
     bool enableIpV6();
+    bool IPv6(bool state);
     IPv6Address localIPv6();
 
     // STA WiFi info
diff --git a/libraries/WiFi/src/WiFiServer.cpp b/libraries/WiFi/src/WiFiServer.cpp
index db21858125b..ad2cb7fb364 100644
--- a/libraries/WiFi/src/WiFiServer.cpp
+++ b/libraries/WiFi/src/WiFiServer.cpp
@@ -47,8 +47,8 @@ WiFiClient WiFiServer::available(){
     _accepted_sockfd = -1;
   }
   else {
-  struct sockaddr_in _client;
-  int cs = sizeof(struct sockaddr_in);
+  struct sockaddr_in6 _client;
+  int cs = sizeof(struct sockaddr_in6);
 #ifdef ESP_IDF_VERSION_MAJOR
     client_sock = lwip_accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs);
 #else
@@ -76,14 +76,23 @@ void WiFiServer::begin(uint16_t port, int enable){
   if(port){
       _port = port;
   }
-  struct sockaddr_in server;
-  sockfd = socket(AF_INET , SOCK_STREAM, 0);
+  struct sockaddr_in6 server;
+  sockfd = socket(AF_INET6 , SOCK_STREAM, 0);
   if (sockfd < 0)
     return;
   setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
-  server.sin_family = AF_INET;
-  server.sin_addr.s_addr = _addr;
-  server.sin_port = htons(_port);
+  server.sin6_family = AF_INET6;
+  if (_addr.type() == IPv4) {
+    log_e("---------------- IPv4");
+    memcpy(server.sin6_addr.s6_addr+11, (uint8_t*)&_addr[0], 4);
+    server.sin6_addr.s6_addr[10] = 0xFF;
+    server.sin6_addr.s6_addr[11] = 0xFF;
+  } else {
+    log_e("---------------- IPv6");
+    memcpy(server.sin6_addr.s6_addr, (uint8_t*)&_addr[0], 16);
+  }
+  memset(server.sin6_addr.s6_addr, 0x0, 16);
+  server.sin6_port = htons(_port);
   if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
     return;
   if(listen(sockfd , _max_clients) < 0)
@@ -106,8 +115,8 @@ bool WiFiServer::hasClient() {
     if (_accepted_sockfd >= 0) {
       return true;
     }
-    struct sockaddr_in _client;
-    int cs = sizeof(struct sockaddr_in);
+    struct sockaddr_in6 _client;
+    int cs = sizeof(struct sockaddr_in6);
 #ifdef ESP_IDF_VERSION_MAJOR
     _accepted_sockfd = lwip_accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs);
 #else
diff --git a/libraries/WiFi/src/WiFiServer.h b/libraries/WiFi/src/WiFiServer.h
index 346986abad5..d933231bdbb 100644
--- a/libraries/WiFi/src/WiFiServer.h
+++ b/libraries/WiFi/src/WiFiServer.h
@@ -37,7 +37,6 @@ class WiFiServer : public Server {
   public:
     void listenOnLocalhost(){}
 
-    // _addr(INADDR_ANY) is the same as _addr() ==> 0.0.0.0
     WiFiServer(uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_addr(),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false) {
       log_v("WiFiServer::WiFiServer(port=%d, ...)", port);
     }
diff --git a/libraries/WiFi/src/WiFiUdp.cpp b/libraries/WiFi/src/WiFiUdp.cpp
index 476b5a4a8b5..c0042c8659e 100644
--- a/libraries/WiFi/src/WiFiUdp.cpp
+++ b/libraries/WiFi/src/WiFiUdp.cpp
@@ -48,7 +48,11 @@ uint8_t WiFiUDP::begin(IPAddress address, uint16_t port){
     return 0;
   }
 
+#if LWIP_IPV6
+  if ((udp_server=socket(address.isV6() ? AF_INET6 : AF_INET, SOCK_DGRAM, 0)) == -1){
+#else
   if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1){
+#endif
     log_e("could not create socket: %d", errno);
     return 0;
   }
@@ -60,16 +64,35 @@ uint8_t WiFiUDP::begin(IPAddress address, uint16_t port){
       return 0;
   }
 
-  struct sockaddr_in addr;
-  memset((char *) &addr, 0, sizeof(addr));
-  addr.sin_family = AF_INET;
-  addr.sin_port = htons(server_port);
-  addr.sin_addr.s_addr = (in_addr_t)address;
-  if(bind(udp_server , (struct sockaddr*)&addr, sizeof(addr)) == -1){
+  struct sockaddr_storage serveraddr = {};
+  size_t sock_size = 0;
+#if LWIP_IPV6
+  if (address.isV6()) {
+    struct sockaddr_in6 *tmpaddr = (struct sockaddr_in6 *)&serveraddr;
+    memset((char *) tmpaddr, 0, sizeof(struct sockaddr_in));
+    tmpaddr->sin6_family = AF_INET6;
+    tmpaddr->sin6_port = htons(server_port);
+    // memcpy(tmpaddr->sin6_addr.un.u8_addr, &ip[0], 16); // TODO
+    tmpaddr->sin6_addr = in6addr_any;
+    tmpaddr->sin6_flowinfo = 0;
+    sock_size = sizeof(sockaddr_in6);
+  } else
+#endif
+  if (1) {
+    struct sockaddr_in *tmpaddr = (struct sockaddr_in *)&serveraddr;
+    memset((char *) tmpaddr, 0, sizeof(struct sockaddr_in));
+    tmpaddr->sin_family = AF_INET;
+    tmpaddr->sin_port = htons(server_port);
+    tmpaddr->sin_addr.s_addr = (in_addr_t)address;
+    sock_size = sizeof(sockaddr_in);
+  }
+  //AddLog(LOG_LEVEL_DEBUG, "WiFiUDP46::begin udp_server=%p sock_addr=%p sock_size=%i", udp_server, sock_addr, sock_size);
+  if(bind(udp_server , (sockaddr*)&serveraddr, sock_size) == -1){
     log_e("could not bind socket: %d", errno);
     stop();
     return 0;
   }
+
   fcntl(udp_server, F_SETFL, O_NONBLOCK);
   return 1;
 }
@@ -80,7 +103,7 @@ uint8_t WiFiUDP::begin(uint16_t p){
 
 uint8_t WiFiUDP::beginMulticast(IPAddress a, uint16_t p){
   if(begin(IPAddress(INADDR_ANY), p)){
-    if(a != 0){
+    if(!ip_addr_isany((ip_addr_t*)a)){
       struct ip_mreq mreq;
       mreq.imr_multiaddr.s_addr = (in_addr_t)a;
       mreq.imr_interface.s_addr = INADDR_ANY;
@@ -109,11 +132,15 @@ void WiFiUDP::stop(){
   }
   if(udp_server == -1)
     return;
-  if(multicast_ip != 0){
+  if(!ip_addr_isany((ip_addr_t*)multicast_ip)){
     struct ip_mreq mreq;
     mreq.imr_multiaddr.s_addr = (in_addr_t)multicast_ip;
     mreq.imr_interface.s_addr = (in_addr_t)0;
+#if LWIP_IPV6
+    setsockopt(udp_server, IPPROTO_IPV6, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
+#else
     setsockopt(udp_server, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
+#endif
     multicast_ip = IPAddress(INADDR_ANY);
   }
   close(udp_server);
@@ -174,14 +201,28 @@ int WiFiUDP::beginPacket(const char *host, uint16_t port){
 }
 
 int WiFiUDP::endPacket(){
-  struct sockaddr_in recipient;
-  recipient.sin_addr.s_addr = (uint32_t)remote_ip;
-  recipient.sin_family = AF_INET;
-  recipient.sin_port = htons(remote_port);
-  int sent = sendto(udp_server, tx_buffer, tx_buffer_len, 0, (struct sockaddr*) &recipient, sizeof(recipient));
-  if(sent < 0){
-    log_e("could not send data: %d", errno);
-    return 0;
+
+  if (remote_ip.isV4()) {
+    struct sockaddr_in recipient;
+    recipient.sin_addr.s_addr = (uint32_t)remote_ip;
+    recipient.sin_family = AF_INET;
+    recipient.sin_port = htons(remote_port);
+    int sent = sendto(udp_server, tx_buffer, tx_buffer_len, 0, (struct sockaddr*) &recipient, sizeof(recipient));
+    if(sent < 0){
+      log_e("could not send data: %d", errno);
+      return 0;
+    }
+  } else {
+    struct sockaddr_in6 recipient;
+    recipient.sin6_flowinfo = 0;
+    recipient.sin6_addr = *(in6_addr*)(ip_addr_t*)remote_ip;
+    recipient.sin6_family = AF_INET6;
+    recipient.sin6_port = htons(remote_port);
+    int sent = sendto(udp_server, tx_buffer, tx_buffer_len, 0, (struct sockaddr*) &recipient, sizeof(recipient));
+    if(sent < 0){
+      log_e("could not send data: %d", errno);
+      return 0;
+    }
   }
   return 1;
 }
diff --git a/tools/platformio-build.py b/tools/platformio-build.py
index 39bda3a8829..5f3086e0810 100644
--- a/tools/platformio-build.py
+++ b/tools/platformio-build.py
@@ -82,52 +82,41 @@ def get_bootloader_image(variants_dir):
     return (
         variant_bootloader
         if isfile(variant_bootloader)
-        else join(
-            FRAMEWORK_DIR,
-            "tools",
-            "sdk",
-            build_mcu,
-            "bin",
-            "bootloader_${__get_board_boot_mode(__env__)}_${__get_board_f_flash(__env__)}.bin",
+        else generate_bootloader_image(
+            join(
+                FRAMEWORK_DIR,
+                "tools",
+                "sdk",
+                build_mcu,
+                "bin",
+                "bootloader_${__get_board_boot_mode(__env__)}_${__get_board_f_flash(__env__)}.elf",
+            )
         )
     )
 
 
-def get_patched_bootloader_image(original_bootloader_image, bootloader_offset):
-    patched_bootloader_image = join(env.subst("$BUILD_DIR"), "patched_bootloader.bin")
+def generate_bootloader_image(bootloader_elf):
     bootloader_cmd = env.Command(
-        patched_bootloader_image,
-        original_bootloader_image,
-        env.VerboseAction(
-            " ".join(
-                [
-                    '"$PYTHONEXE"',
-                    join(
-                        platform.get_package_dir("tool-esptoolpy") or "", "esptool.py"
-                    ),
-                    "--chip",
-                    build_mcu,
-                    "merge_bin",
-                    "-o",
-                    "$TARGET",
-                    "--flash_mode",
-                    "${__get_board_flash_mode(__env__)}",
-                    "--flash_freq",
-                    "${__get_board_f_flash(__env__)}",
-                    "--flash_size",
-                    board_config.get("upload.flash_size", "4MB"),
-                    "--target-offset",
-                    bootloader_offset,
-                    bootloader_offset,
-                    "$SOURCE",
-                ]
-            ),
-            "Updating bootloader headers",
-        ),
+        join("$BUILD_DIR", "bootloader.bin"),
+        bootloader_elf,
+        env.VerboseAction(" ".join([
+            '"$PYTHONEXE" "$OBJCOPY"',
+            "--chip", build_mcu, "elf2image",
+            "--flash_mode", "${__get_board_flash_mode(__env__)}",
+            "--flash_freq", "${__get_board_f_flash(__env__)}",
+            "--flash_size", board_config.get("upload.flash_size", "4MB"),
+            "-o", "$TARGET", "$SOURCES"
+        ]), "Building $TARGET"),
     )
+
     env.Depends("$BUILD_DIR/$PROGNAME$PROGSUFFIX", bootloader_cmd)
 
-    return patched_bootloader_image
+    # Because the Command always returns a NodeList, we have to
+    # access the first element in the list to get the Node object
+    # that actually represents the bootloader image.
+    # Also, this file is later used in generic Python code, so the
+    # Node object in converted to a generic string
+    return str(bootloader_cmd[0])
 
 
 def add_tinyuf2_extra_image():
@@ -210,34 +199,13 @@ def add_tinyuf2_extra_image():
 # Process framework extra images
 #
 
-# Starting with v2.0.4 the Arduino core contains updated bootloader images that have
-# innacurate default headers. This results in bootloops if firmware is flashed via
-# OpenOCD (e.g. debugging or uploading via debug tools). For this reason, before
-# uploading or debugging we need to adjust the bootloader binary according to
-# the values of the --flash-size and --flash-mode arguments.
-# Note: This behavior doesn't occur if uploading is done via esptoolpy, as esptoolpy
-# overrides the binary image headers before flashing.
-
-bootloader_patch_required = bool(
-    env.get("PIOFRAMEWORK", []) == ["arduino"]
-    and (
-        "debug" in env.GetBuildType()
-        or env.subst("$UPLOAD_PROTOCOL") in board_config.get("debug.tools", {})
-        or env.IsIntegrationDump()
-    )
-)
-
-bootloader_image_path = get_bootloader_image(variants_dir)
-bootloader_offset = "0x1000" if build_mcu in ("esp32", "esp32s2") else "0x0000"
-if bootloader_patch_required:
-    bootloader_image_path = get_patched_bootloader_image(
-        bootloader_image_path, bootloader_offset
-    )
-
 env.Append(
     LIBSOURCE_DIRS=[join(FRAMEWORK_DIR, "libraries")],
     FLASH_EXTRA_IMAGES=[
-        (bootloader_offset, bootloader_image_path),
+        (
+            "0x1000" if build_mcu in ("esp32", "esp32s2") else "0x0000",
+            get_bootloader_image(variants_dir),
+        ),
         ("0x8000", join(env.subst("$BUILD_DIR"), "partitions.bin")),
         ("0xe000", join(FRAMEWORK_DIR, "tools", "partitions", "boot_app0.bin")),
     ]
diff --git a/variants/wt32-eth01/pins_arduino.h b/variants/wt32-eth01/pins_arduino.h
index 7a7742254a2..6cb2a6a5d7a 100644
--- a/variants/wt32-eth01/pins_arduino.h
+++ b/variants/wt32-eth01/pins_arduino.h
@@ -54,12 +54,12 @@ static const uint8_t RX = 3;
 
 //SPI VSPI default pins
 static const uint8_t SS    = -1;
-static const uint8_t MOSI  = 15;
-static const uint8_t MISO  = 12;
-static const uint8_t SCK   = 14;
+static const uint8_t MOSI  = 14;
+static const uint8_t MISO  = 15;
+static const uint8_t SCK   = 12;
 
 //I2C default pins
-static const uint8_t SDA = 2;
-static const uint8_t SCL = 4;
+static const uint8_t SDA = 33;
+static const uint8_t SCL = 32;
 
 #endif /* Pins_Arduino_h */