From a885afced8519e29efc77e45b6e6a56203d57f04 Mon Sep 17 00:00:00 2001 From: Luc <8822552+luc-github@users.noreply.github.com> Date: Sun, 4 Sep 2022 15:40:03 +0800 Subject: [PATCH] Update documentation Remove SDFat2 warning Add several sanity check Fix some edge cases in authentication process Remove debug messages Clarify some status in http handler responses --- docs/Commands.md | 2 +- docs/Handlers.md | 156 +++- esp3d/src/include/version.h | 2 +- .../modules/http/handles/handle-SD-files.cpp | 16 +- .../modules/http/handles/handle-config.cpp | 3 + .../src/modules/http/handles/handle-files.cpp | 8 +- .../src/modules/http/handles/handle-login.cpp | 10 +- .../modules/http/handles/handle-updatefw.cpp | 15 +- esp3d/src/modules/network/netservices.cpp | 4 + esp3d/src/modules/network/netservices.h | 5 - esp3d/src/modules/sensor/sensor.cpp | 1 - .../SDFat2/SdFat-2.1.2/src/SdFat.h | 846 ++++++++++-------- 12 files changed, 639 insertions(+), 429 deletions(-) diff --git a/docs/Commands.md b/docs/Commands.md index 85eb9695..7ac96ab4 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -300,9 +300,9 @@ label can be: light/framesize/quality/contrast/brightness/saturation/gainceiling * Get state / Set Enable / Disable Serial Bridge Communication `[ESP930] json= pwd=` + * Get / Set Serial Bridge Baud Rate `[ESP931] json= pwd=` - * Set quiet boot if strapping pin is High `[ESP999]QUIETBOOT pwd=` \ No newline at end of file diff --git a/docs/Handlers.md b/docs/Handlers.md index 6599e97d..1240ea54 100644 --- a/docs/Handlers.md +++ b/docs/Handlers.md @@ -1,43 +1,161 @@ -# Web Handlers in ESP3D +# Web Handlers -#### / +### / root is the default handler where all files will be served, if no file is defined, it looks for index.html or index.html.gz (compressed) if you call specific file, it will look for the filename and filename.gz (compressed) if no file is defined and there is not index.html(.gz) it will display embedded page another way to show the embedded page is /?forcefallback=yes -#### /sd/ -it will serve any file from SD card if there is one +### /sd/ +it will serve any file from SD card if there is one, it is only a wrapper to read SD card, no upload -#### /files -this handler handle all commands for FS, including upload on FS +### /files +this handler handle all commands for FS, including upload on FS. + possible options/arguments are: +- `quiet=yes` can be used when you don't want list files but just upload them +- `path=...` define the path to the file +- `action=...` define the action to execute which can be: + - delete + delete the file defined by `filename=...` it will also use `path=...` to do full path + - deletedir + delete the directory defined by `filename=...` it will also use `path=...` to do full path + - createdir + create the directory defined by `filename=...` it will also use `path=...` to do full path +- `createPath=yes` when doing upload and the path do not exists, it will create it, POST only +- `S=...` give the size of uploaded file with name, need to be set before file is set in upload, POST only -#### /sdfiles +the output is a json file: + + ``` + { + "files":[ //the files list + { + "name":"index.html.gz", //the name of the file + "size":"83.46 KB", //the formated size of the file + "time":"2022-09-04 11:56:05" //the time when the file was modified last time, this one is optional and depend on (FILESYSTEM_TIMESTAMP_FEATURE) + }, + { + "name":"subdir", //the name of the file / directory + "size":"-1", //the size is -1 because it is a directory + "time":"" //no time for directories optional as depend on (FILESYSTEM_TIMESTAMP_FEATURE) + } + ], + "path":"/", //current path + "occupation":"52", //% of occupation + "status":"subdir created", //status + "total":"192.00 KB", //Formated total space of Filesystem + "used":"100.00 KB" //Formated used space of Filesystem + } + ``` +### /sdfiles this handler handle all commands for SD, including upload on SD (only shared and direct SD) +this handler handle all commands for FS, including upload on FS. + possible options/arguments are: +- `quiet=yes` can be used when you don't want list files but just upload them +- `path=...` define the path to the file +- `action=...` define the action to execute which can be: + - list + Will refresh the stats of the files + - delete + delete the file defined by `filename=...` it will also use `path=...` to do full path + - deletedir + delete the directory defined by `filename=...` it will also use `path=...` to do full path + - createdir + create the directory defined by `filename=...` it will also use `path=...` to do full path +- `createPath=yes` when doing upload and the path do not exists, it will create it, POST only +- `S=...` give the size of uploaded file with name, need to be set before file is set in upload, POST only -#### /upload -this handler is for MKS boards using MKS communication protocol if enabled, it handle all commands for SD, including upload on SD +the output is a json file: -#### /command -this handler is for all commands + ``` + { + "files":[ //the files list + { + "name":"3Oc-pika2.gco",//the name of the file + "shortname":"3Oc-pika2.gco", //the 8.3 shortname if available, if not the name of the file + "size":"83.46 KB", //the formated size of the file + "time":"2022-09-04 11:56:05" //the time when the file was modified last time, this one is optional and depend on (SD_TIMESTAMP_FEATURE) + }, + { + "name":"subdir", //the name of the file / directory + "size":"-1", //the size is -1 because it is a directory + "time":"" //no time for directories optional as depend on (SD_TIMESTAMP_FEATURE) + } + ], + "path":"/", //current path + "occupation":"52", //% of occupation + "status":"subdir created", //status + "total":"192.00 KB", //Formated total space of Filesystem + "used":"100.00 KB" //Formated used space of Filesystem + } + ``` +### /upload +this handler is for MKS boards using MKS communication protocol if enabled, it handle only upload on SD -#### /login +### /command +this handler is for all commands the parameter is `cmd=...` +if it is an `[ESPXXX]` command the answer is the `[ESPXXX]` response +if it is not an `[ESPXXX]` command the answer is `ESP3D says: command forwarded` and can be ignored + +### /login this handler is for authentication function if enabled + possible options/arguments are: + - `DISCONNECT=YES` + it will clear current session, remove authentication cookie, set status to `disconnected` and response code to 401 + - `SUBMIT=YES` + to login it will need also `PASSWORD=...` and `USER=...`, the answer will be 200 if success and 401 if failed + if user is already authenticated it can use `NEWPASSWORD=...` instead of `PASSWORD=...` to change his password, if successful answer will be returned with code 200, otherwise code will be 500 if change failed or if password format is invalid -#### /config -this handler is a shortcut to [ESP420] command +Output: -#### /updatefw +- if authentified and no submission: + `{"status":"Identified","authentication_lvl":"admin"}` and code 200 +- if not authenticated and no submission: + `{"status":"Wrong authentication!","authentication_lvl":"guest"}` and code 401 + + +### /config +this handler is a shortcut to [ESP420] command in text mode, to get output in json add `json=yes` + +### /updatefw this handler is for FW upload and update +Answer output is : +`{"status":"..."}` if upload is successful the ESP will restart -#### /snap +### /snap this handler is on esp32cam with camera enabled to capture a Frame +it answer by sending a jpg image -#### /description.xml -this handler is for SSDP if enabled to present device informations +### /description.xml +this handler is for SSDP if enabled to present device informations -#### Captive portal bypass handlers +``` + + + 1 + 0 + + http://192.168.2.178:80/ + + urn:schemas-upnp-org:device:upnp:rootdevice:1 + esp3d + / + 52332 + ESP Board + + ESP3D 3.0 + https://www.espressif.com/en/products/devkits + Espressif Systems + https://www.espressif.com + uuid:38323636-4558-4dda-9188-cda0e600cc6c + + + + +``` +### Captive portal bypass handlers to avoid a redirect to index.html and so a refresh of the page, some classic handler have been added so they all go to / handler actually - /generate_204 - /gconnectivitycheck.gstatic.com - /fwlink/ + diff --git a/esp3d/src/include/version.h b/esp3d/src/include/version.h index 529a324b..fcba32dc 100644 --- a/esp3d/src/include/version.h +++ b/esp3d/src/include/version.h @@ -22,7 +22,7 @@ #define _VERSION_ESP3D_H //version and sources location -#define FW_VERSION "3.0.0.a210" +#define FW_VERSION "3.0.0.a211" #define REPOSITORY "https://github.com/luc-github/ESP3D/tree/3.0" #endif //_VERSION_ESP3D_H diff --git a/esp3d/src/modules/http/handles/handle-SD-files.cpp b/esp3d/src/modules/http/handles/handle-SD-files.cpp index 40e86bb6..305f8be4 100644 --- a/esp3d/src/modules/http/handles/handle-SD-files.cpp +++ b/esp3d/src/modules/http/handles/handle-SD-files.cpp @@ -48,8 +48,8 @@ void HTTP_Server::handleSDFileList () if (_webserver->hasArg ("quiet")) { if(_webserver->arg ("quiet") == "yes") { - Serial.println("quiet"); - _webserver->send (200, "text/plain", "{\"status\":\"ok\"}"); + status = "{\"status\":\"" + status + "\"}"; + _webserver->send (200, "text/plain", status.c_str()); return; } } @@ -184,11 +184,13 @@ void HTTP_Server::handleSDFileList () } #ifdef FILESYSTEM_TIMESTAMP_FEATURE buffer2send+="\",\"time\":\""; - time_t t = sub.getLastWrite(); - struct tm * tmstruct = localtime(&t); - char str[20]; //buffer should be 20 - sprintf(str,"%d-%02d-%02d %02d:%02d:%02d",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec); - buffer2send+=str; + if (!sub.isDirectory()) { + time_t t = sub.getLastWrite(); + struct tm * tmstruct = localtime(&t); + char str[20]; //buffer should be 20 + sprintf(str,"%d-%02d-%02d %02d:%02d:%02d",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec); + buffer2send+=str; + } #endif //FILESYSTEM_TIMESTAMP_FEATURE buffer2send+="\"}"; if (buffer2send.length() > 1100) { diff --git a/esp3d/src/modules/http/handles/handle-config.cpp b/esp3d/src/modules/http/handles/handle-config.cpp index c027a652..8c3425c9 100644 --- a/esp3d/src/modules/http/handles/handle-config.cpp +++ b/esp3d/src/modules/http/handles/handle-config.cpp @@ -35,6 +35,9 @@ void HTTP_Server::handle_config () { level_authenticate_type auth_level = AuthenticationService::authenticated_level(); String cmd = "[ESP420]"; + if (_webserver->hasArg("json")) { + cmd = "[ESP420]json="+_webserver->arg("json"); + } ESP3DOutput output(_webserver); output.printMSGLine("
");
     esp3d_commands.process((uint8_t*)cmd.c_str(), cmd.length(), &output, auth_level);
diff --git a/esp3d/src/modules/http/handles/handle-files.cpp b/esp3d/src/modules/http/handles/handle-files.cpp
index 3cbc39b9..696bea5f 100644
--- a/esp3d/src/modules/http/handles/handle-files.cpp
+++ b/esp3d/src/modules/http/handles/handle-files.cpp
@@ -50,8 +50,8 @@ void HTTP_Server::handleFSFileList ()
     }
     if (_webserver->hasArg ("quiet")) {
         if(_webserver->arg ("quiet") == "yes") {
-            Serial.println("quiet");
-            _webserver->send (200, "text/plain", "{\"status\":\"ok\"}");
+            status = "{\"status\":\"" + status + "\"}";
+            _webserver->send (200, "text/plain", status.c_str());
             return;
         }
     }
@@ -165,7 +165,9 @@ void HTTP_Server::handleFSFileList ()
                 }
 #ifdef FILESYSTEM_TIMESTAMP_FEATURE
                 buffer2send+="\",\"time\":\"";
-                buffer2send+=timeserver.current_time(sub.getLastWrite());
+                if (!sub.isDirectory()) {
+                    buffer2send+=timeserver.current_time(sub.getLastWrite());
+                }
 #endif //FILESYSTEM_TIMESTAMP_FEATURE
                 buffer2send+="\"}";
                 if (buffer2send.length() > 1100) {
diff --git a/esp3d/src/modules/http/handles/handle-login.cpp b/esp3d/src/modules/http/handles/handle-login.cpp
index 1e1e9b34..512a0cbe 100644
--- a/esp3d/src/modules/http/handles/handle-login.cpp
+++ b/esp3d/src/modules/http/handles/handle-login.cpp
@@ -37,7 +37,7 @@ void HTTP_Server::handle_login()
     int code = 401;
     String status = "Wrong authentication!";
     //Disconnect can be done anytime no need to check credential
-    if (_webserver->hasArg("DISCONNECT")) {
+    if (_webserver->hasArg("DISCONNECT") && _webserver->arg("DISCONNECT")=="YES") {
         AuthenticationService::ClearCurrentSession();
         _webserver->sendHeader("Set-Cookie","ESPSESSIONID=0");
         _webserver->sendHeader("Cache-Control","no-cache");
@@ -96,7 +96,12 @@ void HTTP_Server::handle_login()
                 }
             }
         }
-    }//SUBMIT
+    } else {
+        if (auth_level == LEVEL_USER || auth_level == LEVEL_ADMIN) {
+            status = "Identified";
+            code = 200;
+        }
+    }
     _webserver->sendHeader("Cache-Control","no-cache");
     String smsg = "{\"status\":\"";
     smsg+=status;
@@ -110,6 +115,7 @@ void HTTP_Server::handle_login()
     }
     smsg += "\"}";
     _webserver->send(code, "application/json", smsg);
+    return;
 #else // No AUTHENTICATION_FEATURE
     _webserver->sendHeader("Cache-Control","no-cache");
     _webserver->send(200, "application/json", "{\"status\":\"ok\",\"authentication_lvl\":\"admin\"}");
diff --git a/esp3d/src/modules/http/handles/handle-updatefw.cpp b/esp3d/src/modules/http/handles/handle-updatefw.cpp
index 21abb215..f921b53e 100644
--- a/esp3d/src/modules/http/handles/handle-updatefw.cpp
+++ b/esp3d/src/modules/http/handles/handle-updatefw.cpp
@@ -38,7 +38,20 @@ void HTTP_Server::handleUpdate ()
         return;
     }
     String jsonfile = "{\"status\":\"" ;
-    jsonfile += String(_upload_status);
+    switch(_upload_status) {
+    case  UPLOAD_STATUS_NONE :
+        jsonfile += "no file";
+        break;
+    case  UPLOAD_STATUS_CANCELLED :
+        jsonfile += "canceled";
+        break;
+    case  UPLOAD_STATUS_SUCCESSFUL :
+        jsonfile += "ok";
+        break;
+    default :
+        jsonfile += "error";
+        break;
+    }
     jsonfile += "\"}";
     _webserver->sendHeader("Cache-Control", "no-cache");
     _webserver->send(200, "application/json", jsonfile);
diff --git a/esp3d/src/modules/network/netservices.cpp b/esp3d/src/modules/network/netservices.cpp
index d991570c..0c94d4a9 100644
--- a/esp3d/src/modules/network/netservices.cpp
+++ b/esp3d/src/modules/network/netservices.cpp
@@ -261,6 +261,10 @@ bool NetServices::begin()
         SSDP.setSerialNumber (stmp.c_str());
         //Any customization could be here
         SSDP.setModelName (ESP_MODEL_NAME);
+#if defined(ESP_MODEL_DESCRIPTION)
+        //this one is optional because windows doesn't care about this field
+        SSDP.setModelDescription(ESP_MODEL_DESCRIPTION);
+#endif //ESP_MODEL_DESCRIPTION
         SSDP.setModelURL (ESP_MODEL_URL);
         SSDP.setModelNumber (ESP_MODEL_NUMBER);
         SSDP.setManufacturer (ESP_MANUFACTURER_NAME);
diff --git a/esp3d/src/modules/network/netservices.h b/esp3d/src/modules/network/netservices.h
index 641ce505..ed251f95 100644
--- a/esp3d/src/modules/network/netservices.h
+++ b/esp3d/src/modules/network/netservices.h
@@ -34,11 +34,6 @@ public:
     {
         return _started;
     }
-    static void start()
-    {
-        _restart=true;
-        Serial.println("Restarting netservices");
-    }
 private:
     static bool _started;
     static bool _restart;
diff --git a/esp3d/src/modules/sensor/sensor.cpp b/esp3d/src/modules/sensor/sensor.cpp
index 52e205a0..29d3da2d 100644
--- a/esp3d/src/modules/sensor/sensor.cpp
+++ b/esp3d/src/modules/sensor/sensor.cpp
@@ -139,7 +139,6 @@ uint8_t ESP3DSensor::getIDFromString(const char * s)
     if (_device) {
         return _device->getIDFromString(s);
     }
-    Serial.println("no device");
     return 0;
 }
 
diff --git a/extra-libraries/SDFat2/SdFat-2.1.2/src/SdFat.h b/extra-libraries/SDFat2/SdFat-2.1.2/src/SdFat.h
index ac70377b..3ae8ea9b 100644
--- a/extra-libraries/SDFat2/SdFat-2.1.2/src/SdFat.h
+++ b/extra-libraries/SDFat2/SdFat-2.1.2/src/SdFat.h
@@ -47,386 +47,450 @@
  * \brief base SD file system template class.
  */
 template 
-class SdBase : public Vol {
- public:
-  //----------------------------------------------------------------------------
-  /** Initialize SD card and file system.
-   *
-   * \param[in] csPin SD card chip select pin.
-   * \return true for success or false for failure.
-   */
-  bool begin(SdCsPin_t csPin = SS) {
+class SdBase : public Vol
+{
+public:
+    //----------------------------------------------------------------------------
+    /** Initialize SD card and file system.
+     *
+     * \param[in] csPin SD card chip select pin.
+     * \return true for success or false for failure.
+     */
+    bool begin(SdCsPin_t csPin = SS)
+    {
 #ifdef BUILTIN_SDCARD
-    if (csPin == BUILTIN_SDCARD) {
-      return begin(SdioConfig(FIFO_SDIO));
-    }
+        if (csPin == BUILTIN_SDCARD) {
+            return begin(SdioConfig(FIFO_SDIO));
+        }
 #endif  // BUILTIN_SDCARD
-    return begin(SdSpiConfig(csPin, SHARED_SPI));
-  }
-  //----------------------------------------------------------------------------
-  /** Initialize SD card and file system.
-   *
-   * \param[in] csPin SD card chip select pin.
-   * \param[in] maxSck Maximum SCK frequency.
-   * \return true for success or false for failure.
-   */
-  bool begin(SdCsPin_t csPin, uint32_t maxSck) {
-    return begin(SdSpiConfig(csPin, SHARED_SPI, maxSck));
-  }
-  //----------------------------------------------------------------------------
-  /** Initialize SD card and file system for SPI mode.
-   *
-   * \param[in] spiConfig SPI configuration.
-   * \return true for success or false for failure.
-   */
-  bool begin(SdSpiConfig spiConfig) {
-    return cardBegin(spiConfig) && Vol::begin(m_card);
-  }
-  //---------------------------------------------------------------------------
-  /** Initialize SD card and file system for SDIO mode.
-   *
-   * \param[in] sdioConfig SDIO configuration.
-   * \return true for success or false for failure.
-   */
-  bool begin(SdioConfig sdioConfig) {
-    return cardBegin(sdioConfig) && Vol::begin(m_card);
-  }
-  //----------------------------------------------------------------------------
-  /** \return Pointer to SD card object. */
-  SdCard* card() {return m_card;}
-  //----------------------------------------------------------------------------
-  /** Initialize SD card in SPI mode.
-   *
-   * \param[in] spiConfig SPI configuration.
-   * \return true for success or false for failure.
-   */
-  bool cardBegin(SdSpiConfig spiConfig) {
-    m_card = m_cardFactory.newCard(spiConfig);
-    return m_card && !m_card->errorCode();
-  }
-  //----------------------------------------------------------------------------
-  /** Initialize SD card in SDIO mode.
-   *
-   * \param[in] sdioConfig SDIO configuration.
-   * \return true for success or false for failure.
-   */
-  bool cardBegin(SdioConfig sdioConfig) {
-    m_card = m_cardFactory.newCard(sdioConfig);
-    return m_card && !m_card->errorCode();
-  }
-  //----------------------------------------------------------------------------
-  /** End use of card. */
-  void end() {
-    Vol::end();
-    if (m_card) {
-      m_card->end();
+        return begin(SdSpiConfig(csPin, SHARED_SPI));
     }
-  }
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] pr Print destination.
-   */
-  void errorHalt(print_t* pr) {
-    if (sdErrorCode()) {
-      pr->print(F("SdError: 0X"));
-      pr->print(sdErrorCode(), HEX);
-      pr->print(F(",0X"));
-      pr->println(sdErrorData(), HEX);
-    } else if (!Vol::fatType()) {
-      pr->println(F("Check SD format."));
+    //----------------------------------------------------------------------------
+    /** Initialize SD card and file system.
+     *
+     * \param[in] csPin SD card chip select pin.
+     * \param[in] maxSck Maximum SCK frequency.
+     * \return true for success or false for failure.
+     */
+    bool begin(SdCsPin_t csPin, uint32_t maxSck)
+    {
+        return begin(SdSpiConfig(csPin, SHARED_SPI, maxSck));
     }
-    while (true) {}
-  }
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] pr Print destination.
-   * \param[in] msg Message to print.
-   */
-  void errorHalt(print_t* pr, const char* msg) {
-    pr->print(F("error: "));
-    pr->println(msg);
-    errorHalt(pr);
-  }
-  //----------------------------------------------------------------------------
-  /** %Print msg and halt.
-   *
-   * \param[in] pr Print destination.
-   * \param[in] msg Message to print.
-   */
-  void errorHalt(print_t* pr, const __FlashStringHelper* msg) {
-    pr->print(F("error: "));
-    pr->println(msg);
-    errorHalt(pr);
-  }
-  //----------------------------------------------------------------------------
-  /** Format SD card
-   *
-   * \param[in] pr Print destination.
-   * \return true for success else false.
-   */
-  bool format(print_t* pr = nullptr) {
-    Fmt fmt;
-    uint8_t* mem = Vol::end();
-    if (!mem) {
-      return false;
+    //----------------------------------------------------------------------------
+    /** Initialize SD card and file system for SPI mode.
+     *
+     * \param[in] spiConfig SPI configuration.
+     * \return true for success or false for failure.
+     */
+    bool begin(SdSpiConfig spiConfig)
+    {
+        return cardBegin(spiConfig) && Vol::begin(m_card);
     }
-    bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi();
-    if (switchSpi && !setDedicatedSpi(true)) {
-      return 0;
+    //---------------------------------------------------------------------------
+    /** Initialize SD card and file system for SDIO mode.
+     *
+     * \param[in] sdioConfig SDIO configuration.
+     * \return true for success or false for failure.
+     */
+    bool begin(SdioConfig sdioConfig)
+    {
+        return cardBegin(sdioConfig) && Vol::begin(m_card);
     }
-    bool rtn = fmt.format(card(), mem, pr);
-    if (switchSpi && !setDedicatedSpi(false)) {
-      return 0;
+    //----------------------------------------------------------------------------
+    /** \return Pointer to SD card object. */
+    SdCard* card()
+    {
+        return m_card;
     }
-    return rtn;
-  }
-  //----------------------------------------------------------------------------
-  /** \return the free cluster count. */
-  uint32_t freeClusterCount() {
-    bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi();
-    if (switchSpi && !setDedicatedSpi(true)) {
-      return 0;
+    //----------------------------------------------------------------------------
+    /** Initialize SD card in SPI mode.
+     *
+     * \param[in] spiConfig SPI configuration.
+     * \return true for success or false for failure.
+     */
+    bool cardBegin(SdSpiConfig spiConfig)
+    {
+        m_card = m_cardFactory.newCard(spiConfig);
+        return m_card && !m_card->errorCode();
     }
-    uint32_t rtn = Vol::freeClusterCount();
-    if (switchSpi && !setDedicatedSpi(false)) {
-      return 0;
+    //----------------------------------------------------------------------------
+    /** Initialize SD card in SDIO mode.
+     *
+     * \param[in] sdioConfig SDIO configuration.
+     * \return true for success or false for failure.
+     */
+    bool cardBegin(SdioConfig sdioConfig)
+    {
+        m_card = m_cardFactory.newCard(sdioConfig);
+        return m_card && !m_card->errorCode();
     }
-    return rtn;
-  }
-  //----------------------------------------------------------------------------
-  /** \return true if can be in dedicated SPI state */
-  bool hasDedicatedSpi() {return m_card ? m_card->hasDedicatedSpi() : false;}
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] pr Print destination.
-   */
-  void initErrorHalt(print_t* pr) {
-    initErrorPrint(pr);
-    while (true) {}
-  }
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] pr Print destination.
-   * \param[in] msg Message to print.
-   */
-  void initErrorHalt(print_t* pr, const char* msg) {
-    pr->println(msg);
-    initErrorHalt(pr);
-  }
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] pr Print destination.
-   * \param[in] msg Message to print.
-   */
-  void initErrorHalt(print_t* pr, const __FlashStringHelper* msg) {
-    pr->println(msg);
-    initErrorHalt(pr);
-  }
-  //----------------------------------------------------------------------------
-  /** Print error details after begin() fails.
-   *
-   * \param[in] pr Print destination.
-   */
-  void initErrorPrint(print_t* pr) {
-    pr->println(F("begin() failed"));
-    if (sdErrorCode()) {
-      pr->println(F("Do not reformat the SD."));
-      if (sdErrorCode() == SD_CARD_ERROR_CMD0) {
-        pr->println(F("No card, wrong chip select pin, or wiring error?"));
-      }
+    //----------------------------------------------------------------------------
+    /** End use of card. */
+    void end()
+    {
+        Vol::end();
+        if (m_card) {
+            m_card->end();
+        }
     }
-    errorPrint(pr);
-  }
-  //----------------------------------------------------------------------------
-  /** \return true if in dedicated SPI state. */
-  bool isDedicatedSpi() {return m_card ? m_card->isDedicatedSpi() : false;}
-  //----------------------------------------------------------------------------
-  /** %Print volume FAT/exFAT type.
-   *
-   * \param[in] pr Print destination.
-   */
-  void printFatType(print_t* pr) {
-    if (Vol::fatType() == FAT_TYPE_EXFAT) {
-      pr->print(F("exFAT"));
-    } else {
-      pr->print(F("FAT"));
-      pr->print(Vol::fatType());
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] pr Print destination.
+     */
+    void errorHalt(print_t* pr)
+    {
+        if (sdErrorCode()) {
+            pr->print(F("SdError: 0X"));
+            pr->print(sdErrorCode(), HEX);
+            pr->print(F(",0X"));
+            pr->println(sdErrorData(), HEX);
+        } else if (!Vol::fatType()) {
+            pr->println(F("Check SD format."));
+        }
+        while (true) {}
     }
-  }
-  //----------------------------------------------------------------------------
-  /** %Print SD errorCode and errorData.
-   *
-   * \param[in] pr Print destination.
-   */
-  void errorPrint(print_t* pr) {
-    if (sdErrorCode()) {
-      pr->print(F("SdError: 0X"));
-      pr->print(sdErrorCode(), HEX);
-      pr->print(F(",0X"));
-      pr->println(sdErrorData(), HEX);
-    } else if (!Vol::fatType()) {
-      pr->println(F("Check SD format."));
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] pr Print destination.
+     * \param[in] msg Message to print.
+     */
+    void errorHalt(print_t* pr, const char* msg)
+    {
+        pr->print(F("error: "));
+        pr->println(msg);
+        errorHalt(pr);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print msg and halt.
+     *
+     * \param[in] pr Print destination.
+     * \param[in] msg Message to print.
+     */
+    void errorHalt(print_t* pr, const __FlashStringHelper* msg)
+    {
+        pr->print(F("error: "));
+        pr->println(msg);
+        errorHalt(pr);
+    }
+    //----------------------------------------------------------------------------
+    /** Format SD card
+     *
+     * \param[in] pr Print destination.
+     * \return true for success else false.
+     */
+    bool format(print_t* pr = nullptr)
+    {
+        Fmt fmt;
+        uint8_t* mem = Vol::end();
+        if (!mem) {
+            return false;
+        }
+        bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi();
+        if (switchSpi && !setDedicatedSpi(true)) {
+            return 0;
+        }
+        bool rtn = fmt.format(card(), mem, pr);
+        if (switchSpi && !setDedicatedSpi(false)) {
+            return 0;
+        }
+        return rtn;
+    }
+    //----------------------------------------------------------------------------
+    /** \return the free cluster count. */
+    uint32_t freeClusterCount()
+    {
+        bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi();
+        if (switchSpi && !setDedicatedSpi(true)) {
+            return 0;
+        }
+        uint32_t rtn = Vol::freeClusterCount();
+        if (switchSpi && !setDedicatedSpi(false)) {
+            return 0;
+        }
+        return rtn;
+    }
+    //----------------------------------------------------------------------------
+    /** \return true if can be in dedicated SPI state */
+    bool hasDedicatedSpi()
+    {
+        return m_card ? m_card->hasDedicatedSpi() : false;
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] pr Print destination.
+     */
+    void initErrorHalt(print_t* pr)
+    {
+        initErrorPrint(pr);
+        while (true) {}
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] pr Print destination.
+     * \param[in] msg Message to print.
+     */
+    void initErrorHalt(print_t* pr, const char* msg)
+    {
+        pr->println(msg);
+        initErrorHalt(pr);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] pr Print destination.
+     * \param[in] msg Message to print.
+     */
+    void initErrorHalt(print_t* pr, const __FlashStringHelper* msg)
+    {
+        pr->println(msg);
+        initErrorHalt(pr);
+    }
+    //----------------------------------------------------------------------------
+    /** Print error details after begin() fails.
+     *
+     * \param[in] pr Print destination.
+     */
+    void initErrorPrint(print_t* pr)
+    {
+        pr->println(F("begin() failed"));
+        if (sdErrorCode()) {
+            pr->println(F("Do not reformat the SD."));
+            if (sdErrorCode() == SD_CARD_ERROR_CMD0) {
+                pr->println(F("No card, wrong chip select pin, or wiring error?"));
+            }
+        }
+        errorPrint(pr);
+    }
+    //----------------------------------------------------------------------------
+    /** \return true if in dedicated SPI state. */
+    bool isDedicatedSpi()
+    {
+        return m_card ? m_card->isDedicatedSpi() : false;
+    }
+    //----------------------------------------------------------------------------
+    /** %Print volume FAT/exFAT type.
+     *
+     * \param[in] pr Print destination.
+     */
+    void printFatType(print_t* pr)
+    {
+        if (Vol::fatType() == FAT_TYPE_EXFAT) {
+            pr->print(F("exFAT"));
+        } else {
+            pr->print(F("FAT"));
+            pr->print(Vol::fatType());
+        }
+    }
+    //----------------------------------------------------------------------------
+    /** %Print SD errorCode and errorData.
+     *
+     * \param[in] pr Print destination.
+     */
+    void errorPrint(print_t* pr)
+    {
+        if (sdErrorCode()) {
+            pr->print(F("SdError: 0X"));
+            pr->print(sdErrorCode(), HEX);
+            pr->print(F(",0X"));
+            pr->println(sdErrorData(), HEX);
+        } else if (!Vol::fatType()) {
+            pr->println(F("Check SD format."));
+        }
+    }
+    //----------------------------------------------------------------------------
+    /** %Print msg, any SD error code.
+     *
+     * \param[in] pr Print destination.
+     * \param[in] msg Message to print.
+     */
+    void errorPrint(print_t* pr, char const* msg)
+    {
+        pr->print(F("error: "));
+        pr->println(msg);
+        errorPrint(pr);
     }
-  }
-  //----------------------------------------------------------------------------
-  /** %Print msg, any SD error code.
-   *
-   * \param[in] pr Print destination.
-   * \param[in] msg Message to print.
-   */
-  void errorPrint(print_t* pr, char const* msg) {
-    pr->print(F("error: "));
-    pr->println(msg);
-    errorPrint(pr);
-  }
 
-  /** %Print msg, any SD error code.
-   *
-   * \param[in] pr Print destination.
-   * \param[in] msg Message to print.
-   */
-  void errorPrint(print_t* pr, const __FlashStringHelper* msg) {
-    pr->print(F("error: "));
-    pr->println(msg);
-    errorPrint(pr);
-  }
-  //----------------------------------------------------------------------------
-  /** %Print error info and return.
-   *
-   * \param[in] pr Print destination.
-   */
-  void printSdError(print_t* pr) {
-    if (sdErrorCode()) {
-      if (sdErrorCode() == SD_CARD_ERROR_CMD0) {
-        pr->println(F("No card, wrong chip select pin, or wiring error?"));
-      }
-      pr->print(F("SD error: "));
-      printSdErrorSymbol(pr, sdErrorCode());
-      pr->print(F(" = 0x"));
-      pr->print(sdErrorCode(), HEX);
-      pr->print(F(",0x"));
-      pr->println(sdErrorData(), HEX);
-    } else if (!Vol::fatType()) {
-      pr->println(F("Check SD format."));
+    /** %Print msg, any SD error code.
+     *
+     * \param[in] pr Print destination.
+     * \param[in] msg Message to print.
+     */
+    void errorPrint(print_t* pr, const __FlashStringHelper* msg)
+    {
+        pr->print(F("error: "));
+        pr->println(msg);
+        errorPrint(pr);
     }
-  }
-  //----------------------------------------------------------------------------
-  /** \return SD card error code. */
-  uint8_t sdErrorCode() {
-    if (m_card) {
-      return m_card->errorCode();
+    //----------------------------------------------------------------------------
+    /** %Print error info and return.
+     *
+     * \param[in] pr Print destination.
+     */
+    void printSdError(print_t* pr)
+    {
+        if (sdErrorCode()) {
+            if (sdErrorCode() == SD_CARD_ERROR_CMD0) {
+                pr->println(F("No card, wrong chip select pin, or wiring error?"));
+            }
+            pr->print(F("SD error: "));
+            printSdErrorSymbol(pr, sdErrorCode());
+            pr->print(F(" = 0x"));
+            pr->print(sdErrorCode(), HEX);
+            pr->print(F(",0x"));
+            pr->println(sdErrorData(), HEX);
+        } else if (!Vol::fatType()) {
+            pr->println(F("Check SD format."));
+        }
     }
-    return SD_CARD_ERROR_INVALID_CARD_CONFIG;
-  }
-  //----------------------------------------------------------------------------
-  /** \return SD card error data. */
-  uint8_t sdErrorData() {return m_card ? m_card->errorData() : 0;}
-  //----------------------------------------------------------------------------
-  /** Set SPI sharing state
-   * \param[in] value desired state.
-   * \return true for success else false;
-   */
-  bool setDedicatedSpi(bool value) {
-    if (m_card) {
-      return m_card->setDedicatedSpi(value);
+    //----------------------------------------------------------------------------
+    /** \return SD card error code. */
+    uint8_t sdErrorCode()
+    {
+        if (m_card) {
+            return m_card->errorCode();
+        }
+        return SD_CARD_ERROR_INVALID_CARD_CONFIG;
+    }
+    //----------------------------------------------------------------------------
+    /** \return SD card error data. */
+    uint8_t sdErrorData()
+    {
+        return m_card ? m_card->errorData() : 0;
+    }
+    //----------------------------------------------------------------------------
+    /** Set SPI sharing state
+     * \param[in] value desired state.
+     * \return true for success else false;
+     */
+    bool setDedicatedSpi(bool value)
+    {
+        if (m_card) {
+            return m_card->setDedicatedSpi(value);
+        }
+        return false;
+    }
+    //----------------------------------------------------------------------------
+    /** \return pointer to base volume */
+    Vol* vol()
+    {
+        return reinterpret_cast(this);
+    }
+    //----------------------------------------------------------------------------
+    /** Initialize file system after call to cardBegin.
+     *
+     * \return true for success or false for failure.
+     */
+    bool volumeBegin()
+    {
+        return Vol::begin(m_card);
     }
-    return false;
-  }
-  //----------------------------------------------------------------------------
-  /** \return pointer to base volume */
-  Vol* vol() {return reinterpret_cast(this);}
-  //----------------------------------------------------------------------------
-  /** Initialize file system after call to cardBegin.
-   *
-   * \return true for success or false for failure.
-   */
-  bool volumeBegin() {
-     return Vol::begin(m_card);
-  }
 #if ENABLE_ARDUINO_SERIAL
-  /** Print error details after begin() fails. */
-  void initErrorPrint() {
-    initErrorPrint(&Serial);
-  }
-  //----------------------------------------------------------------------------
-  /** %Print msg to Serial and halt.
-   *
-   * \param[in] msg Message to print.
-   */
-  void errorHalt(const __FlashStringHelper* msg) {
-    errorHalt(&Serial, msg);
-  }
-  //----------------------------------------------------------------------------
-  /** %Print error info to Serial and halt. */
-  void errorHalt() {errorHalt(&Serial);}
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] msg Message to print.
-   */
-  void errorHalt(const char* msg) {errorHalt(&Serial, msg);}
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt. */
-  void initErrorHalt() {initErrorHalt(&Serial);}
-  //----------------------------------------------------------------------------
-  /** %Print msg, any SD error code.
-   *
-   * \param[in] msg Message to print.
-   */
-  void errorPrint(const char* msg) {errorPrint(&Serial, msg);}
-   /** %Print msg, any SD error code.
-   *
-   * \param[in] msg Message to print.
-   */
-  void errorPrint(const __FlashStringHelper* msg) {errorPrint(&Serial, msg);}
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] msg Message to print.
-   */
-  void initErrorHalt(const char* msg) {initErrorHalt(&Serial, msg);}
-  //----------------------------------------------------------------------------
-  /** %Print error info and halt.
-   *
-   * \param[in] msg Message to print.
-   */
-  void initErrorHalt(const __FlashStringHelper* msg) {
-    initErrorHalt(&Serial, msg);
-  }
+    /** Print error details after begin() fails. */
+    void initErrorPrint()
+    {
+        initErrorPrint(&Serial);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print msg to Serial and halt.
+     *
+     * \param[in] msg Message to print.
+     */
+    void errorHalt(const __FlashStringHelper* msg)
+    {
+        errorHalt(&Serial, msg);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info to Serial and halt. */
+    void errorHalt()
+    {
+        errorHalt(&Serial);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] msg Message to print.
+     */
+    void errorHalt(const char* msg)
+    {
+        errorHalt(&Serial, msg);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt. */
+    void initErrorHalt()
+    {
+        initErrorHalt(&Serial);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print msg, any SD error code.
+     *
+     * \param[in] msg Message to print.
+     */
+    void errorPrint(const char* msg)
+    {
+        errorPrint(&Serial, msg);
+    }
+    /** %Print msg, any SD error code.
+    *
+    * \param[in] msg Message to print.
+    */
+    void errorPrint(const __FlashStringHelper* msg)
+    {
+        errorPrint(&Serial, msg);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] msg Message to print.
+     */
+    void initErrorHalt(const char* msg)
+    {
+        initErrorHalt(&Serial, msg);
+    }
+    //----------------------------------------------------------------------------
+    /** %Print error info and halt.
+     *
+     * \param[in] msg Message to print.
+     */
+    void initErrorHalt(const __FlashStringHelper* msg)
+    {
+        initErrorHalt(&Serial, msg);
+    }
 #endif  // ENABLE_ARDUINO_SERIAL
-  //----------------------------------------------------------------------------
- private:
-  SdCard* m_card = nullptr;
-  SdCardFactory m_cardFactory;
+    //----------------------------------------------------------------------------
+private:
+    SdCard* m_card = nullptr;
+    SdCardFactory m_cardFactory;
 };
 //------------------------------------------------------------------------------
 /**
  * \class SdFat32
  * \brief SD file system class for FAT volumes.
  */
-class SdFat32 : public SdBase {
- public:
+class SdFat32 : public SdBase
+{
+public:
 };
 //------------------------------------------------------------------------------
 /**
  * \class SdExFat
  * \brief SD file system class for exFAT volumes.
  */
-class SdExFat : public SdBase {
- public:
+class SdExFat : public SdBase
+{
+public:
 };
 //------------------------------------------------------------------------------
 /**
  * \class SdFs
  * \brief SD file system class for FAT16, FAT32, and exFAT volumes.
  */
-class SdFs : public SdBase {
- public:
+class SdFs : public SdBase
+{
+public:
 };
 //------------------------------------------------------------------------------
 #if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN)
@@ -449,7 +513,7 @@ typedef FsBaseFile SdBaseFile;
 #if defined __has_include
 #if __has_include()
 #define HAS_INCLUDE_FS_H
-#warning File not defined because __has_include(FS.h)
+//#warning File not defined because __has_include(FS.h)
 #endif  // __has_include()
 #endif  // defined __has_include
 #ifndef HAS_INCLUDE_FS_H
@@ -466,49 +530,53 @@ typedef FsFile File;
  * \class SdFile
  * \brief FAT16/FAT32 file with Print.
  */
-class SdFile : public PrintFile {
- public:
-  SdFile() {}
-  /** Create an open SdFile.
-   * \param[in] path path for file.
-   * \param[in] oflag open flags.
-   */
-  SdFile(const char* path, oflag_t oflag) {
-    open(path, oflag);
-  }
-  /** Set the date/time callback function
-   *
-   * \param[in] dateTime The user's call back function.  The callback
-   * function is of the form:
-   *
-   * \code
-   * void dateTime(uint16_t* date, uint16_t* time) {
-   *   uint16_t year;
-   *   uint8_t month, day, hour, minute, second;
-   *
-   *   // User gets date and time from GPS or real-time clock here
-   *
-   *   // return date using FS_DATE macro to format fields
-   *   *date = FS_DATE(year, month, day);
-   *
-   *   // return time using FS_TIME macro to format fields
-   *   *time = FS_TIME(hour, minute, second);
-   * }
-   * \endcode
-   *
-   * Sets the function that is called when a file is created or when
-   * a file's directory entry is modified by sync(). All timestamps,
-   * access, creation, and modify, are set when a file is created.
-   * sync() maintains the last access date and last modify date/time.
-   *
-   */
-  static void dateTimeCallback(
-    void (*dateTime)(uint16_t* date, uint16_t* time)) {
-    FsDateTime::setCallback(dateTime);
-  }
-  /**  Cancel the date/time callback function. */
-  static void dateTimeCallbackCancel() {
-    FsDateTime::clearCallback();
-  }
+class SdFile : public PrintFile
+{
+public:
+    SdFile() {}
+    /** Create an open SdFile.
+     * \param[in] path path for file.
+     * \param[in] oflag open flags.
+     */
+    SdFile(const char* path, oflag_t oflag)
+    {
+        open(path, oflag);
+    }
+    /** Set the date/time callback function
+     *
+     * \param[in] dateTime The user's call back function.  The callback
+     * function is of the form:
+     *
+     * \code
+     * void dateTime(uint16_t* date, uint16_t* time) {
+     *   uint16_t year;
+     *   uint8_t month, day, hour, minute, second;
+     *
+     *   // User gets date and time from GPS or real-time clock here
+     *
+     *   // return date using FS_DATE macro to format fields
+     *   *date = FS_DATE(year, month, day);
+     *
+     *   // return time using FS_TIME macro to format fields
+     *   *time = FS_TIME(hour, minute, second);
+     * }
+     * \endcode
+     *
+     * Sets the function that is called when a file is created or when
+     * a file's directory entry is modified by sync(). All timestamps,
+     * access, creation, and modify, are set when a file is created.
+     * sync() maintains the last access date and last modify date/time.
+     *
+     */
+    static void dateTimeCallback(
+        void (*dateTime)(uint16_t* date, uint16_t* time))
+    {
+        FsDateTime::setCallback(dateTime);
+    }
+    /**  Cancel the date/time callback function. */
+    static void dateTimeCallbackCancel()
+    {
+        FsDateTime::clearCallback();
+    }
 };
 #endif  // SdFat_h