diff --git a/README.md b/README.md
index 69a80386..f0023d94 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,21 @@ This firmware uses [Espressif's IDF](https://github.com/espressif/esp-idf)
 1. Load the `Tools -> SerialNINAPassthrough` example sketch on to the board
 1. Use `esptool` to flash the compiled firmware
 
+### Building with docker
+
+As an alternative for building we can use the docker image from espressif idf, we can do that as follows:
+
+```
+docker run -v $PWD:/data espressif/idf:v3.3.1 -- sh -c 'cd /data && make'
+```
+
+You can also flash the firmware with the following snippet:
+
+```
+DEVICE=/dev/ttyACM0
+docker run --device=$DEVICE -v $PWD:/data espressif/idf:v3.3.1 -- sh -c 'cd /data && make flash ESPPORT=$DEVICE'
+```
+
 ## Notes
 If updating the NINA firmware for an **Arduino UNO WiFi Rev. 2** or **Arduino Nano RP2040** board via [SerialNINAPassthrough](https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/Tools/SerialNINAPassthrough/SerialNINAPassthrough.ino) sketch, then the `esptool` invocation needs to be changed slightly:
 ```diff
diff --git a/main/CommandHandler.cpp b/main/CommandHandler.cpp
index c04a803e..88653472 100644
--- a/main/CommandHandler.cpp
+++ b/main/CommandHandler.cpp
@@ -967,7 +967,7 @@ int setEnt(const uint8_t command[], uint8_t response[])
     char password[128 + 1];
     char identity[128 + 1];
     const char* rootCA;
-    
+
     memset(username, 0x00, sizeof(username));
     memset(password, 0x00, sizeof(password));
     memset(identity, 0x00, sizeof(identity));
@@ -2070,6 +2070,659 @@ int socket_getpeername(const uint8_t command[], uint8_t response[])
   return 14;
 }
 
+/*
+ * Preferences API
+ */
+#include "Preferences.h"
+
+Preferences preferences;
+const char PREF_TAG[] = "preferences";
+
+int pref_begin(const uint8_t command[], uint8_t response[])
+{
+
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         store_name size             < 1 byte >
+  //[4..n]      store_name                  < n byte >
+  //[n+1]       readonly                    < 1 byte >
+  //[n+2]       partition label size        < 1 byte >
+  //[n+3..n+m]  partition label             < m byte >
+
+  uint8_t nargs = command[2];
+  char store_name[32];
+  char partition_label[32];
+  const uint8_t* partition_label_ptr = nullptr;
+  bool readonly=false;
+
+  // command_ptr points to the next argument, in this case
+  // it points to the length of store_name string
+  const uint8_t* command_ptr = &command[3];
+
+  if(nargs < 1 && nargs > 3) {
+    ESP_LOGE(PREF_TAG, "Prefrences begin wrong number of arguments");
+    response[4] = 255;
+    goto error;
+  }
+
+  memset(store_name, 0x00, sizeof(store_name));
+  memcpy(store_name, command_ptr+1, *command_ptr);
+  store_name[*command_ptr] = '\0';
+
+  // move the pointer to the next argument, by adding the length
+  // of store_name string
+  command_ptr += *command_ptr + 1;
+
+  if(nargs > 1) {
+    command_ptr++; // the first byte contains the length (that is 1) of the next byte
+    readonly = *command_ptr;
+    command_ptr++;
+  }
+
+  if(nargs > 2) {
+    memset(partition_label, 0x00, sizeof(partition_label));
+    memcpy(partition_label, command_ptr+1, *command_ptr);
+    partition_label[*command_ptr] = '\0';
+
+    partition_label_ptr = command_ptr;
+  }
+
+  response[4] = preferences.begin(store_name, readonly, (char*)partition_label_ptr) ? 0 : 1;
+
+error:
+  response[2] = 1;          // number of parameters
+  response[3] = 1;          // length of first parameter
+
+  return 6;
+}
+
+int pref_end(const uint8_t command[], uint8_t response[])
+{
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+
+  preferences.end();
+
+  response[2] = 1;          // number of parameters
+  response[3] = 1;          // length of first parameter
+  response[4] = 1;
+
+  return 6;
+}
+
+int pref_clear(const uint8_t command[], uint8_t response[])
+{
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+
+  response[2] = 1;                   // number of parameters
+  response[3] = 1;                   // length of first parameter
+  response[4] = preferences.clear() ? 0 : 1; // result of Preferences clear operation
+
+  // response has to start ad position 2, and has to take into account
+  // 0xee that is put after the function being called
+  return 6;
+}
+
+int pref_remove(const uint8_t command[], uint8_t response[])
+{
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         key size                    < 1 byte >
+  //[4..n]      key                         < n byte >
+
+  uint8_t nargs = command[2];
+  char key[16];
+
+  // command_ptr points to the next argument, in this case
+  // it points to the length of key string
+  const uint8_t* command_ptr = &command[3];
+
+  if(nargs != 1) {
+    ESP_LOGE(PREF_TAG, "Prefrences remove wrong number of arguments");
+    response[4] = 255;
+    goto error;
+  }
+
+  memset(key, 0x00, sizeof(key));
+  memcpy(key, command_ptr+1, *command_ptr);
+  key[*command_ptr] = '\0';
+
+  response[4] = preferences.remove(key) ? 0 : 1; // result of Preferences end operation
+error:
+  response[2] = 1;                 // number of parameters
+  response[3] = 1;                 // length of first parameter
+
+  // response has to start ad position 2, and has to take into account
+  // 0xee that is put after the function being called
+  return 6;
+}
+
+int pref_len(const uint8_t command[], uint8_t response[])
+{
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         key size                    < 1 byte >
+  //[4..n]      key                         < n byte >
+
+  uint8_t nargs = command[2];
+  char key[16];
+
+  // command_ptr points to the next argument, in this case
+  // it points to the length of key string
+  const uint8_t* command_ptr = &command[3];
+
+  // restricting the return as 32 bit integer as it is enough
+  uint32_t len = 0;
+
+  if(nargs != 1) {
+    ESP_LOGE(PREF_TAG, "Prefrences length wrong number of arguments");
+    response[2] = 1;
+    response[3] = 1;
+    response[4] = 255;
+    return 6;
+  }
+
+  memset(key, 0x00, sizeof(key));
+  memcpy(key, command_ptr+1, *command_ptr);
+  key[*command_ptr] = '\0';
+
+  len = preferences.getBytesLength(key);
+
+  response[2] = 1;          // number of parameters
+  response[3] = 4;          // length of first parameter
+
+  // write the result in big endian into the response buffer
+  response[4] = (len >> 0)  & 0xff;
+  response[5] = (len >> 8)  & 0xff;
+  response[6] = (len >> 16) & 0xff;
+  response[7] = (len >> 24) & 0xff;
+
+  return 9;
+}
+
+int pref_stat(const uint8_t command[], uint8_t response[])
+{
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+
+  // restricting the return as 32 bit integer as it is enough
+  uint32_t res = 0;
+
+  res = preferences.freeEntries();
+
+  response[2] = 1;          // number of parameters
+  response[3] = 4;          // length of first parameter
+
+  // write the result in big endian into the response buffer
+  response[4] = (res >> 0)  & 0xff;
+  response[5] = (res >> 8)  & 0xff;
+  response[6] = (res >> 16) & 0xff;
+  response[7] = (res >> 24) & 0xff;
+
+  return 9;
+}
+
+int pref_put(const uint8_t command[], uint8_t response[])
+{
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         key size                    < 1 byte >
+  //[4..n]      key                         < n byte >
+  //[n]         type                        < 1 byte >
+  //[n+1]       len                         < 1 byte >
+  //[n+2..sizeof(type)]                     < sizeof(type)
+  //            value                         or len byte >
+
+  uint8_t nargs = command[2];
+  char key[16];
+  uint16_t len;
+
+  // we are going to store the value (if not array type) in a 64 bit integer, because it is easier to handle
+  uint64_t value=0;
+
+  // command_ptr points to the next argument, in this case
+  // it points to the length of key string
+  const uint8_t* command_ptr = &command[3];
+
+  // restricting the return as 32 bit integer as it is enough
+  size_t res = 0;
+
+  if(nargs != 3) {
+    ESP_LOGE(PREF_TAG, "Prefrences put wrong number of arguments");
+    response[2] = 1;
+    response[3] = 1;
+    response[4] = 255;
+    return 6;
+  }
+
+  memset(key, 0x00, sizeof(key));
+  memcpy(key, command_ptr+1, *command_ptr);
+  key[*command_ptr] = '\0';
+
+  // next argument
+  command_ptr += *command_ptr + 1;
+
+  command_ptr++; // The first byte contains the length of the parameter, which is 1
+  PreferenceType type = (PreferenceType)*command_ptr;
+  command_ptr++;
+
+  // extract length
+  len = command_ptr[0]<<8 | command_ptr[1];
+  command_ptr+=2;
+
+  // extract value convert from bigendian, TODO not forr array types
+  for(uint8_t i=0; i<len && type != PT_BLOB && type != PT_STR; i++) {
+    value |= (((uint64_t)command_ptr[i]) << (i << 3));
+  }
+
+  switch(type) {
+    case PT_I8:
+      res = preferences.putChar(key, static_cast<int8_t>(value));
+      break;
+    case PT_U8:
+      res = preferences.putUChar(key, static_cast<uint8_t>(value));
+      break;
+    case PT_I16:
+      res = preferences.putShort(key, static_cast<int16_t>(value));
+      break;
+    case PT_U16:
+      res = preferences.putUShort(key, static_cast<uint16_t>(value));
+      break;
+    case PT_I32:
+      res = preferences.putInt(key, static_cast<int32_t>(value));
+      break;
+    case PT_U32:
+      res = preferences.putUInt(key, static_cast<uint32_t>(value));
+      break;
+    case PT_I64:
+      res = preferences.putLong64(key, static_cast<int64_t>(value));
+      break;
+    case PT_U64:
+      res = preferences.putULong64(key, static_cast<uint64_t>(value));
+      break;
+    case PT_STR:
+      // for simplicity we send the string null terminated from the client side
+      res = preferences.putString(key, (const char*)command_ptr);
+      break;
+    case PT_BLOB:
+      ets_printf("put bytes \n");
+      res = preferences.putBytes(key, command_ptr, len);
+      break;
+    case PT_INVALID:
+    default:
+      ESP_LOGE(PREF_TAG, "Prefrences put invalid type");
+      response[2] = 1;
+      response[3] = 1;
+      response[4] = 254;
+      return 6;
+  }
+
+  response[2] = 1; // response nargs
+  response[3] = 4;          // length of first parameter
+
+  response[4] = (res >> 0)  & 0xff;
+  response[5] = (res >> 8)  & 0xff;
+  response[6] = (res >> 16) & 0xff;
+  response[7] = (res >> 24) & 0xff;
+
+  return 9;
+}
+
+int pref_get(const uint8_t command[], uint8_t response[])
+{
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         key size                    < 1 byte >
+  //[4..n]      key                         < n byte >
+  //[n]         type                        < 1 byte >
+
+  uint8_t nargs = command[2];
+  char key[16];
+
+  // command_ptr points to the next argument, in this case
+  // it points to the length of key string
+  const uint8_t* command_ptr = &command[3];
+
+  // restricting the return as 32 bit integer as it is enough
+  uint32_t res_size = 0;
+
+  // all the kind of values can fit in a 64 bit integer
+  uint32_t res=0;
+
+  if(nargs != 2) {
+    ESP_LOGE(PREF_TAG, "Prefrences put wrong number of arguments");
+    response[2] = 1;
+    response[3] = 0;
+    return 5;
+  }
+
+  memset(key, 0x00, sizeof(key));
+  memcpy(key, command_ptr+1, *command_ptr);
+  key[*command_ptr] = '\0';
+
+  // next argument
+  command_ptr += *command_ptr + 1;
+
+  command_ptr++; // The first byte contains the length of the parameter, which is 1
+  PreferenceType type = static_cast<PreferenceType>(*command_ptr);
+
+  command_ptr++;
+
+  switch(type) {
+    case PT_I8:
+      res = static_cast<int8_t>(preferences.getChar(key));
+      res_size = 1;
+      break;
+    case PT_U8:
+      res = static_cast<uint8_t>(preferences.getUChar(key));
+      res_size = 1;
+      break;
+    case PT_I16:
+      res = static_cast<int16_t>(preferences.getShort(key));
+      res_size = 2;
+      break;
+    case PT_U16:
+      res_size = 2;
+      res = static_cast<uint16_t>(preferences.getUShort(key));
+      break;
+    case PT_I32:
+      res_size = 4;
+      res = static_cast<int32_t>(preferences.getInt(key));
+      break;
+    case PT_U32:
+      res_size = 4;
+      res = static_cast<uint32_t>(preferences.getUInt(key));
+      break;
+    case PT_I64:
+      res_size = 8;
+      res = static_cast<int64_t>(preferences.getLong64(key));
+      break;
+    case PT_U64:
+      res_size = 8;
+      res = static_cast<uint64_t>(preferences.getULong64(key));
+      break;
+    case PT_STR:
+      res_size = preferences.getString(key, (char*) &response[5], SPI_MAX_DMA_LEN - 8);
+      goto array_return;
+    case PT_BLOB:
+      res_size = preferences.getBytes(key, &response[5], SPI_MAX_DMA_LEN - 8);
+      goto array_return;
+    case PT_INVALID:
+    default:
+      ESP_LOGE(PREF_TAG, "Prefrences put invalid type");
+      response[2] = 1;
+      response[3] = 0;
+      return 5;
+  }
+
+  // fill the response buffer
+  for(uint8_t i=0; i<res_size; i++) {
+    response[5+i] = (res >> ((res_size-i-1) << 3)) & 0xff;
+  }
+
+array_return:
+
+  response[2] = 1; // the number of parameters
+
+  // the next 2 bytes are the size of the returned value. Since the client api support length with only 2 bytes
+  // we can return string and blobs up to that size
+  response[3] = (res_size >> 8)  & 0xff; // readParamLen16 wants little endian length
+  response[4] = (res_size >> 0)  & 0xff;
+
+  return 6 + res_size;
+}
+
+int pref_getType(const uint8_t command[], uint8_t response[]) {
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         key size                    < 1 byte >
+  //[4..n]      key                         < n byte >
+
+  uint8_t nargs = command[2];
+  char key[16];
+
+  // command_ptr points to the next argument, in this case
+  // it points to the length of key string
+  const uint8_t* command_ptr = &command[3];
+
+  memset(key, 0x00, sizeof(key));
+  memcpy(key, command_ptr+1, *command_ptr);
+  key[*command_ptr] = '\0';
+
+
+  response[2] = 1; // response nargs
+  response[3] = 1; // response nargs
+  response[4] = preferences.getType(key);
+
+  return 6;
+}
+
+/*
+ * BLE vHCI API
+ */
+#include "esp_bt.h"
+
+#define TO_HOST_BUF_SIZE                256 // bytes
+static RingbufHandle_t buf_handle       = NULL;
+static SemaphoreHandle_t vhci_send_sem  = NULL;
+
+static void controller_rcv_pkt_ready() {
+	if (vhci_send_sem) {
+		xSemaphoreGive(vhci_send_sem);
+  }
+}
+
+/*
+ * The following callback is called when the bt controller has some data available
+ * this data is put into a queue that is then consumed by calling ble_read
+ */
+static int host_rcv_pkt(uint8_t *data, uint16_t len) {
+  if(buf_handle == NULL) {
+    ets_printf("failed host_rcv_pkt\n");
+    return ESP_FAIL;
+  }
+
+  UBaseType_t res = xRingbufferSend(buf_handle, data, len, pdMS_TO_TICKS(2000)); // TODO verify xTicksToWait value
+
+  if (res != pdTRUE) {
+    ets_printf("unable to send data to ring buffer\n");
+  }
+  return ESP_OK;
+}
+
+static esp_bt_controller_config_t btControllerConfig = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+static esp_vhci_host_callback_t vhciHostCb = {
+	controller_rcv_pkt_ready,
+	host_rcv_pkt
+};
+
+int ble_begin(const uint8_t command[], uint8_t response[]) {
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+
+  esp_err_t ret = ESP_OK;
+
+  if((ret = esp_bt_controller_init(&btControllerConfig)) != ESP_OK) {
+    ets_printf("failed esp_bt_controller_init %s\n", esp_err_to_name(ret));
+
+    goto exit;
+  }
+
+  while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE);
+
+  if((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) {
+    ets_printf("failed esp_bt_controller_enable %s\n", esp_err_to_name(ret));
+
+    goto exit;
+  }
+
+  if((buf_handle = xRingbufferCreate(TO_HOST_BUF_SIZE, RINGBUF_TYPE_BYTEBUF)) == NULL) {
+    ret = ESP_ERR_NO_MEM;
+    ets_printf("failed xRingbufferCreate\n");
+
+    goto exit;
+  }
+
+  vhci_send_sem = xSemaphoreCreateBinary();
+	if (vhci_send_sem == NULL) {
+		ets_printf("Failed to create VHCI send sem\n");
+		ret =  ESP_ERR_NO_MEM;
+    goto exit;
+	}
+  xSemaphoreGive(vhci_send_sem);
+
+  esp_bt_sleep_enable();
+
+  esp_vhci_host_register_callback(&vhciHostCb);
+
+exit:
+  response[2] = 1;          // number of parameters
+  response[3] = 1;          // length of first parameter
+  response[4] = ret;
+
+  return 6;
+}
+
+int ble_end(const uint8_t command[], uint8_t response[]) {
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+
+	esp_bt_controller_disable();
+	esp_bt_controller_deinit();
+
+  if(buf_handle != NULL) {
+    vRingbufferDelete(buf_handle);
+  }
+
+  if (vhci_send_sem != NULL) {
+		/* Dummy take and give sema before deleting it */
+		xSemaphoreTake(vhci_send_sem, pdMS_TO_TICKS(2000));
+		xSemaphoreGive(vhci_send_sem);
+		vSemaphoreDelete(vhci_send_sem);
+		vhci_send_sem = NULL;
+	}
+
+  response[2] = 1;          // number of parameters
+  response[3] = 1;          // length of first parameter
+  response[4] = 1;
+
+  return 6;
+}
+
+int ble_available(const uint8_t command[], uint8_t response[]) {
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  uint16_t available = 0;
+  if(buf_handle != NULL) {
+    available = TO_HOST_BUF_SIZE - xRingbufferGetCurFreeSize(buf_handle);
+  }
+
+  response[2] = 1;          // number of parameters
+  response[3] = 2;          // length of first parameter
+  response[4] = (available >> 8)  & 0xff;
+  response[5] = (available >> 0)  & 0xff;
+
+  return 7;
+}
+
+int ble_peek(const uint8_t command[], uint8_t response[]) {
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         the number 2                < 1 byte >
+  //[4..5]      size                        < 2 byte >
+  // this could be useless xQueuePeek
+  uint8_t nargs = command[2];
+  // if nargs != 1 -> error
+  size_t res = 0;
+  // uint16_t size = ntohs(*((uint16_t *) &command[4]));
+  uint16_t size = *((uint16_t *) &command[4]);
+  uint8_t* received = nullptr;
+
+  if(size > TO_HOST_BUF_SIZE - xRingbufferGetCurFreeSize(buf_handle)) {
+    size = 0;
+    goto exit;
+  }
+
+  received = (uint8_t*)xRingbufferReceiveUpTo(buf_handle, &res, pdMS_TO_TICKS(2000), size);
+
+  memcpy(&response[5], received, res);
+
+exit:
+  response[2] = 1;          // number of parameters
+  response[3] = (size >> 8)  & 0xff;
+  response[4] = (size >> 0)  & 0xff;
+
+  return 6 + res;
+}
+
+int ble_read(const uint8_t command[], uint8_t response[]) {
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3]         the number 2                < 1 byte >
+  //[4..5]      size                        < 2 byte >
+  uint8_t nargs = command[2];
+  // if nargs != 1 -> error
+  size_t res = 0;
+  // uint16_t size = ntohs(*((uint16_t *) &command[4]));
+  uint16_t size = *((uint16_t *) &command[4]);
+  uint8_t* received = nullptr;
+
+  if(size > TO_HOST_BUF_SIZE - xRingbufferGetCurFreeSize(buf_handle)) {
+    size = 0;
+    goto exit;
+  }
+
+  received = (uint8_t*)xRingbufferReceiveUpTo(buf_handle, &res, pdMS_TO_TICKS(2000), size);
+
+  memcpy(&response[5], received, res);
+
+  vRingbufferReturnItem(buf_handle, received);
+
+exit:
+  response[2] = 1;          // number of parameters
+  response[3] = (size >> 8)  & 0xff;
+  response[4] = (size >> 0)  & 0xff;
+
+  return 6 + res;
+}
+
+int ble_write(const uint8_t command[], uint8_t response[]) {
+  //[0]         CMD_START                   < 0xE0   >
+  //[1]         Command                     < 1 byte >
+  //[2]         N args                      < 1 byte >
+  //[3..4]      size                        < 2 byte >
+  //[4..4+size] buffer                      < size byte >
+
+  uint8_t nargs = command[2];
+  // if nargs != 1 -> error
+
+  uint16_t size = ntohs(*((uint16_t *) &command[3]));
+
+  while(!esp_vhci_host_check_send_available()) { // TODO add timeout
+    // TODO delay
+  }
+
+  if (vhci_send_sem && xSemaphoreTake(vhci_send_sem, pdMS_TO_TICKS(2000)) == pdTRUE) {
+    esp_vhci_host_send_packet((uint8_t*)&command[5], size);
+	}
+
+  response[2] = 1;          // number of parameters
+  response[3] = 2;          // length of first parameter
+  response[4] = (size >> 0)  & 0xff;
+  response[5] = (size >> 8)  & 0xff;
+
+  return 7;
+}
+
 typedef int (*CommandHandlerType)(const uint8_t command[], uint8_t response[]);
 
 const CommandHandlerType commandHandlers[] = {
@@ -2086,10 +2739,30 @@ const CommandHandlerType commandHandlers[] = {
   disconnect, NULL, getIdxRSSI, getIdxEnct, reqHostByName, getHostByName, startScanNetworks, getFwVersion, NULL, sendUDPdata, getRemoteData, getTime, getIdxBSSID, getIdxChannel, ping, getSocket,
 
   // 0x40 -> 0x4f
-  setEnt, NULL, NULL, NULL, sendDataTcp, getDataBufTcp, insertDataBuf, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-
-  // 0x50 -> 0x5f
-  setPinMode, setDigitalWrite, setAnalogWrite, getDigitalRead, getAnalogRead, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  setEnt, NULL, NULL, NULL, sendDataTcp, getDataBufTcp, insertDataBuf, NULL, NULL, NULL,
+
+  // BLE functions 0x4a -> 0x4f
+  ble_begin,      // 0x4a
+  ble_end,        // 0x4b
+  ble_available,  // 0x4c
+  ble_peek,       // 0x4d
+  ble_read,       // 0x4e
+  ble_write,      // 0x4f
+
+  // 0x50 -> 0x54
+  setPinMode, setDigitalWrite, setAnalogWrite, getDigitalRead, getAnalogRead,
+
+  // KVStore functions 0x55 -> 0x87
+  pref_begin,       // 0x55
+  pref_end,         // 0x56
+  pref_clear,       // 0x57
+  pref_remove,      // 0x58
+  pref_len,         // 0x59
+  pref_stat,        // 0x5A
+  pref_put,         // 0x5B
+  pref_get,         // 0x5C
+  pref_getType,     // 0x5D
+  NULL, NULL,
 
   // 0x60 -> 0x6f
   writeFile, readFile, deleteFile, existsFile, downloadFile,  applyOTA, renameFile, downloadOTA, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
@@ -2239,7 +2912,7 @@ void CommandHandlerClass::handleWiFiDisconnect()
   // close all non-listening sockets
 
   for (int i = 0; i < CONFIG_LWIP_MAX_SOCKETS; i++) {
-    struct sockaddr_in addr; 
+    struct sockaddr_in addr;
     size_t addrLen = sizeof(addr);
     int socket = LWIP_SOCKET_OFFSET + i;
 
diff --git a/main/Preferences.cpp b/main/Preferences.cpp
new file mode 100644
index 00000000..53f576f1
--- /dev/null
+++ b/main/Preferences.cpp
@@ -0,0 +1,535 @@
+// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Preferences.h"
+
+#include "nvs.h"
+#include "nvs_flash.h"
+#include "esp_log.h"
+
+const char * nvs_errors[] = { "OTHER", "NOT_INITIALIZED", "NOT_FOUND", "TYPE_MISMATCH", "READ_ONLY", "NOT_ENOUGH_SPACE", "INVALID_NAME", "INVALID_HANDLE", "REMOVE_FAILED", "KEY_TOO_LONG", "PAGE_FULL", "INVALID_STATE", "INVALID_LENGTH"};
+#define nvs_error(e) (((e)>ESP_ERR_NVS_BASE)?nvs_errors[(e)&~(ESP_ERR_NVS_BASE)]:nvs_errors[0])
+
+Preferences::Preferences()
+    :_handle(0)
+    ,_started(false)
+    ,_readOnly(false)
+{}
+
+Preferences::~Preferences(){
+    end();
+}
+
+bool Preferences::begin(const char * name, bool readOnly, const char* partition_label){
+    if(_started){
+        return false;
+    }
+    _readOnly = readOnly;
+    esp_err_t err = ESP_OK;
+    if (partition_label != NULL) {
+        err = nvs_flash_init_partition(partition_label);
+        if (err) {
+            ESP_LOGE(__FILE__, "nvs_flash_init_partition failed: %s", nvs_error(err));
+            return false;
+        }
+        err = nvs_open_from_partition(partition_label, name, readOnly ? NVS_READONLY : NVS_READWRITE, &_handle);
+    } else {
+        err = nvs_open(name, readOnly ? NVS_READONLY : NVS_READWRITE, &_handle);
+    }
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_open failed: %s", nvs_error(err));
+        return false;
+    }
+    _started = true;
+    return true;
+}
+
+void Preferences::end(){
+    if(!_started){
+        return;
+    }
+    nvs_close(_handle);
+    _started = false;
+}
+
+/*
+ * Clear all keys in opened preferences
+ * */
+
+bool Preferences::clear(){
+    if(!_started || _readOnly){
+        return false;
+    }
+    esp_err_t err = nvs_erase_all(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_erase_all fail: %s", nvs_error(err));
+        return false;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s", nvs_error(err));
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Remove a key
+ * */
+
+bool Preferences::remove(const char * key){
+    if(!_started || !key || _readOnly){
+        return false;
+    }
+    esp_err_t err = nvs_erase_key(_handle, key);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_erase_key fail: %s %s", key, nvs_error(err));
+        return false;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Put a key value
+ * */
+
+size_t Preferences::putChar(const char* key, int8_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_i8(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_i8 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 1;
+}
+
+size_t Preferences::putUChar(const char* key, uint8_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_u8(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_u8 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 1;
+}
+
+size_t Preferences::putShort(const char* key, int16_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_i16(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_i16 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 2;
+}
+
+size_t Preferences::putUShort(const char* key, uint16_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_u16(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_u16 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 2;
+}
+
+size_t Preferences::putInt(const char* key, int32_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_i32(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_i32 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 4;
+}
+
+size_t Preferences::putUInt(const char* key, uint32_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_u32(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_u32 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 4;
+}
+
+size_t Preferences::putLong(const char* key, int32_t value){
+    return putInt(key, value);
+}
+
+size_t Preferences::putULong(const char* key, uint32_t value){
+    return putUInt(key, value);
+}
+
+size_t Preferences::putLong64(const char* key, int64_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_i64(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_i64 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 8;
+}
+
+size_t Preferences::putULong64(const char* key, uint64_t value){
+    if(!_started || !key || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_u64(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_u64 fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return 8;
+}
+
+size_t Preferences::putFloat(const char* key, const float_t value){
+    return putBytes(key, (void*)&value, sizeof(float_t));
+}
+
+size_t Preferences::putDouble(const char* key, const double_t value){
+    return putBytes(key, (void*)&value, sizeof(double_t));
+}
+
+size_t Preferences::putBool(const char* key, const bool value){
+    return putUChar(key, (uint8_t) (value ? 1 : 0));
+}
+
+size_t Preferences::putString(const char* key, const char* value){
+    if(!_started || !key || !value || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_str(_handle, key, value);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_str fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return strlen(value);
+}
+
+size_t Preferences::putString(const char* key, const String value){
+    return putString(key, value.c_str());
+}
+
+size_t Preferences::putBytes(const char* key, const void* value, size_t len){
+    if(!_started || !key || !value || !len || _readOnly){
+        return 0;
+    }
+    esp_err_t err = nvs_set_blob(_handle, key, value, len);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_set_blob fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    err = nvs_commit(_handle);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return len;
+}
+
+PreferenceType Preferences::getType(const char* key) {
+    if(!_started || !key || strlen(key)>15){
+        return PT_INVALID;
+    }
+    int8_t mt1; uint8_t mt2; int16_t mt3; uint16_t mt4;
+    int32_t mt5; uint32_t mt6; int64_t mt7; uint64_t mt8;
+    size_t len = 0;
+    if(nvs_get_i8(_handle, key, &mt1) == ESP_OK) return PT_I8;
+    if(nvs_get_u8(_handle, key, &mt2) == ESP_OK) return PT_U8;
+    if(nvs_get_i16(_handle, key, &mt3) == ESP_OK) return PT_I16;
+    if(nvs_get_u16(_handle, key, &mt4) == ESP_OK) return PT_U16;
+    if(nvs_get_i32(_handle, key, &mt5) == ESP_OK) return PT_I32;
+    if(nvs_get_u32(_handle, key, &mt6) == ESP_OK) return PT_U32;
+    if(nvs_get_i64(_handle, key, &mt7) == ESP_OK) return PT_I64;
+    if(nvs_get_u64(_handle, key, &mt8) == ESP_OK) return PT_U64;
+    if(nvs_get_str(_handle, key, NULL, &len) == ESP_OK) return PT_STR;
+    if(nvs_get_blob(_handle, key, NULL, &len) == ESP_OK) return PT_BLOB;
+    return PT_INVALID;
+}
+
+bool Preferences::isKey(const char* key) {
+    return getType(key) != PT_INVALID;
+}
+
+/*
+ * Get a key value
+ * */
+
+int8_t Preferences::getChar(const char* key, const int8_t defaultValue){
+    int8_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_i8(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_i8 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+uint8_t Preferences::getUChar(const char* key, const uint8_t defaultValue){
+    uint8_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_u8(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_u8 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+int16_t Preferences::getShort(const char* key, const int16_t defaultValue){
+    int16_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_i16(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_i16 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+uint16_t Preferences::getUShort(const char* key, const uint16_t defaultValue){
+    uint16_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_u16(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_u16 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+int32_t Preferences::getInt(const char* key, const int32_t defaultValue){
+    int32_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_i32(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_i32 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+uint32_t Preferences::getUInt(const char* key, const uint32_t defaultValue){
+    uint32_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_u32(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_u32 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+int32_t Preferences::getLong(const char* key, const int32_t defaultValue){
+    return getInt(key, defaultValue);
+}
+
+uint32_t Preferences::getULong(const char* key, const uint32_t defaultValue){
+    return getUInt(key, defaultValue);
+}
+
+int64_t Preferences::getLong64(const char* key, const int64_t defaultValue){
+    int64_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_i64(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_i64 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+uint64_t Preferences::getULong64(const char* key, const uint64_t defaultValue){
+    uint64_t value = defaultValue;
+    if(!_started || !key){
+        return value;
+    }
+    esp_err_t err = nvs_get_u64(_handle, key, &value);
+    if(err){
+        ESP_LOGV(__FILE__, "nvs_get_u64 fail: %s %s", key, nvs_error(err));
+    }
+    return value;
+}
+
+float_t Preferences::getFloat(const char* key, const float_t defaultValue) {
+    float_t value = defaultValue;
+    getBytes(key, (void*) &value, sizeof(float_t));
+    return value;
+}
+
+double_t Preferences::getDouble(const char* key, const double_t defaultValue) {
+    double_t value = defaultValue;
+    getBytes(key, (void*) &value, sizeof(double_t));
+    return value;
+}
+
+bool Preferences::getBool(const char* key, const bool defaultValue) {
+    return getUChar(key, defaultValue ? 1 : 0) == 1;
+}
+
+size_t Preferences::getString(const char* key, char* value, const size_t maxLen){
+    size_t len = 0;
+    if(!_started || !key || !value || !maxLen){
+        return 0;
+    }
+    esp_err_t err = nvs_get_str(_handle, key, NULL, &len);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_get_str len fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    if(len > maxLen){
+        ESP_LOGE(__FILE__, "not enough space in value: %u < %u", maxLen, len);
+        return 0;
+    }
+    err = nvs_get_str(_handle, key, value, &len);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_get_str fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return len;
+}
+
+String Preferences::getString(const char* key, const String defaultValue){
+    char * value = NULL;
+    size_t len = 0;
+    if(!_started || !key){
+        return String(defaultValue);
+    }
+    esp_err_t err = nvs_get_str(_handle, key, value, &len);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_get_str len fail: %s %s", key, nvs_error(err));
+        return String(defaultValue);
+    }
+    char buf[len];
+    value = buf;
+    err = nvs_get_str(_handle, key, value, &len);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_get_str fail: %s %s", key, nvs_error(err));
+        return String(defaultValue);
+    }
+    return String(buf);
+}
+
+size_t Preferences::getBytesLength(const char* key){
+    size_t len = 0;
+    if(!_started || !key){
+        return 0;
+    }
+    esp_err_t err = nvs_get_blob(_handle, key, NULL, &len);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_get_blob len fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return len;
+}
+
+size_t Preferences::getBytes(const char* key, void * buf, size_t maxLen){
+    size_t len = getBytesLength(key);
+    if(!len || !buf || !maxLen){
+        return len;
+    }
+    if(len > maxLen){
+        ESP_LOGE(__FILE__, "not enough space in buffer: %u < %u", maxLen, len);
+        return 0;
+    }
+    esp_err_t err = nvs_get_blob(_handle, key, buf, &len);
+    if(err){
+        ESP_LOGE(__FILE__, "nvs_get_blob fail: %s %s", key, nvs_error(err));
+        return 0;
+    }
+    return len;
+}
+
+size_t Preferences::freeEntries() {
+    nvs_stats_t nvs_stats;
+    esp_err_t err = nvs_get_stats(NULL, &nvs_stats);
+    if(err){
+        ESP_LOGE(__FILE__, "Failed to get nvs statistics");
+        return 0;
+    }
+    return nvs_stats.free_entries;
+}
diff --git a/main/Preferences.h b/main/Preferences.h
new file mode 100644
index 00000000..5dbcbd4c
--- /dev/null
+++ b/main/Preferences.h
@@ -0,0 +1,77 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _PREFERENCES_H_
+#define _PREFERENCES_H_
+
+#include "Arduino.h"
+
+typedef enum {
+    PT_I8, PT_U8, PT_I16, PT_U16, PT_I32, PT_U32, PT_I64, PT_U64, PT_STR, PT_BLOB, PT_INVALID
+} PreferenceType;
+
+class Preferences {
+    protected:
+        uint32_t _handle;
+        bool _started;
+        bool _readOnly;
+    public:
+        Preferences();
+        ~Preferences();
+
+        bool begin(const char * name, bool readOnly=false, const char* partition_label=NULL);
+        void end();
+
+        bool clear();
+        bool remove(const char * key);
+
+        size_t putChar(const char* key, int8_t value);
+        size_t putUChar(const char* key, uint8_t value);
+        size_t putShort(const char* key, int16_t value);
+        size_t putUShort(const char* key, uint16_t value);
+        size_t putInt(const char* key, int32_t value);
+        size_t putUInt(const char* key, uint32_t value);
+        size_t putLong(const char* key, int32_t value);
+        size_t putULong(const char* key, uint32_t value);
+        size_t putLong64(const char* key, int64_t value);
+        size_t putULong64(const char* key, uint64_t value);
+        size_t putFloat(const char* key, float_t value);
+        size_t putDouble(const char* key, double_t value);
+        size_t putBool(const char* key, bool value);
+        size_t putString(const char* key, const char* value);
+        size_t putString(const char* key, String value);
+        size_t putBytes(const char* key, const void* value, size_t len);
+
+        bool isKey(const char* key);
+        PreferenceType getType(const char* key);
+        int8_t getChar(const char* key, int8_t defaultValue = 0);
+        uint8_t getUChar(const char* key, uint8_t defaultValue = 0);
+        int16_t getShort(const char* key, int16_t defaultValue = 0);
+        uint16_t getUShort(const char* key, uint16_t defaultValue = 0);
+        int32_t getInt(const char* key, int32_t defaultValue = 0);
+        uint32_t getUInt(const char* key, uint32_t defaultValue = 0);
+        int32_t getLong(const char* key, int32_t defaultValue = 0);
+        uint32_t getULong(const char* key, uint32_t defaultValue = 0);
+        int64_t getLong64(const char* key, int64_t defaultValue = 0);
+        uint64_t getULong64(const char* key, uint64_t defaultValue = 0);
+        float_t getFloat(const char* key, float_t defaultValue = NAN);
+        double_t getDouble(const char* key, double_t defaultValue = NAN);
+        bool getBool(const char* key, bool defaultValue = false);
+        size_t getString(const char* key, char* value, size_t maxLen);
+        String getString(const char* key, String defaultValue = String());
+        size_t getBytesLength(const char* key);
+        size_t getBytes(const char* key, void * buf, size_t maxLen);
+        size_t freeEntries();
+};
+
+#endif
diff --git a/main/sketch.ino.cpp b/main/sketch.ino.cpp
index e304c498..5879b867 100644
--- a/main/sketch.ino.cpp
+++ b/main/sketch.ino.cpp
@@ -88,7 +88,6 @@ void setDebug(int d) {
 }
 
 void setupWiFi();
-void setupBluetooth();
 
 void setup() {
   setDebug(debug);
@@ -104,50 +103,11 @@ void setup() {
   digitalWrite(27, HIGH);
 #endif
 
-  pinMode(5, INPUT);
-  if (digitalRead(5) == LOW) {
-    setupBluetooth();
-  } else {
-    setupWiFi();
-  }
+  setupWiFi();
 }
 
 // #define UNO_WIFI_REV2
 
-void setupBluetooth() {
-  periph_module_enable(PERIPH_UART1_MODULE);
-  periph_module_enable(PERIPH_UHCI0_MODULE);
-
-#if defined(UNO_WIFI_REV2)
-  uart_set_pin(UART_NUM_1, 1, 3, 33, 0); // TX, RX, RTS, CTS
-#elif defined(NANO_RP2040_CONNECT)
-  uart_set_pin(UART_NUM_1, 1, 3, 33, 12); // TX, RX, RTS, CTS
-#else
-  uart_set_pin(UART_NUM_1, 23, 12, 18, 5);
-#endif
-  uart_set_hw_flow_ctrl(UART_NUM_1, UART_HW_FLOWCTRL_CTS_RTS, 5);
-
-  esp_bt_controller_config_t btControllerConfig = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); 
-
-  btControllerConfig.hci_uart_no = UART_NUM_1;
-#if defined(UNO_WIFI_REV2) || defined(NANO_RP2040_CONNECT)
-  btControllerConfig.hci_uart_baudrate = 115200;
-#else
-  btControllerConfig.hci_uart_baudrate = 912600;
-#endif
-
-  esp_bt_controller_init(&btControllerConfig);
-  while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE);
-  esp_bt_controller_enable(ESP_BT_MODE_BLE);
-  esp_bt_sleep_enable();
-
-  vTaskSuspend(NULL);
-
-  while (1) {
-    vTaskDelay(portMAX_DELAY);
-  }
-}
-
 unsigned long getTime() {
   int ret = 0;
   do {
@@ -157,7 +117,12 @@ unsigned long getTime() {
 }
 
 void setupWiFi() {
-  esp_bt_controller_mem_release(ESP_BT_MODE_BTDM);
+  esp_err_t ret = ESP_OK;
+
+  if((ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
+    ets_printf("failed esp_bt_controller_mem_release %s\n", esp_err_to_name(ret));
+  }
+
   SPIS.begin();
 
   esp_vfs_spiffs_conf_t conf = {
@@ -167,7 +132,7 @@ void setupWiFi() {
     .format_if_mount_failed = true
   };
 
-  esp_err_t ret = esp_vfs_spiffs_register(&conf);
+  ret = esp_vfs_spiffs_register(&conf);
 
   if (WiFi.status() == WL_NO_SHIELD) {
     while (1); // no shield
diff --git a/sdkconfig b/sdkconfig
index 48a88a1c..4b1c61c0 100644
--- a/sdkconfig
+++ b/sdkconfig
@@ -148,14 +148,8 @@ CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_SYNC_CONN_EFF=0
 CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE_0=y
 CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE_1=
 CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE=0
-CONFIG_BTDM_CONTROLLER_HCI_MODE_VHCI=
-CONFIG_BTDM_CONTROLLER_HCI_MODE_UART_H4=y
-
-#
-# HCI UART(H4) Options
-#
-CONFIG_BT_HCI_UART_NO=1
-CONFIG_BT_HCI_UART_BAUDRATE=115200
+CONFIG_BTDM_CONTROLLER_HCI_MODE_VHCI=y
+CONFIG_BTDM_CONTROLLER_HCI_MODE_UART_H4=
 
 #
 # MODEM SLEEP Options
@@ -174,6 +168,202 @@ CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=
 CONFIG_BLE_ADV_REPORT_FLOW_CONTROL_SUPPORTED=y
 CONFIG_BLE_ADV_REPORT_FLOW_CONTROL_NUM=100
 CONFIG_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
+CONFIG_BLUEDROID_ENABLED=y
+CONFIG_BLUEDROID_PINNED_TO_CORE_0=y
+CONFIG_BLUEDROID_PINNED_TO_CORE_1=
+CONFIG_BLUEDROID_PINNED_TO_CORE=0
+CONFIG_BTC_TASK_STACK_SIZE=3072
+CONFIG_BTU_TASK_STACK_SIZE=4096
+CONFIG_BLUEDROID_MEM_DEBUG=
+CONFIG_CLASSIC_BT_ENABLED=
+CONFIG_GATTS_ENABLE=y
+CONFIG_GATTS_SEND_SERVICE_CHANGE_MANUAL=
+CONFIG_GATTS_SEND_SERVICE_CHANGE_AUTO=y
+CONFIG_GATTS_SEND_SERVICE_CHANGE_MODE=0
+CONFIG_GATTC_ENABLE=y
+CONFIG_GATTC_CACHE_NVS_FLASH=
+CONFIG_BLE_SMP_ENABLE=y
+CONFIG_SMP_SLAVE_CON_PARAMS_UPD_ENABLE=
+CONFIG_BT_STACK_NO_LOG=
+
+#
+# BT DEBUG LOG LEVEL
+#
+CONFIG_HCI_TRACE_LEVEL_NONE=
+CONFIG_HCI_TRACE_LEVEL_ERROR=
+CONFIG_HCI_TRACE_LEVEL_WARNING=y
+CONFIG_HCI_TRACE_LEVEL_API=
+CONFIG_HCI_TRACE_LEVEL_EVENT=
+CONFIG_HCI_TRACE_LEVEL_DEBUG=
+CONFIG_HCI_TRACE_LEVEL_VERBOSE=
+CONFIG_HCI_INITIAL_TRACE_LEVEL=2
+CONFIG_BTM_TRACE_LEVEL_NONE=
+CONFIG_BTM_TRACE_LEVEL_ERROR=
+CONFIG_BTM_TRACE_LEVEL_WARNING=y
+CONFIG_BTM_TRACE_LEVEL_API=
+CONFIG_BTM_TRACE_LEVEL_EVENT=
+CONFIG_BTM_TRACE_LEVEL_DEBUG=
+CONFIG_BTM_TRACE_LEVEL_VERBOSE=
+CONFIG_BTM_INITIAL_TRACE_LEVEL=2
+CONFIG_L2CAP_TRACE_LEVEL_NONE=
+CONFIG_L2CAP_TRACE_LEVEL_ERROR=
+CONFIG_L2CAP_TRACE_LEVEL_WARNING=y
+CONFIG_L2CAP_TRACE_LEVEL_API=
+CONFIG_L2CAP_TRACE_LEVEL_EVENT=
+CONFIG_L2CAP_TRACE_LEVEL_DEBUG=
+CONFIG_L2CAP_TRACE_LEVEL_VERBOSE=
+CONFIG_L2CAP_INITIAL_TRACE_LEVEL=2
+CONFIG_RFCOMM_TRACE_LEVEL_NONE=
+CONFIG_RFCOMM_TRACE_LEVEL_ERROR=
+CONFIG_RFCOMM_TRACE_LEVEL_WARNING=y
+CONFIG_RFCOMM_TRACE_LEVEL_API=
+CONFIG_RFCOMM_TRACE_LEVEL_EVENT=
+CONFIG_RFCOMM_TRACE_LEVEL_DEBUG=
+CONFIG_RFCOMM_TRACE_LEVEL_VERBOSE=
+CONFIG_RFCOMM_INITIAL_TRACE_LEVEL=2
+CONFIG_SDP_TRACE_LEVEL_NONE=
+CONFIG_SDP_TRACE_LEVEL_ERROR=
+CONFIG_SDP_TRACE_LEVEL_WARNING=y
+CONFIG_SDP_TRACE_LEVEL_API=
+CONFIG_SDP_TRACE_LEVEL_EVENT=
+CONFIG_SDP_TRACE_LEVEL_DEBUG=
+CONFIG_SDP_TRACE_LEVEL_VERBOSE=
+CONFIG_SDP_INITIAL_TRACE_LEVEL=2
+CONFIG_GAP_TRACE_LEVEL_NONE=
+CONFIG_GAP_TRACE_LEVEL_ERROR=
+CONFIG_GAP_TRACE_LEVEL_WARNING=y
+CONFIG_GAP_TRACE_LEVEL_API=
+CONFIG_GAP_TRACE_LEVEL_EVENT=
+CONFIG_GAP_TRACE_LEVEL_DEBUG=
+CONFIG_GAP_TRACE_LEVEL_VERBOSE=
+CONFIG_GAP_INITIAL_TRACE_LEVEL=2
+CONFIG_BNEP_TRACE_LEVEL_NONE=
+CONFIG_BNEP_TRACE_LEVEL_ERROR=
+CONFIG_BNEP_TRACE_LEVEL_WARNING=y
+CONFIG_BNEP_TRACE_LEVEL_API=
+CONFIG_BNEP_TRACE_LEVEL_EVENT=
+CONFIG_BNEP_TRACE_LEVEL_DEBUG=
+CONFIG_BNEP_TRACE_LEVEL_VERBOSE=
+CONFIG_BNEP_INITIAL_TRACE_LEVEL=2
+CONFIG_PAN_TRACE_LEVEL_NONE=
+CONFIG_PAN_TRACE_LEVEL_ERROR=
+CONFIG_PAN_TRACE_LEVEL_WARNING=y
+CONFIG_PAN_TRACE_LEVEL_API=
+CONFIG_PAN_TRACE_LEVEL_EVENT=
+CONFIG_PAN_TRACE_LEVEL_DEBUG=
+CONFIG_PAN_TRACE_LEVEL_VERBOSE=
+CONFIG_PAN_INITIAL_TRACE_LEVEL=2
+CONFIG_A2D_TRACE_LEVEL_NONE=
+CONFIG_A2D_TRACE_LEVEL_ERROR=
+CONFIG_A2D_TRACE_LEVEL_WARNING=y
+CONFIG_A2D_TRACE_LEVEL_API=
+CONFIG_A2D_TRACE_LEVEL_EVENT=
+CONFIG_A2D_TRACE_LEVEL_DEBUG=
+CONFIG_A2D_TRACE_LEVEL_VERBOSE=
+CONFIG_A2D_INITIAL_TRACE_LEVEL=2
+CONFIG_AVDT_TRACE_LEVEL_NONE=
+CONFIG_AVDT_TRACE_LEVEL_ERROR=
+CONFIG_AVDT_TRACE_LEVEL_WARNING=y
+CONFIG_AVDT_TRACE_LEVEL_API=
+CONFIG_AVDT_TRACE_LEVEL_EVENT=
+CONFIG_AVDT_TRACE_LEVEL_DEBUG=
+CONFIG_AVDT_TRACE_LEVEL_VERBOSE=
+CONFIG_AVDT_INITIAL_TRACE_LEVEL=2
+CONFIG_AVCT_TRACE_LEVEL_NONE=
+CONFIG_AVCT_TRACE_LEVEL_ERROR=
+CONFIG_AVCT_TRACE_LEVEL_WARNING=y
+CONFIG_AVCT_TRACE_LEVEL_API=
+CONFIG_AVCT_TRACE_LEVEL_EVENT=
+CONFIG_AVCT_TRACE_LEVEL_DEBUG=
+CONFIG_AVCT_TRACE_LEVEL_VERBOSE=
+CONFIG_AVCT_INITIAL_TRACE_LEVEL=2
+CONFIG_AVRC_TRACE_LEVEL_NONE=
+CONFIG_AVRC_TRACE_LEVEL_ERROR=
+CONFIG_AVRC_TRACE_LEVEL_WARNING=y
+CONFIG_AVRC_TRACE_LEVEL_API=
+CONFIG_AVRC_TRACE_LEVEL_EVENT=
+CONFIG_AVRC_TRACE_LEVEL_DEBUG=
+CONFIG_AVRC_TRACE_LEVEL_VERBOSE=
+CONFIG_AVRC_INITIAL_TRACE_LEVEL=2
+CONFIG_MCA_TRACE_LEVEL_NONE=
+CONFIG_MCA_TRACE_LEVEL_ERROR=
+CONFIG_MCA_TRACE_LEVEL_WARNING=y
+CONFIG_MCA_TRACE_LEVEL_API=
+CONFIG_MCA_TRACE_LEVEL_EVENT=
+CONFIG_MCA_TRACE_LEVEL_DEBUG=
+CONFIG_MCA_TRACE_LEVEL_VERBOSE=
+CONFIG_MCA_INITIAL_TRACE_LEVEL=2
+CONFIG_HID_TRACE_LEVEL_NONE=
+CONFIG_HID_TRACE_LEVEL_ERROR=
+CONFIG_HID_TRACE_LEVEL_WARNING=y
+CONFIG_HID_TRACE_LEVEL_API=
+CONFIG_HID_TRACE_LEVEL_EVENT=
+CONFIG_HID_TRACE_LEVEL_DEBUG=
+CONFIG_HID_TRACE_LEVEL_VERBOSE=
+CONFIG_HID_INITIAL_TRACE_LEVEL=2
+CONFIG_APPL_TRACE_LEVEL_NONE=
+CONFIG_APPL_TRACE_LEVEL_ERROR=
+CONFIG_APPL_TRACE_LEVEL_WARNING=y
+CONFIG_APPL_TRACE_LEVEL_API=
+CONFIG_APPL_TRACE_LEVEL_EVENT=
+CONFIG_APPL_TRACE_LEVEL_DEBUG=
+CONFIG_APPL_TRACE_LEVEL_VERBOSE=
+CONFIG_APPL_INITIAL_TRACE_LEVEL=2
+CONFIG_GATT_TRACE_LEVEL_NONE=
+CONFIG_GATT_TRACE_LEVEL_ERROR=
+CONFIG_GATT_TRACE_LEVEL_WARNING=y
+CONFIG_GATT_TRACE_LEVEL_API=
+CONFIG_GATT_TRACE_LEVEL_EVENT=
+CONFIG_GATT_TRACE_LEVEL_DEBUG=
+CONFIG_GATT_TRACE_LEVEL_VERBOSE=
+CONFIG_GATT_INITIAL_TRACE_LEVEL=2
+CONFIG_SMP_TRACE_LEVEL_NONE=
+CONFIG_SMP_TRACE_LEVEL_ERROR=
+CONFIG_SMP_TRACE_LEVEL_WARNING=y
+CONFIG_SMP_TRACE_LEVEL_API=
+CONFIG_SMP_TRACE_LEVEL_EVENT=
+CONFIG_SMP_TRACE_LEVEL_DEBUG=
+CONFIG_SMP_TRACE_LEVEL_VERBOSE=
+CONFIG_SMP_INITIAL_TRACE_LEVEL=2
+CONFIG_BTIF_TRACE_LEVEL_NONE=
+CONFIG_BTIF_TRACE_LEVEL_ERROR=
+CONFIG_BTIF_TRACE_LEVEL_WARNING=y
+CONFIG_BTIF_TRACE_LEVEL_API=
+CONFIG_BTIF_TRACE_LEVEL_EVENT=
+CONFIG_BTIF_TRACE_LEVEL_DEBUG=
+CONFIG_BTIF_TRACE_LEVEL_VERBOSE=
+CONFIG_BTIF_INITIAL_TRACE_LEVEL=2
+CONFIG_BTC_TRACE_LEVEL_NONE=
+CONFIG_BTC_TRACE_LEVEL_ERROR=
+CONFIG_BTC_TRACE_LEVEL_WARNING=y
+CONFIG_BTC_TRACE_LEVEL_API=
+CONFIG_BTC_TRACE_LEVEL_EVENT=
+CONFIG_BTC_TRACE_LEVEL_DEBUG=
+CONFIG_BTC_TRACE_LEVEL_VERBOSE=
+CONFIG_BTC_INITIAL_TRACE_LEVEL=2
+CONFIG_OSI_TRACE_LEVEL_NONE=
+CONFIG_OSI_TRACE_LEVEL_ERROR=
+CONFIG_OSI_TRACE_LEVEL_WARNING=y
+CONFIG_OSI_TRACE_LEVEL_API=
+CONFIG_OSI_TRACE_LEVEL_EVENT=
+CONFIG_OSI_TRACE_LEVEL_DEBUG=
+CONFIG_OSI_TRACE_LEVEL_VERBOSE=
+CONFIG_OSI_INITIAL_TRACE_LEVEL=2
+CONFIG_BLUFI_TRACE_LEVEL_NONE=
+CONFIG_BLUFI_TRACE_LEVEL_ERROR=
+CONFIG_BLUFI_TRACE_LEVEL_WARNING=y
+CONFIG_BLUFI_TRACE_LEVEL_API=
+CONFIG_BLUFI_TRACE_LEVEL_EVENT=
+CONFIG_BLUFI_TRACE_LEVEL_DEBUG=
+CONFIG_BLUFI_TRACE_LEVEL_VERBOSE=
+CONFIG_BLUFI_INITIAL_TRACE_LEVEL=2
+CONFIG_BT_ACL_CONNECTIONS=4
+CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=
+CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=
+CONFIG_BLE_HOST_QUEUE_CONGESTION_CHECK=
+CONFIG_SMP_ENABLE=y
+CONFIG_BLE_ACTIVE_SCAN_REPORT_ADV_SCAN_RSP_INDIVIDUALLY=
+CONFIG_BLE_ESTABLISH_LINK_CONNECTION_TIMEOUT=30
 CONFIG_BT_RESERVE_DRAM=0xdb5c
 
 #