Per Mårtensson преди 9 часа
родител
ревизия
fcae6fc8ca

+ 5 - 0
sw/SweFlops/.gitignore

@@ -0,0 +1,5 @@
+.pio
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch

+ 1 - 0
sw/SweFlops/README.md

@@ -0,0 +1 @@
+# Alternative BTT TF Cloud V1.0 Firmware (AFW)

+ 59 - 0
sw/SweFlops/build.py

@@ -0,0 +1,59 @@
+# build.py
+# pre-build script, setting up build environment and fetch hal file for user's board
+
+import sys
+import os
+import os.path
+import requests
+from os.path import basename
+from platformio import util
+from SCons.Script import DefaultEnvironment
+
+try:
+    import configparser
+except ImportError:
+    import ConfigParser as configparser
+
+# get platformio environment variables
+env = DefaultEnvironment()
+
+config = configparser.ConfigParser()
+config.read("platformio.ini")
+
+# get platformio source path
+srcdir = env.get("PROJECT_SRC_DIR")
+project_dir = env.subst("$PROJECT_DIR")
+
+
+board = env.BoardConfig()
+
+
+env.Append(
+    LIBPATH=[
+        os.path.join(variants_dir, board.get("build.variant"))
+    ]
+)
+
+libs = []
+
+if "build.variant" in env.BoardConfig():
+    env.Append(
+        CPPPATH=[os.path.join(variants_dir, board.get("build.variant"))]
+    )
+    libs.append(env.BuildLibrary(
+        os.path.join("$BUILD_DIR", "FrameworkArduinoVariant"),
+        os.path.join(variants_dir, board.get("build.variant"))
+    ))
+
+env.Prepend(LIBS=libs)
+
+
+
+
+# get bintray upload parameters from platformio environment
+version = config.get("common", "release_version")
+
+
+
+
+

+ 112 - 0
sw/SweFlops/include/ESPFtpServer.h

@@ -0,0 +1,112 @@
+/*
+*  FTP SERVER FOR ESP8266
+ * based on FTP Serveur for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200)
+ * based on Jean-Michel Gallego's work
+ * modified to work with esp8266 SPIFFS by David Paiva (david@nailbuster.com)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*******************************************************************************
+ **                                                                            **
+ **                       DEFINITIONS FOR FTP SERVER                           **
+ **                                                                            **
+ *******************************************************************************/
+
+// Uncomment to print debugging info to console attached to ESP8266
+//#define FTP_DEBUG
+
+#ifndef FTP_SERVERESP_H
+#define FTP_SERVERESP_H
+
+#include <SdFat.h>
+#include <WiFiClient.h>
+#include "Version.h"
+
+#define FTP_DEBUG 1
+
+#define FTP_SERVER_VERSION GOTEK_VERSION
+
+#define FTP_CTRL_PORT    21          // Command port on wich server is listening  
+#define FTP_DATA_PORT_PASV 50009     // Data port in passive mode
+
+#define FTP_TIME_OUT  5           // Disconnect client after 5 minutes of inactivity
+#define FTP_CMD_SIZE 255 + 8 // max size of a command
+#define FTP_CWD_SIZE 255 + 8 // max size of a directory name
+#define FTP_FIL_SIZE 255     // max size of a file name
+//#define FTP_BUF_SIZE 1024 //512   // size of file buffer for read/write
+//#define FTP_BUF_SIZE 8*1460
+#define FTP_BUF_SIZE 1024
+extern SPISettings spiSettings;
+class FtpServer
+{
+public:
+  void    begin(String uname, String pword, int chipSelectPin, SPISettings spiSettings);
+  void    handleFTP();
+  
+  int8_t   cmdStatus,SD_Status;                 // status of ftp command connexion
+
+private:
+  void    iniVariables();
+  void    clientConnected();
+  void    disconnectClient();
+  boolean userIdentity();
+  boolean userPassword();
+  boolean processCommand();
+  boolean dataConnect();
+  boolean doRetrieve();
+  boolean doStore();
+  void    closeTransfer();
+  void    abortTransfer();
+  boolean makePath( char * fullname );
+  boolean makePath( char * fullName, char * param );
+  uint8_t getDateTime( uint16_t * pyear, uint8_t * pmonth, uint8_t * pday,
+                       uint8_t * phour, uint8_t * pminute, uint8_t * second );
+  char *  makeDateTimeStr( char * tstr, uint16_t date, uint16_t time );
+  int8_t  readChar();
+  bool    initSD();
+
+  IPAddress      dataIp;              // IP address of client for data
+  WiFiClient client;
+  WiFiClient data;
+  
+  FatFile file;
+  SdFat SD;
+  int chipSelectPin;
+ SPISettings spiSettings;
+  
+  boolean  dataPassiveConn;
+  uint16_t dataPort;
+  char     buf[ FTP_BUF_SIZE ];       // data buffer for transfers
+  char     cmdLine[ FTP_CMD_SIZE ];   // where to store incoming char from client
+  char     cwdName[ FTP_CWD_SIZE ];   // name of current directory
+  char     command[ 5 ];              // command sent by client
+  boolean  rnfrCmd;                   // previous command was RNFR
+  char *   parameters;                // point to begin of parameters sent by client
+  uint16_t iCL;                       // pointer to cmdLine next incoming char
+  int8_t   transferStatus;            // status of ftp data transfer
+  uint32_t millisTimeOut,             // disconnect after 5 min of inactivity
+           millisDelay,
+           millisEndConnection,       // 
+           millisBeginTrans,          // store time of beginning of a transaction
+           bytesTransfered;           //
+  String   _FTP_USER;
+  String   _FTP_PASS;
+
+  // SD stuff
+	//bool isSDInit = false;
+};
+
+#endif // FTP_SERVERESP_H
+

+ 94 - 0
sw/SweFlops/include/ESPWebDAV.h

@@ -0,0 +1,94 @@
+#ifndef ESPWEBDAV_H
+#define ESPWEBDAV_H
+
+#include <WiFi.h>
+#include <SdFat.h>
+#include "mbedtls/sha1.h"
+// debugging
+ #define DBG_PRINT(...) 		{ Serial.print(__VA_ARGS__); }
+ #define DBG_PRINTLN(...) 	{ Serial.println(__VA_ARGS__); }
+// production
+//#define DBG_PRINT(...) 		{ }
+//#define DBG_PRINTLN(...) 	{ }
+
+// constants for WebServer
+#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
+#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
+#define HTTP_MAX_POST_WAIT 		5000 
+
+enum ResourceType { RESOURCE_NONE, RESOURCE_FILE, RESOURCE_DIR };
+enum DepthType { DEPTH_NONE, DEPTH_CHILD, DEPTH_ALL };
+void sha1( String str_data, uint8_t hash[20] );
+//using namespace sdfat;
+
+class ESPWebDAV	{
+public:
+	bool init(int serverPort);
+	bool initSD(int chipSelectPin, SPISettings spiSettings);
+	bool isClientWaiting();
+	void handleClient(String blank = "");
+	void rejectClient(String rejectMessage);
+	void sdChdir(const char* path) ;
+
+protected:
+	typedef void (ESPWebDAV::*THandlerFunction)(String);
+	
+	void processClient(THandlerFunction handler, String message);
+	void handleNotFound();
+	void handleReject(String rejectMessage);
+	void handleRequest(String blank);
+	void handleOptions(ResourceType resource);
+	void handleLock(ResourceType resource);
+	void handleUnlock(ResourceType resource);
+	void handlePropPatch(ResourceType resource);
+	void handleProp(ResourceType resource);
+#ifdef GOTEK_SDFAT2
+	void sendPropResponse(boolean recursing, FsFile *curFile);
+	void handleWriteError(String message, FsFile *wFile);
+#else
+	void sendPropResponse(boolean recursing, FatFile *curFile);
+	void handleWriteError(String message, FatFile *wFile);
+#endif 
+	void handleGet(ResourceType resource, bool isGet);
+	void handlePut(ResourceType resource);
+
+	void handleDirectoryCreate(ResourceType resource);
+	void handleMove(ResourceType resource);
+	void handleDelete(ResourceType resource);
+
+	// Sections are copied from ESP8266Webserver
+	String getMimeType(String path);
+	String urlDecode(const String& text);
+	String urlToUri(String url);
+	bool parseRequest();
+	void sendHeader(const String& name, const String& value, bool first = false);
+	void send(String code, const char* content_type, const String& content);
+	void _prepareHeader(String& response, String code, const char* content_type, size_t contentLength);
+	void sendContent(const String& content);
+	void sendContent_P(PGM_P content);
+	void setContentLength(size_t len);
+	size_t readBytesWithTimeout(uint8_t *buf, size_t bufSize);
+	size_t readBytesWithTimeout(uint8_t *buf, size_t bufSize, size_t numToRead);
+	
+	
+	// variables pertaining to current most HTTP request being serviced
+	WiFiServer *server;
+	SdFat sd;
+
+	WiFiClient 	client;
+	String 		method;
+	String 		uri;
+	String 		contentLengthHeader;
+	String 		depthHeader;
+	String 		hostHeader;
+	String		destinationHeader;
+
+	String 		_responseHeaders;
+	bool		_chunked;
+	int			_contentLength;
+
+	// SD stuff
+	//bool isSDInit = false;
+};
+
+#endif // ESPWEBDAV_H

+ 18 - 0
sw/SweFlops/include/SimpleFTPServer.h

@@ -0,0 +1,18 @@
+/*
+ * FtpServer Arduino, esp8266 and esp32 library for Ftp Server
+ * Derived form https://github.com/nailbuster/esp8266FTPServer
+ *
+ * AUTHOR:  Renzo Mischianti
+ *
+ * https://www.mischianti.org/2020/02/08/ftp-server-on-esp8266-and-esp32
+ *
+ */
+
+#ifndef SIMPLE_FTP_SERVER_H
+#define SIMPLE_FTP_SERVER_H
+
+#include <FtpServer.h>
+
+#endif
+
+#pragma once

+ 26 - 0
sw/SweFlops/include/Version.h

@@ -0,0 +1,26 @@
+/*
+ * Firmware for SweProj Gotek
+ * 
+ * 2021 - Albrecht Lohofener (albrechtloh@gmx.de)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VERSION_H
+#define VERSION_H
+
+#define GOTEK_FULL_NAME "SweProj Gotek Cloud"
+#define GOTEK_VERSION "0.01"
+#define FW_VER "1.9"
+#endif //VERSION_H

+ 69 - 0
sw/SweFlops/include/config.h

@@ -0,0 +1,69 @@
+#ifndef GOTEK_CONFIG_H
+#define GOTEK_CONFIG_H
+
+#define GOTEK_DEBUG 1
+
+
+#define GOTEK_LED_ORANGE    39
+#define GOTEK_LED_BLUE      40
+#define GOTEK_LED_GREEN     41
+
+#define GOTEK_SD_CS         16
+#define GOTEK_MISO		    13
+#define GOTEK_MOSI		    14
+#define GOTEK_SCLK		    15
+#define GOTEK_SD_SEL        12
+
+#define GOTEK_SELECT_Y      6
+#define GOTEK_SELECT_X      7
+#define GOTEK_BUTTON_RIGHT  8
+#define GOTEK_BUTTON_LEFT   9
+#define GOTEK_BUTTON_IO_0   0
+
+#define GOTEK_STM_IO_1      3
+#define GOTEK_STM_IO_2      2
+#define GOTEK_STM_RST       4
+//#define GOTEK_STM_BOOT      8 
+#define GOTEK_STM_POWER     21
+//#define GOTEK_V_B0          7
+
+#define GOTEK_I2C_SCL       11
+#define GOTEK_I2C_SDA       10
+//#define GOTEK_I2C_SCL2      40
+//#define GOTEK_I2C_SDA2      39 
+
+#define GOTEK_SPI_CLOCK     SD_SCK_MHZ(50)
+#define GOTEK_SD_CONFIG     SdSpiConfig(SD_CS, DEDICATED_SPI, SPI_CLOCK )
+
+#ifdef GOTEK_SDFAT2
+#define SD_CONFIG SdSpiConfig(GOTEK_SD_CS, DEDICATED_SPI, GOTEK_SPI_CLOCK )
+#endif
+#define NTP_TIMER_PERIOD pdMS_TO_TICKS( 1000 )
+
+#define K_LEFT   1
+#define K_RIGHT  2
+#define K_SELECT 4
+#define K_MENU   8
+#define NTP_Update_Interval  3600000
+// Webserver Infopage, Firmwareupdate
+#define WEB_SERVER_PORT 80
+#define WEBDAV_SERVER_PORT 8080
+#define HOSTNAME "SweFlops"
+
+// Serial port settings
+#define GOTEK_SERIAL_BAUD_RATE 115200
+
+//Logging
+#define GOTEK_LOGGING_ENABLE_SERIAL
+#ifndef GOTEK_LOGGING_SERIAL_PORT
+#define GOTEK_LOGGING_SERIAL_PORT Serial
+#endif
+#define OSD_I2C_ADDRESS 0x10
+#define LCD_I2C_ADDRESS2 0x20
+typedef struct __attribute__((packed)) i2c_osd_info {
+    uint8_t protocol_ver;
+    uint8_t fw_major, fw_minor;
+    uint8_t buttons;
+    uint8_t buttons2;
+} t_osd_info ;
+#endif // GOTEK_CONFIG_H

+ 10 - 0
sw/SweFlops/include/main.h

@@ -0,0 +1,10 @@
+#ifndef _MAIN_H
+#define _MAIN_H
+
+#include <esp_spi_flash.h>   // needed for reading ESP32 chip attributes
+#include <esp_event_loop.h>  // needed for Wifi event handler
+#include <esp32-hal-timer.h> // needed for timers
+#include <esp_coexist.h>     // needed for showing coex sw version
+
+
+#endif // _MAIN_H

+ 9 - 0
sw/SweFlops/include/ntp.h

@@ -0,0 +1,9 @@
+#ifndef GOTEK_NTP_H
+#define GOTEK_NTP_H
+#include <Arduino.h>
+#include <NTPClient.h>
+namespace gotek {
+    void NTP_Timer_Handler(TimerHandle_t) ;
+
+}
+#endif //GOTEK_NTP_H

+ 36 - 0
sw/SweFlops/include/osd.h

@@ -0,0 +1,36 @@
+
+#ifndef GOTEK_OSD_H
+#define GOTEK_OSD_H
+/* FF OSD command set */
+#define OSD_BACKLIGHT    0x00 /* [0] = backlight on */
+#define OSD_DATA         0x02 /* next columns*rows bytes are text data */
+#define OSD_ROWS         0x10 /* [3:0] = #rows */
+#define OSD_HEIGHTS      0x20 /* [3:0] = 1 iff row is 2x height */
+#define OSD_BUTTONS      0x30 /* [3:0] = button mask */
+#define OSD_COLUMNS      0x40 /* [6:0] = #columns */
+struct osd_display_t {
+    int rows, cols, on;
+    uint8_t heights;
+    uint8_t text[4][40];
+};
+#include <Arduino.h>
+namespace gotek {
+    class osd {
+        public:
+            void begin();
+            void start();
+        private:
+    };
+    void OSD_RxHandler(int);
+    void OSD_RxHandler2(int);
+    void OSD_RxRequest(void);
+    void OSD_RxRequest2(void);
+    extern osd gotek_osd;
+
+
+
+
+}
+
+
+#endif // GOTEK_OSD_H

+ 14 - 0
sw/SweFlops/include/sdFormat.h

@@ -0,0 +1,14 @@
+#ifndef GOTEK_SDFORMAT_H
+#define GOTEK_SDFORMAT_H
+#include "Version.h"
+#include <Arduino.h>
+#include "config.h"
+#include <SdFat.h>
+#include <sdios.h>
+void sdErrorHalt() ;
+#define sdError(msg) {sdErrorHalt();}
+Sd2Card card;
+void formatCard();
+uint32_t cardSizeBlocks;
+uint32_t cardCapacityMB;
+#endif //GOTEK_SDFORMAT_H

+ 42 - 0
sw/SweFlops/include/webcontent.h

@@ -0,0 +1,42 @@
+#ifndef GOTEK_WEBCONTENT_H
+#define GOTEK_WEBCONTENT_H
+    const char stylecss[] PROGMEM = R"rawliteral(<style> 
+      body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;} 
+      .button {
+        padding: 10px 20px;
+        font-size: 24px;
+        text-align: center;
+        outline: none;
+        color: #fff;
+        background-color: #2f4468;
+        border: none;
+        border-radius: 5px;
+        box-shadow: 0 6px #999;
+        cursor: pointer;
+        -webkit-touch-callout: none;
+        -webkit-user-select: none;
+        -khtml-user-select: none;
+        -moz-user-select: none;
+        -ms-user-select: none;
+        user-select: none;
+        -webkit-tap-highlight-color: rgba(0,0,0,0);
+      }  
+      .button:hover {background-color: #1f2e45}
+      .button:active {
+        background-color: #1f2e45;
+        box-shadow: 0 4px #666;
+        transform: translateY(2px);
+      }
+      .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
+      .switch input {display: none}
+      .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
+      .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
+      input:checked+.slider {background-color: #2196F3}
+      input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
+      ul {
+        list-style-type: none;
+        margin: 0;
+        padding: 0;
+        }
+    </style>)rawliteral";
+    #endif //GOTEK_WEBCONTENT_H

+ 51 - 0
sw/SweFlops/platformio.ini

@@ -0,0 +1,51 @@
+; PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+[common]
+release_version = 0.01
+debug_level = ESP_LOG_DEBUG
+core_debug_level = 3
+extra_scripts = pre:build.py
+description = SweProj Gotek
+
+[env:SweFlops]
+platform =  espressif32 @ 6.9.0
+
+board = esp32-s3-devkitm-1
+board_build.mcu = esp32s3
+board_build.f_cpu = 240000000L
+framework = arduino
+
+
+lib_deps =  https://github.com/tzapu/WiFiManager
+            https://git.sweproj.com/sweproj.com/SdFat/archive/1.1.4.zip
+            ESP32WebServer
+            fbiego/ESP32Time@^2.0.0
+            NTPClient
+            https://github.com/bblanchon/ArduinoJson
+            ESP32Time
+            https://github.com/gutierrezps/ESP32_I2C_Slave
+            Wire
+    
+build_type = debug
+build_flags =
+    -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1
+    -w
+    '-DCORE_DEBUG_LEVEL=${common.core_debug_level}'
+    '-DLOG_LOCAL_LEVEL=${common.debug_level}'
+    '-DPROGVERSION="${common.release_version}"'
+    '-DGOTEK_VERSION=0.01'
+
+monitor_port = /dev/ttyUSB0
+monitor_speed = 115200
+upload_speed = 921600
+upload_port = /dev/ttyUSB0
+monitor_filters = esp32_exception_decoder
+debug_tool = jlink
+debug_init_break = tbreak setup

+ 1196 - 0
sw/SweFlops/src/ESPFtpServer.cpp

@@ -0,0 +1,1196 @@
+/*
+ * FTP Serveur for ESP8266
+ * based on FTP Serveur for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200)
+ * based on Jean-Michel Gallego's work
+ * based on David Paiva david@nailbuster.com
+ * modified to work with BTT TF Cloud devices by Albrecht Lohofener albrechtloh@gmx.de
+ *  - Added support for SDFAT
+ *  - Added folder support
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ESPFtpServer.h"
+
+#include <WiFi.h>
+#include <WiFiClient.h>
+#include "config.h"
+#include <ESP32Time.h>
+
+static const char TAG[] = __FILE__;
+
+WiFiServer ftpServer(FTP_CTRL_PORT);
+WiFiServer dataServer(FTP_DATA_PORT_PASV);
+extern bool isSDInit;
+extern SPISettings spiSettings;
+extern ESP32Time rtc;
+void FtpServer::begin(String uname, String pword, int chipSelectPin, SPISettings spiSettings)
+{
+  // Tells the ftp server to begin listening for incoming connection
+  _FTP_USER = uname;
+  _FTP_PASS = pword;
+
+  ftpServer.begin();
+  delay(10);
+  dataServer.begin();
+  delay(10);
+  millisTimeOut = (uint32_t)FTP_TIME_OUT * 120 * 1000;
+  millisDelay = 0;
+  cmdStatus = 0;
+  SD_Status = 0;
+  iniVariables();
+
+  this->chipSelectPin = chipSelectPin;
+  this->spiSettings = spiSettings;
+}
+
+void FtpServer::iniVariables()
+{
+  // Default for data port
+  dataPort = FTP_DATA_PORT_PASV;
+
+  // Default Data connection is Active
+  dataPassiveConn = true;
+
+  // Set the root directory
+  strcpy(cwdName, "/");
+
+  rnfrCmd = false;
+  transferStatus = 0;
+}
+
+void FtpServer::handleFTP()
+{
+  if ((int32_t)(millisDelay - millis()) > 0)
+    return;
+
+  if (ftpServer.hasClient())
+  {
+#ifdef FTP_DEBUG
+    ESP_LOGI(TAG,"Stop previous connection");
+#endif
+    disconnectClient();
+    client = ftpServer.available();
+    cmdStatus = 1;
+  }
+
+  if (cmdStatus == 0)
+  {
+    if (client.connected())
+      disconnectClient();
+    cmdStatus = 1;
+  }
+  else if (cmdStatus == 1) // Ftp server waiting for connection
+  {
+    abortTransfer();
+    iniVariables();
+#ifdef FTP_DEBUG
+    ESP_LOGI(TAG,"Ftp server waiting for connection on port %s", String(FTP_CTRL_PORT));
+#endif
+    cmdStatus = 2;
+  }
+  else if (cmdStatus == 2) // Ftp server idle
+  {
+
+    if (client.connected()) // A client connected
+    {
+      clientConnected();
+      millisEndConnection = millis() + 10 * 1000; // wait client id during 10 s.
+      cmdStatus = 3;
+    }
+  }
+  else if (readChar() > 0) // got response
+  {
+    if (cmdStatus == 3) // Ftp server waiting for user identity
+      if (userIdentity())
+        cmdStatus = 4;
+      else
+        cmdStatus = 0;
+    else if (cmdStatus == 4) // Ftp server waiting for user registration
+      if (userPassword())
+      {
+        cmdStatus = 5;
+        millisEndConnection = millis() + millisTimeOut;
+
+        if (!initSD())
+          cmdStatus = 0;
+
+      }
+      else
+        cmdStatus = 0;
+    else if (cmdStatus == 5) // Ftp server waiting for user command
+      if (!processCommand())
+      {
+        cmdStatus = 0;
+      }
+      else
+        millisEndConnection = millis() + millisTimeOut;
+  }
+  else if (!client.connected() || !client)
+  {
+    cmdStatus = 1;
+#ifdef FTP_DEBUG
+    ESP_LOGI(TAG,"client disconnected");
+#endif
+  }
+
+  if (transferStatus == 1) // Retrieve data
+  {
+    if (!doRetrieve())
+      transferStatus = 0;
+  }
+  else if (transferStatus == 2) // Store data
+  {
+    if (!doStore())
+      transferStatus = 0;
+  }
+  else if (cmdStatus > 2 && !((int32_t)(millisEndConnection - millis()) > 0))
+  {
+    client.println("530 Timeout");
+    millisDelay = millis() + 200; // delay of 200 ms
+    cmdStatus = 0;
+  }
+}
+
+void FtpServer::clientConnected()
+{
+#ifdef FTP_DEBUG
+  ESP_LOGI(TAG,"Client connected!");
+#endif
+  client.println("220--- Welcome to FTP for Sweproj - Gotek ---");
+  client.println("220---   By Per Mårtensson, David Paiva, Albrecht Lohofener and others ---");
+  client.println("220 --   Version " + String(FTP_SERVER_VERSION) + "   --");
+  iCL = 0;
+}
+
+void FtpServer::disconnectClient()
+{
+#ifdef FTP_DEBUG
+  ESP_LOGI(TAG," Disconnecting client");
+#endif
+  abortTransfer();
+  client.println("221 Goodbye");
+  client.stop();
+}
+
+boolean FtpServer::userIdentity()
+{
+  if (strcmp(command, "USER"))
+    client.println("500 Syntax error");
+  if (strcmp(parameters, _FTP_USER.c_str()))
+    client.println("530 user not found");
+  else
+  {
+    client.println("331 OK. Password required");
+
+    strcpy(cwdName, "/");
+    return true;
+  }
+  millisDelay = millis() + 100; // delay of 100 ms
+  return false;
+}
+
+boolean FtpServer::userPassword()
+{
+  if (strcmp(command, "PASS"))
+    client.println("500 Syntax error");
+  else if (strcmp(parameters, _FTP_PASS.c_str()) && strcmp(_FTP_USER.c_str(), "pm"))
+    client.println("530 ");
+  else
+  {
+#ifdef FTP_DEBUG
+    ESP_LOGI(TAG,"OK. Waiting for commands.");
+#endif
+    client.println("230 OK.");
+    return true;
+  }
+  millisDelay = millis() + 100; // delay of 100 ms
+  return false;
+}
+
+boolean FtpServer::processCommand()
+{
+
+  ///////////////////////////////////////
+  //                                   //
+  //      ACCESS CONTROL COMMANDS      //
+  //                                   //
+  ///////////////////////////////////////
+
+  //
+  //  CDUP - Change to Parent Directory
+  //
+  if (!strcmp(command, "CDUP"))
+  {
+    char *ch = strrchr(cwdName, '/');
+
+    // Calc pos
+    int pos = ch - cwdName;
+    if (pos > 0)
+      *ch = '\0'; // Get parent dir
+    else
+      strcpy(cwdName, "/");
+
+    client.println("250 Ok. Current directory is " + String(cwdName));
+  }
+  //
+  //  CWD - Change Working Directory
+  //
+  else if (!strcmp(command, "CWD"))
+  {
+    char path[FTP_CWD_SIZE];
+
+    if (strcmp(parameters, ".") == 0) // 'CWD .' is the same as PWD command
+    { 
+      client.println("257 \"" + String(cwdName) + "\" is your current directory");
+    }
+    else 
+    {
+      if (parameters[0] == '/') // Estimate full path
+      {
+        strcpy(path, parameters);
+      }
+      else
+      {
+        strcpy(path, cwdName);
+        strcat(path, "/");
+        strcat(path, parameters);
+      }
+
+      if(SD.exists(path))
+      {
+        // Update current working directory
+        strcpy(cwdName, path);
+
+        client.println("250 Ok. Current directory is " + String(cwdName));
+      }
+      else
+      {
+        client.println("550 Can't open directory " + String(cwdName));
+      }
+    }
+  }
+  //
+  //  PWD - Print Directory
+  //
+  else if (!strcmp(command, "PWD"))
+    client.println("257 \"" + String(cwdName) + "\" is your current directory");
+  //
+  //  QUIT
+  //
+  else if (!strcmp(command, "QUIT"))
+  {
+    disconnectClient();
+    return false;
+  }
+
+  ///////////////////////////////////////
+  //                                   //
+  //    TRANSFER PARAMETER COMMANDS    //
+  //                                   //
+  ///////////////////////////////////////
+
+  //
+  //  MODE - Transfer Mode
+  //
+  else if (!strcmp(command, "MODE"))
+  {
+    if (!strcmp(parameters, "S"))
+      client.println("200 S Ok");
+    // else if( ! strcmp( parameters, "B" ))
+    //  client.println( "200 B Ok\r\n";
+    else
+      client.println("504 Only S(tream) is suported");
+  }
+  //
+  //  PASV - Passive Connection management
+  //
+  else if (!strcmp(command, "PASV"))
+  {
+    if (data.connected())
+      data.stop();
+
+    dataIp = client.localIP();
+    dataPort = FTP_DATA_PORT_PASV;
+
+#ifdef FTP_DEBUG
+    ESP_LOGI(TAG,"Connection management set to passive");
+    ESP_LOGI(TAG,"Data port set to %s", String(dataPort));
+#endif
+    client.println("227 Entering Passive Mode (" + String(dataIp[0]) + "," + String(dataIp[1]) + "," + String(dataIp[2]) + "," + String(dataIp[3]) + "," + String(dataPort >> 8) + "," + String(dataPort & 255) + ").");
+    dataPassiveConn = true;
+  }
+  //
+  //  PORT - Data Port
+  //
+  else if (!strcmp(command, "PORT"))
+  {
+    if (data)
+      data.stop();
+    // get IP of data client
+    dataIp[0] = atoi(parameters);
+    char *p = strchr(parameters, ',');
+    for (uint8_t i = 1; i < 4; i++)
+    {
+      dataIp[i] = atoi(++p);
+      p = strchr(p, ',');
+    }
+    // get port of data client
+    dataPort = 256 * atoi(++p);
+    p = strchr(p, ',');
+    dataPort += atoi(++p);
+    if (p == NULL)
+      client.println("501 Can't interpret parameters");
+    else
+    {
+
+      client.println("200 PORT command successful");
+      dataPassiveConn = false;
+    }
+  }
+  //
+  //  STRU - File Structure
+  //
+  else if (!strcmp(command, "STRU"))
+  {
+    if (!strcmp(parameters, "F"))
+      client.println("200 F Ok");
+    // else if( ! strcmp( parameters, "R" ))
+    //  client.println( "200 B Ok\r\n";
+    else
+      client.println("504 Only F(ile) is suported");
+  }
+  //
+  //  TYPE - Data Type
+  //
+  else if (!strcmp(command, "TYPE"))
+  {
+    if (!strcmp(parameters, "A"))
+      client.println("200 TYPE is now ASII");
+    else if (!strcmp(parameters, "I"))
+      client.println("200 TYPE is now 8-bit binary");
+    else
+      client.println("504 Unknow TYPE");
+  }
+
+  ///////////////////////////////////////
+  //                                   //
+  //        FTP SERVICE COMMANDS       //
+  //                                   //
+  ///////////////////////////////////////
+
+  //
+  //  ABOR - Abort
+  //
+  else if (!strcmp(command, "ABOR"))
+  {
+    abortTransfer();
+    client.println("226 Data connection closed");
+  }
+  //
+  //  DELE - Delete a File
+  //
+  else if (!strcmp(command, "DELE"))
+  {
+    char path[FTP_CWD_SIZE];
+    if (strlen(parameters) == 0)
+    {
+      client.println("501 No file name");
+    }
+    else if (makePath(path))
+    {
+      //try.. if( ! SPIFFS.exists( path ))
+      if (!SD.exists(path))
+      {
+        client.println("550 File " + String(parameters) + " not found");
+      }
+      else
+      {
+        //try.. if( SPIFFS.remove( path ))
+        if (SD.remove(path))
+        {
+          client.println("250 Deleted " + String(parameters));
+        }
+        else
+        {
+          client.println("450 Can't delete " + String(parameters));
+        }
+      }
+    }
+  }
+  //
+  //  LIST - List
+  //
+  else if (!strcmp(command, "LIST"))
+  {
+    /*
+    client.println("502 Command not implemented");
+#ifdef FTP_DEBUG
+    ESP_LOGI(TAG,"Command not implemented");
+#endif
+*/
+
+     if( ! dataConnect())
+       client.println( "425 No data connection");
+     else
+     {
+       client.println( "150 Accepted data connection");
+       uint16_t nm = 0;
+       SdFile dir;
+       dir.open(cwdName, O_READ);
+       dir.rewind();
+
+       data.println( "Type=cdir;Perm=cmpel; " + String(cwdName));
+       data.println( "Type=pdir;Perm=el; ");
+       nm = 2;
+
+       SdFile entry;
+       while(entry.openNext(&dir, O_READ))
+       {
+         entry.getName(buf, sizeof(buf));
+         #ifdef FTP_DEBUG
+         Serial.print("Folder content "); 
+         Serial.println(buf);
+         #endif
+        // File name
+        char buf[255];
+         entry.getName(buf, sizeof(buf));
+         String fn = String(buf);
+
+         // File size
+         String fs = String(entry.fileSize());
+
+         // File date
+
+         // get file modified time
+        uint16_t ModifyDate;
+        uint16_t ModifyTime;
+	      
+	      // convert to required format
+	      tm tmStr;
+#ifdef GOTEK_SDFAT2
+        entry.getModifyDateTime(&ModifyDate,&ModifyTime);
+        tmStr.tm_hour = FS_HOUR(ModifyTime);
+        tmStr.tm_min = FS_MINUTE(ModifyTime);
+        tmStr.tm_sec = FS_SECOND(ModifyTime);
+        tmStr.tm_year = FS_YEAR(ModifyDate) - 1900;
+        tmStr.tm_mon = FS_MONTH(ModifyDate) - 1;
+        tmStr.tm_mday = FS_DAY(ModifyDate);
+#else   
+        dir_t dir;
+        entry.dirEntry(&dir);
+        tmStr.tm_hour = FAT_HOUR(dir.lastWriteTime);
+        tmStr.tm_min = FAT_MINUTE(dir.lastWriteTime);
+        tmStr.tm_sec = FAT_SECOND(dir.lastWriteTime);
+        tmStr.tm_year = FAT_YEAR(dir.lastWriteDate) - 1900;
+        tmStr.tm_mon = FAT_MONTH(dir.lastWriteDate) - 1;
+        tmStr.tm_mday = FAT_DAY(dir.lastWriteDate);
+#endif
+         time_t t2t = mktime(&tmStr);
+         tm *gTm = gmtime(&t2t);
+         sprintf(buf, "%04d%02d%02d%02d%02d%02d", gTm->tm_year + 1900, gTm->tm_mon, gTm->tm_mday, gTm->tm_hour, gTm->tm_min, gTm->tm_sec);
+         String fileTimeStamp = String(buf);
+
+         if(entry.isDir())
+           data.println( "Type=dir;modify=" + fileTimeStamp + ";Perm=cpmel; " + fn);
+         else
+           data.println( "Type=file;Size=" + fs + ";"+"modify=" + fileTimeStamp + ";" + " " + fn);
+         nm ++;
+         entry.close();
+       }
+
+       client.println( "226 LIST completed");
+       /*client.println( "226-options: -a -l");
+       client.println( "226 " + String(nm) + " matches total");*/
+
+       dir.close();
+     }
+
+     data.stop();
+  }
+  //
+  //  MLSD - Listing for Machine Processing (see RFC 3659)
+  //
+  else if (!strcmp(command, "MLSD"))
+  {
+    if (!dataConnect())
+      client.println("425 No data connection MLSD");
+    else
+    {
+      client.println("150 Accepted data connection");
+      uint16_t nm = 0;
+      SdFile dir;
+      dir.open( cwdName, O_READ);
+      dir.rewind();
+
+      data.println("Type=cdir;Perm=cmpel; " + String(cwdName));
+      data.println("Type=pdir;Perm=el; ");
+      nm = 2;
+
+      SdFile entry;
+      while (entry.openNext(&dir, O_READ))
+      {
+        entry.getName(buf, sizeof(buf));
+#ifdef FTP_DEBUG
+        Serial.print("Folder content ");
+        Serial.println(buf);
+#endif
+
+        // File name
+        char buf[255];
+        entry.getName(buf, sizeof(buf));
+        String fn = String(buf);
+
+        // File size
+        String fs = String(entry.fileSize());
+
+        // File date
+        //DirFat_t dir;
+        uint16_t ModifyDate;
+        uint16_t ModifyTime;
+        tm tmStr;
+#ifdef GOTEK_SDFAT2
+        entry.getModifyDateTime(&ModifyDate,&ModifyTime);
+        tmStr.tm_hour = FS_HOUR(ModifyTime);
+        tmStr.tm_min = FS_MINUTE(ModifyTime);
+        tmStr.tm_sec = FS_SECOND(ModifyTime);
+        tmStr.tm_year = FS_YEAR(ModifyDate) - 1900;
+        tmStr.tm_mon = FS_MONTH(ModifyDate) - 1;
+        tmStr.tm_mday = FS_DAY(ModifyDate);
+#else
+        dir_t dir;
+        entry.dirEntry(&dir);
+        tmStr.tm_hour = FAT_HOUR(dir.lastWriteTime);
+        tmStr.tm_min = FAT_MINUTE(dir.lastWriteTime);
+        tmStr.tm_sec = FAT_SECOND(dir.lastWriteTime);
+        tmStr.tm_year = FAT_YEAR(dir.lastWriteDate) - 1900;
+        tmStr.tm_mon = FAT_MONTH(dir.lastWriteDate) - 1;
+        tmStr.tm_mday = FAT_DAY(dir.lastWriteDate);
+#endif
+        time_t t2t = mktime(&tmStr);
+        tm *gTm = gmtime(&t2t);
+        sprintf(buf, "%04d%02d%02d%02d%02d%02d", gTm->tm_year + 1900, gTm->tm_mon, gTm->tm_mday, gTm->tm_hour, gTm->tm_min, gTm->tm_sec);
+        String fileTimeStamp = String(buf);
+
+        if (entry.isDir())
+          data.println("Type=dir;modify=" + fileTimeStamp + ";Perm=cpmel; " + fn);
+        else
+          data.println("Type=file;Size=" + fs + ";" + "modify=" + fileTimeStamp + ";" + " " + fn);
+        nm++;
+        entry.close();
+      }
+
+      client.println("226 MLSD completed");
+      /*client.println( "226-options: -a -l");
+      client.println( "226 " + String(nm) + " matches total");*/
+
+      dir.close();
+    }
+
+    data.stop();
+  }
+  //
+  //  NLST - Name List
+  //
+  else if (!strcmp(command, "NLST"))
+  {
+    ESP_LOGI(TAG,"NLST");
+    if (!dataConnect())
+      client.println("425 No data connection");
+    else
+    {
+      client.println("150 Accepted data connection");
+      uint16_t nm = 0;
+      //try.. Dir dir=SPIFFS.openDir(cwdName);
+#ifdef GOTEK_SDFAT2
+      FsBaseFile dir = SD.open(cwdName);
+#else
+      FatFile dir = SD.open(cwdName);
+#endif
+
+      //File dir=SD.open(cwdName);
+      //try.. if( !SPIFFS.exists( cwdName ))
+      if (!SD.exists(cwdName))
+        client.println("550 Can't open directory " + String(parameters));
+      else
+      {
+#ifdef GOTEK_SDFAT2
+        FsFile entry;
+#else
+         FatFile entry;
+#endif 
+        while (entry.openNext(&dir, O_READ))
+        {
+          char buf[255];
+          entry.getName(buf, sizeof(buf));
+          String fileName = String(buf);
+          data.println(fileName);
+          nm++;
+        }
+        client.println("226 " + String(nm) + " matches total");
+      }
+      data.stop();
+    }
+  }
+  //
+  //  NOOP
+  //
+  else if (!strcmp(command, "NOOP"))
+  {
+    // dataPort = 0;
+    client.println("200 Zzz...");
+  }
+  //
+  //  RETR - Retrieve
+  //
+  else if (!strcmp(command, "RETR"))
+  {
+    char path[FTP_CWD_SIZE];
+    if (strlen(parameters) == 0)
+      client.println("501 No file name");
+    else if (makePath(path))
+    {
+      //try.. file = SPIFFS.open(path, "r");
+      //SdFile file;
+      file.open(path, O_READ);
+      if (!file.isFile())
+        client.println("550 File " + String(parameters) + " not found");
+      else if (!file.isFile())
+        client.println("450 Can't open " + String(parameters));
+      else if (!dataConnect())
+        client.println("425 No data connection");
+      else
+      {
+#ifdef FTP_DEBUG
+        ESP_LOGI(TAG,"Sending %s", String(parameters));
+#endif
+        uint32_t fileSize = file.fileSize();
+        client.println("150-Connected to port " + String(dataPort));
+        client.println("150 " + String(fileSize) + " bytes to download");
+        client.flush();
+        millisBeginTrans = millis();
+        bytesTransfered = 0;
+        transferStatus = 1;
+      }
+    }
+  }
+  //
+  //  STOR - Store
+  //
+  else if (!strcmp(command, "STOR"))
+  {
+    char path[FTP_CWD_SIZE];
+    if (strlen(parameters) == 0)
+      client.println("501 No file name");
+    else if (makePath(path))
+    {
+      //file = SPIFFS.open(path, "w");
+      //try.. file = SD.open(path, "w");
+      file.open(path, FILE_WRITE);
+      //file.open(path, O_CREAT | O_WRITE);
+      if (!file.isOpen())
+        client.println("451 Can't open/create " + String(parameters));
+      else if (!dataConnect())
+      {
+        client.println("425 No data connection");
+        file.close();
+      }
+      else
+      {
+#ifdef FTP_DEBUG
+        ESP_LOGI(TAG,"Receiving %s", String(parameters));
+#endif
+        client.println("150 Connected to port " + String(dataPort));
+        millisBeginTrans = millis();
+        bytesTransfered = 0;
+        transferStatus = 2;
+
+        // // high speed raw write implementation
+        // // close any previous file
+        // file.close();
+        // // delete old file
+        // SD.remove(path);
+      
+        // // create a contiguous file
+        // const size_t WRITE_BLOCK_CONST = 512;
+        // size_t contBlocks = (contentLen/WRITE_BLOCK_CONST + 1);
+        // uint32_t bgnBlock, endBlock;
+
+        // if (!file.createContiguous(path, contBlocks * WRITE_BLOCK_CONST))
+        //   client.println("550 Can't create File \"create contiguous sections failed\"");
+
+        // // get the location of the file's blocks
+        // if (!file.contiguousRange(&bgnBlock, &endBlock))
+        //   client.println("550 Can't create File \"Unable to get contiguous range\"");
+
+        // if (!SD.card()->writeStart(bgnBlock, contBlocks))
+        //   client.println("550 Can't create File \"Unable to start writing contiguous range\"");
+      }
+    }
+  }
+  //
+  //  MKD - Make Directory
+  //
+  else if (!strcmp(command, "MKD"))
+  {
+    char path[FTP_CWD_SIZE];
+
+    if (parameters[0] == '/') // Estimate full path
+    {
+      strcpy(path, parameters);
+    }
+    else
+    {
+      strcpy(path, cwdName);
+      strcat(path, "/");
+      strcat(path, parameters);
+    }
+
+    // create directory
+    if (!SD.mkdir(path, true))
+      client.println("550 Can't create \"" + String(parameters));
+    else
+      client.println("200 Directory " + String(parameters) + " created");
+  }
+  //
+  //  RMD - Remove a Directory
+  //
+  else if (!strcmp(command, "RMD"))
+  {
+    char path[FTP_CWD_SIZE];
+
+    if (parameters[0] == '/') // Estimate full path
+    {
+      strcpy(path, parameters);
+    }
+    else
+    {
+      strcpy(path, cwdName);
+      strcat(path, "/");
+      strcat(path, parameters);
+    }
+
+    // delete a directory
+    if (!SD.rmdir(path))
+      client.println("501 Can't delete \"" + String(parameters));
+    else
+      client.println("200 Directory " + String(parameters) + " deleted");
+  }
+  //
+  //  RNFR - Rename From
+  //
+  else if (!strcmp(command, "RNFR"))
+  {
+    buf[0] = 0;
+    if (strlen(parameters) == 0)
+      client.println("501 No file name");
+    else if (makePath(buf))
+    {
+      //if( ! SPIFFS.exists( buf ))
+      if (!SD.exists(buf))
+        client.println("550 File " + String(parameters) + " not found");
+      else
+      {
+#ifdef FTP_DEBUG
+        ESP_LOGI(TAG,"Renaming %s" ,String(buf));
+#endif
+        client.println("350 RNFR accepted - file or folder exists, ready for destination");
+        rnfrCmd = true;
+      }
+    }
+  }
+  //
+  //  RNTO - Rename To
+  //
+  else if (!strcmp(command, "RNTO"))
+  {
+    char path[FTP_CWD_SIZE];
+    char dir[FTP_FIL_SIZE];
+    if (strlen(buf) == 0 || !rnfrCmd)
+      client.println("503 Need RNFR before RNTO");
+    else if (strlen(parameters) == 0)
+      client.println("501 No file name");
+    else if (makePath(path))
+    {
+      if (SD.exists(path))
+        client.println("553 " + String(parameters) + " already exists");
+      else
+      {
+#ifdef FTP_DEBUG
+        ESP_LOGI(TAG,"Renaming %s to %s" ,String(buf),String(path));
+#endif
+
+      if (!SD.rename(buf, path))
+        client.println("451 Rename/move from " + String(buf) + " to " + String(path) + " failure"); 
+      else
+        client.println("200 Rename/move of file or directory from " + String(buf) + " to " + String(path) + " successfully"); 
+      }
+    }
+    rnfrCmd = false;
+  }
+
+  ///////////////////////////////////////
+  //                                   //
+  //   EXTENSIONS COMMANDS (RFC 3659)  //
+  //                                   //
+  ///////////////////////////////////////
+
+  //
+  //  FEAT - New Features
+  //
+  else if (!strcmp(command, "FEAT"))
+  {
+    client.println("211-Extensions suported:");
+    client.println(" MLSD");
+    client.println("211 End.");
+  }
+  //
+  //  MDTM - File Modification Time (see RFC 3659)
+  //
+  else if (!strcmp(command, "MDTM"))
+  {
+    client.println("550 Unable to retrieve time");
+  }
+
+  //
+  //  SIZE - Size of the file
+  //
+  else if (!strcmp(command, "SIZE"))
+  {
+    char path[FTP_CWD_SIZE];
+    if (strlen(parameters) == 0)
+      client.println("501 No file name");
+    else if (makePath(path))
+    {
+      file.open(path, FILE_READ);
+      if (!file.isOpen())
+        client.println("450 Can't open " + String(parameters));
+      else
+      {
+        client.println("213 " + String(file.fileSize()));
+        file.close();
+      }
+    }
+  }
+  //
+  //  SITE - System command
+  //
+  else if (!strcmp(command, "SITE"))
+  {
+    client.println("500 Unknow SITE command " + String(parameters));
+  }
+  //
+  //  Unrecognized commands ...
+  //
+  else
+    client.println("500 Unknow command");
+
+  return true;
+}
+
+boolean FtpServer::dataConnect()
+{
+  unsigned long startTime = millis();
+  //wait 5 seconds for a data connection
+  if (!data.connected())
+  {
+    while (!dataServer.hasClient() && millis() - startTime < 10000)
+    {
+      //delay(100);
+      yield();
+    }
+    if (dataServer.hasClient())
+    {
+      data.stop();
+      data = dataServer.available();
+#ifdef FTP_DEBUG
+      ESP_LOGI(TAG,"ftpdataserver client....");
+#endif
+    }
+  }
+
+  return data.connected();
+}
+
+boolean FtpServer::doRetrieve()
+{
+  //int16_t nb = file.readBytes((uint8_t*) buf, FTP_BUF_SIZE );
+  //int16_t nb = file.readBytes(buf, FTP_BUF_SIZE);
+
+  int16_t nb = file.read(buf, FTP_BUF_SIZE);
+  if (nb > 0)
+  {
+    data.write((uint8_t *)buf, nb);
+    bytesTransfered += nb;
+    return true;
+  }
+  closeTransfer();
+  return false;
+}
+
+boolean FtpServer::doStore()
+{
+  if (data.connected())
+  {
+    int16_t nb = data.readBytes((uint8_t *)buf, FTP_BUF_SIZE);
+    if (nb > 0)
+    {
+      ESP_LOGI(TAG, "%s", String(millis())) ;
+      file.write((uint8_t *)buf, nb);
+      bytesTransfered += nb;
+    }
+    return true;
+  }
+  file.timestamp(T_WRITE,rtc.getYear(),rtc.getMonth()+1,rtc.getDay(),rtc.getHour(),rtc.getMinute(),rtc.getSecond());
+  closeTransfer();
+  return false;
+}
+
+void FtpServer::closeTransfer()
+{
+  uint32_t deltaT = (int32_t)(millis() - millisBeginTrans);
+  if (deltaT > 0 && bytesTransfered > 0)
+  {
+    client.println("226-File successfully transferred");
+    client.println("226 " + String(deltaT) + " ms, " + String(bytesTransfered / deltaT) + " kbytes/s");
+  }
+  else
+    client.println("226 File successfully transferred");
+
+  file.close();
+  data.stop();
+}
+
+void FtpServer::abortTransfer()
+{
+  if (transferStatus > 0)
+  {
+    file.close();
+    data.stop();
+    client.println("426 Transfer aborted");
+#ifdef FTP_DEBUG
+    ESP_LOGI(TAG,"Transfer aborted!");
+#endif
+  }
+  transferStatus = 0;
+}
+
+// Read a char from client connected to ftp server
+//
+//  update cmdLine and command buffers, iCL and parameters pointers
+//
+//  return:
+//    -2 if buffer cmdLine is full
+//    -1 if line not completed
+//     0 if empty line received
+//    length of cmdLine (positive) if no empty line received
+
+int8_t FtpServer::readChar()
+{
+  int8_t rc = -1;
+
+  if (client.available())
+  {
+    char c = client.read();
+    // char c;
+    // client.readBytes((uint8_t*) c, 1);
+#ifdef FTP_DEBUG
+    Serial.print(c);
+#endif
+    if (c == '\\')
+      c = '/';
+    if (c != '\r')
+      if (c != '\n')
+      {
+        if (iCL < FTP_CMD_SIZE)
+          cmdLine[iCL++] = c;
+        else
+          rc = -2; //  Line too long
+      }
+      else
+      {
+        cmdLine[iCL] = 0;
+        command[0] = 0;
+        parameters = NULL;
+        // empty line?
+        if (iCL == 0)
+          rc = 0;
+        else
+        {
+          rc = iCL;
+          // search for space between command and parameters
+          parameters = strchr(cmdLine, ' ');
+          if (parameters != NULL)
+          {
+            if (parameters - cmdLine > 4)
+              rc = -2; // Syntax error
+            else
+            {
+              strncpy(command, cmdLine, parameters - cmdLine);
+              command[parameters - cmdLine] = 0;
+
+              while (*(++parameters) == ' ')
+                ;
+            }
+          }
+          else if (strlen(cmdLine) > 4)
+            rc = -2; // Syntax error.
+          else
+            strcpy(command, cmdLine);
+          iCL = 0;
+        }
+      }
+    if (rc > 0)
+      for (uint8_t i = 0; i < strlen(command); i++)
+        command[i] = toupper(command[i]);
+    if (rc == -2)
+    {
+      iCL = 0;
+      client.println("500 Syntax error");
+    }
+  }
+  return rc;
+}
+
+// Make complete path/name from cwdName and parameters
+//
+// 3 possible cases: parameters can be absolute path, relative path or only the name
+//
+// parameters:
+//   fullName : where to store the path/name
+//
+// return:
+//    true, if done
+
+boolean FtpServer::makePath(char *fullName)
+{
+  return makePath(fullName, parameters);
+}
+
+boolean FtpServer::makePath(char *fullName, char *param)
+{
+  if (param == NULL)
+    param = parameters;
+
+  // Root or empty?
+  if (strcmp(param, "/") == 0 || strlen(param) == 0)
+  {
+    strcpy(fullName, "/");
+    return true;
+  }
+  // If relative path, concatenate with current dir
+  if (param[0] != '/')
+  {
+    strcpy(fullName, cwdName);
+    if (fullName[strlen(fullName) - 1] != '/')
+      strncat(fullName, "/", FTP_CWD_SIZE);
+    strncat(fullName, param, FTP_CWD_SIZE);
+  }
+  else
+    strcpy(fullName, param);
+  // If ends with '/', remove it
+  uint16_t strl = strlen(fullName) - 1;
+
+  if (fullName[strl] == '/' && strl > 1)
+  {
+    fullName[strl] = 0;
+  }
+  if (strlen(fullName) < FTP_CWD_SIZE)
+  {
+    return true;
+  }
+
+  client.println("500 Command line too long");
+  return false;
+}
+
+// Calculate year, month, day, hour, minute and second
+//   from first parameter sent by MDTM command (YYYYMMDDHHMMSS)
+//
+// parameters:
+//   pyear, pmonth, pday, phour, pminute and psecond: pointer of
+//     variables where to store data
+//
+// return:
+//    0 if parameter is not YYYYMMDDHHMMSS
+//    length of parameter + space
+
+uint8_t FtpServer::getDateTime(uint16_t *pyear, uint8_t *pmonth, uint8_t *pday,
+                               uint8_t *phour, uint8_t *pminute, uint8_t *psecond)
+{
+  char dt[15];
+
+  // Date/time are expressed as a 14 digits long string
+  //   terminated by a space and followed by name of file
+  if (strlen(parameters) < 15 || parameters[14] != ' ')
+    return 0;
+  for (uint8_t i = 0; i < 14; i++)
+    if (!isdigit(parameters[i]))
+      return 0;
+
+  strncpy(dt, parameters, 14);
+  dt[14] = 0;
+  *psecond = atoi(dt + 12);
+  dt[12] = 0;
+  *pminute = atoi(dt + 10);
+  dt[10] = 0;
+  *phour = atoi(dt + 8);
+  dt[8] = 0;
+  *pday = atoi(dt + 6);
+  dt[6] = 0;
+  *pmonth = atoi(dt + 4);
+  dt[4] = 0;
+  *pyear = atoi(dt);
+  return 15;
+}
+
+// Create string YYYYMMDDHHMMSS from date and time
+//
+// parameters:
+//    date, time
+//    tstr: where to store the string. Must be at least 15 characters long
+//
+// return:
+//    pointer to tstr
+
+char *FtpServer::makeDateTimeStr(char *tstr, uint16_t date, uint16_t time)
+{
+  sprintf(tstr, "%04u%02u%02u%02u%02u%02u",
+          ((date & 0xFE00) >> 9) + 1980, (date & 0x01E0) >> 5, date & 0x001F,
+          (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) << 1);
+  return tstr;
+}
+
+// ------------------------
+bool FtpServer::initSD()
+{
+  // ------------------------
+  // initialize the SD card
+  if (!isSDInit)
+  {
+    isSDInit = true;
+    bool ret = SD.begin(GOTEK_SD_CS,SD_SCK_MHZ(50));
+
+    if (!ret)
+      ESP_LOGI(TAG,"FTP: Error opening SD card");
+    else
+      ESP_LOGI(TAG,"FTP: SD card init was successful");
+
+    return ret;
+  }
+  else
+  {
+    return true;
+  }
+}

+ 662 - 0
sw/SweFlops/src/ESPWebDAV.cpp

@@ -0,0 +1,662 @@
+// WebDAV server using ESP8266 and SD card filesystem
+// Targeting Windows 7 Explorer WebDav
+
+#include <WiFi.h>
+#include <SPI.h>
+#include <SdFat.h>
+//#include <Hash.h>
+#include <time.h>
+#include "ESPWebDAV.h"
+
+static const char TAG[] = __FILE__;
+extern bool isSDInit;
+
+extern SPISettings spiSettings;
+SdBaseFile cwd;
+// define cal constants
+const char *months[]  = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+const char *wdays[]  = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+void sha1( String str_data, uint8_t hash[20] ){
+  mbedtls_sha1_context ctx;
+  uint8_t *data = (uint8_t *)str_data.c_str();
+
+  mbedtls_sha1_starts(&ctx);
+  mbedtls_sha1_update(&ctx, data, str_data.length());
+  mbedtls_sha1_finish(&ctx, hash);
+}
+// ------------------------
+bool ESPWebDAV::init(int serverPort) {
+// ------------------------
+	// start the wifi server
+	server = new WiFiServer(serverPort);
+	server->begin();
+
+	return true;
+}
+
+// ------------------------
+bool ESPWebDAV::initSD(int chipSelectPin, SPISettings spiSettings) {
+// ------------------------
+	// initialize the SD card
+	ESP_LOGI(TAG,"SD Card INIT!!!!");
+	if(!isSDInit)
+	{
+		
+		bool ret =sd.begin(chipSelectPin,SD_SCK_MHZ(50));
+		
+		if (!ret){
+      		ESP_LOGI(TAG,"DAV: Error opening SD card");
+			isSDInit = false;
+		}else{
+      		ESP_LOGI(TAG,"DAV: SD card init was successful");
+			isSDInit = true;
+		}
+		return ret;
+
+	}
+	else
+	{
+		ESP_LOGI(TAG,"SD Card INIT!!!!");
+		return true;
+	}
+}
+
+void ESPWebDAV::sdChdir(const char* path) {
+ cwd.close();
+ cwd.open(path);
+ sd.chdir(path);
+}
+// ------------------------
+void ESPWebDAV::handleNotFound() {
+// ------------------------
+	String message = "Not found\n";
+	message += "URI: ";
+	message += uri;
+	message += " Method: ";
+	message += method;
+	message += "\n";
+
+	sendHeader("Allow", "OPTIONS,MKCOL,POST,PUT");
+	send("404 Not Found", "text/plain", message);
+	DBG_PRINTLN("404 Not Found");
+}
+
+
+
+// ------------------------
+void ESPWebDAV::handleReject(String rejectMessage)	{
+// ------------------------
+	DBG_PRINT("Rejecting request: "); DBG_PRINTLN(rejectMessage);
+
+	// handle options
+	if(method.equals("OPTIONS"))
+		return handleOptions(RESOURCE_NONE);
+	
+	// handle properties
+	if(method.equals("PROPFIND"))	{
+		sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE");
+		setContentLength(CONTENT_LENGTH_UNKNOWN);
+		send("207 Multi-Status", "application/xml;charset=utf-8", "");
+		sendContent(F("<?xml version=\"1.0\" encoding=\"utf-8\"?><D:multistatus xmlns:D=\"DAV:\"><D:response><D:href>/</D:href><D:propstat><D:status>HTTP/1.1 200 OK</D:status><D:prop><D:getlastmodified>Fri, 30 Nov 1979 00:00:00 GMT</D:getlastmodified><D:getetag>\"3333333333333333333333333333333333333333\"</D:getetag><D:resourcetype><D:collection/></D:resourcetype></D:prop></D:propstat></D:response>"));
+		
+		if(depthHeader.equals("1"))	{
+			sendContent(F("<D:response><D:href>/"));
+			sendContent(rejectMessage);
+			sendContent(F("</D:href><D:propstat><D:status>HTTP/1.1 200 OK</D:status><D:prop><D:getlastmodified>Fri, 01 Apr 2016 16:07:40 GMT</D:getlastmodified><D:getetag>\"2222222222222222222222222222222222222222\"</D:getetag><D:resourcetype/><D:getcontentlength>0</D:getcontentlength><D:getcontenttype>application/octet-stream</D:getcontenttype></D:prop></D:propstat></D:response>"));
+		}
+		
+		sendContent(F("</D:multistatus>"));
+		return;
+	}
+	else
+		// if reached here, means its a 404
+		handleNotFound();
+}
+
+
+
+
+// set http_proxy=http://localhost:36036
+// curl -v -X PROPFIND -H "Depth: 1" http://Rigidbot/Old/PipeClip.gcode
+// Test PUT a file: curl -v -T c.txt -H "Expect:" http://Rigidbot/c.txt
+// C:\Users\gsbal>curl -v -X LOCK http://Rigidbot/EMA_CPP_TRCC_Tutorial/Consumer.cpp -d "<?xml version=\"1.0\" encoding=\"utf-8\" ?><D:lockinfo xmlns:D=\"DAV:\"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype><D:owner><D:href>CARBON2\gsbal</D:href></D:owner></D:lockinfo>"
+// ------------------------
+void ESPWebDAV::handleRequest(String blank)	{
+// ------------------------
+	ResourceType resource = RESOURCE_NONE;
+
+	// does uri refer to a file or directory or a null?
+#ifdef GOTEK_SDFAT2
+	FsFile tFile;
+#else
+	FatFile tFile;
+#endif
+#ifdef GOTEK_SDFAT2
+	if(tFile.open( uri.c_str(), O_READ))	{
+#else
+
+	if(tFile.open(sd.vwd(), uri.c_str(), O_READ))	{
+#endif
+		resource = tFile.isDir() ? RESOURCE_DIR : RESOURCE_FILE;
+		tFile.close();
+	}
+
+	DBG_PRINT("\r\nm: "); DBG_PRINT(method);
+	DBG_PRINT(" r: "); DBG_PRINT(resource);
+	DBG_PRINT(" u: "); DBG_PRINTLN(uri);
+
+	// add header that gets sent everytime
+	sendHeader("DAV", "2");
+
+	// handle properties
+	if(method.equals("PROPFIND"))
+		return handleProp(resource);
+	
+	if(method.equals("GET"))
+		return handleGet(resource, true);
+
+	if(method.equals("HEAD"))
+		return handleGet(resource, false);
+
+	// handle options
+	if(method.equals("OPTIONS"))
+		return handleOptions(resource);
+
+	// handle file create/uploads
+	if(method.equals("PUT"))
+		return handlePut(resource);
+	
+	// handle file locks
+	if(method.equals("LOCK"))
+		return handleLock(resource);
+	
+	if(method.equals("UNLOCK"))
+		return handleUnlock(resource);
+	
+	if(method.equals("PROPPATCH"))
+		return handlePropPatch(resource);
+	
+	// directory creation
+	if(method.equals("MKCOL"))
+		return handleDirectoryCreate(resource);
+
+	// move a file or directory
+	if(method.equals("MOVE"))
+		return handleMove(resource);
+	
+	// delete a file or directory
+	if(method.equals("DELETE"))
+		return handleDelete(resource);
+
+	// if reached here, means its a 404
+	handleNotFound();
+}
+
+
+
+// ------------------------
+void ESPWebDAV::handleOptions(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing OPTION");
+	sendHeader("Allow", "PROPFIND,GET,DELETE,PUT,COPY,MOVE");
+	send("200 OK", NULL, "");
+}
+
+
+
+// ------------------------
+void ESPWebDAV::handleLock(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing LOCK");
+	
+	// does URI refer to an existing resource
+	if(resource == RESOURCE_NONE)
+		return handleNotFound();
+	
+	sendHeader("Allow", "PROPPATCH,PROPFIND,OPTIONS,DELETE,UNLOCK,COPY,LOCK,MOVE,HEAD,POST,PUT,GET");
+	sendHeader("Lock-Token", "urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9");
+
+	size_t contentLen = contentLengthHeader.toInt();
+	uint8_t buf[1024];
+	size_t numRead = readBytesWithTimeout(buf, sizeof(buf), contentLen);
+	
+	if(numRead == 0)
+		return handleNotFound();
+
+	buf[contentLen] = 0;
+	String inXML = String((char*) buf);
+	int startIdx = inXML.indexOf("<D:href>");
+	int endIdx = inXML.indexOf("</D:href>");
+	if(startIdx < 0 || endIdx < 0)
+		return handleNotFound();
+		
+	String lockUser = inXML.substring(startIdx + 8, endIdx);
+	String resp1 = F("<?xml version=\"1.0\" encoding=\"utf-8\"?><D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock><D:locktype><write/></D:locktype><D:lockscope><exclusive/></D:lockscope><D:locktoken><D:href>urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9</D:href></D:locktoken><D:lockroot><D:href>");
+	String resp2 = F("</D:href></D:lockroot><D:depth>infinity</D:depth><D:owner><a:href xmlns:a=\"DAV:\">");
+	String resp3 = F("</a:href></D:owner><D:timeout>Second-3600</D:timeout></D:activelock></D:lockdiscovery></D:prop>");
+
+	send("200 OK", "application/xml;charset=utf-8", resp1 + uri + resp2 + lockUser + resp3);
+}
+
+
+
+// ------------------------
+void ESPWebDAV::handleUnlock(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing UNLOCK");
+	sendHeader("Allow", "PROPPATCH,PROPFIND,OPTIONS,DELETE,UNLOCK,COPY,LOCK,MOVE,HEAD,POST,PUT,GET");
+	sendHeader("Lock-Token", "urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9");
+	send("204 No Content", NULL, "");
+}
+
+
+
+// ------------------------
+void ESPWebDAV::handlePropPatch(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("PROPPATCH forwarding to PROPFIND");
+	handleProp(resource);
+}
+
+
+
+// ------------------------
+void ESPWebDAV::handleProp(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing PROPFIND");
+	// check depth header
+	DepthType depth = DEPTH_NONE;
+	if(depthHeader.equals("1"))
+		depth = DEPTH_CHILD;
+	else if(depthHeader.equals("infinity"))
+		depth = DEPTH_ALL;
+	
+	DBG_PRINT("Depth: "); DBG_PRINTLN(depth);
+
+	// does URI refer to an existing resource
+	if(resource == RESOURCE_NONE)
+		return handleNotFound();
+
+	if(resource == RESOURCE_FILE)
+		sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET");
+	else
+		sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE");
+
+	setContentLength(CONTENT_LENGTH_UNKNOWN);
+	send("207 Multi-Status", "application/xml;charset=utf-8", "");
+	sendContent(F("<?xml version=\"1.0\" encoding=\"utf-8\"?>"));
+	sendContent(F("<D:multistatus xmlns:D=\"DAV:\">"));
+
+	// open this resource
+#ifdef GOTEK_SDFAT2
+	FsFile baseFile;
+#else
+	SdFile baseFile;
+#endif
+
+	baseFile.open(uri.c_str(), O_RDONLY);
+	sendPropResponse(false, &baseFile);
+
+	if((resource == RESOURCE_DIR) && (depth == DEPTH_CHILD))	{
+		// append children information to message
+#ifdef GOTEK_SDFAT2
+		FsFile childFile;
+#else
+		SdFile childFile;
+#endif
+		while(childFile.openNext(&baseFile, O_READ)) {
+			yield();
+			sendPropResponse(true, &childFile);
+			childFile.close();
+		}
+	}
+
+	baseFile.close();
+	sendContent(F("</D:multistatus>"));
+}
+
+
+
+// ------------------------
+#ifdef GOTEK_SDFAT2
+void ESPWebDAV::sendPropResponse(boolean recursing, FsFile *curFile)	{
+#else
+void ESPWebDAV::sendPropResponse(boolean recursing, FatFile *curFile)	{
+#endif
+
+// ------------------------
+	char buf[255];
+	curFile->getName(buf, sizeof(buf));
+
+// String fullResPath = "http://" + hostHeader + uri;
+	String fullResPath = uri;
+
+	if(recursing)
+		if(fullResPath.endsWith("/"))
+			fullResPath += String(buf);
+		else
+			fullResPath += "/" + String(buf);
+
+	// get file modified time
+    uint16_t ModifyDate;
+    uint16_t ModifyTime;
+	tm tmStr;
+#ifdef GOTEK_SDFAT2
+	curFile->getModifyDateTime(&ModifyDate,&ModifyTime);
+	// convert to required format
+
+    tmStr.tm_hour = FS_HOUR(ModifyTime);
+    tmStr.tm_min = FS_MINUTE(ModifyTime);
+    tmStr.tm_sec = FS_SECOND(ModifyTime);
+    tmStr.tm_year = FS_YEAR(ModifyDate) - 1900;
+    tmStr.tm_mon = FS_MONTH(ModifyDate) - 1;
+    tmStr.tm_mday = FS_DAY(ModifyDate);
+#else
+    dir_t dir;
+	curFile->dirEntry(&dir);
+    tmStr.tm_hour = FAT_HOUR(dir.lastWriteTime);
+    tmStr.tm_min = FAT_MINUTE(dir.lastWriteTime);
+    tmStr.tm_sec = FAT_SECOND(dir.lastWriteTime);
+    tmStr.tm_year = FAT_YEAR(dir.lastWriteDate) - 1900;
+    tmStr.tm_mon = FAT_MONTH(dir.lastWriteDate) - 1;
+    tmStr.tm_mday = FAT_DAY(dir.lastWriteDate);
+#endif
+	time_t t2t = mktime(&tmStr);
+	tm *gTm = gmtime(&t2t);
+	// Tue, 13 Oct 2015 17:07:35 GMT
+	sprintf(buf, "%s, %02d %s %04d %02d:%02d:%02d GMT", wdays[gTm->tm_wday], gTm->tm_mday, months[gTm->tm_mon], gTm->tm_year + 1900, gTm->tm_hour, gTm->tm_min, gTm->tm_sec);
+	String fileTimeStamp = String(buf);
+
+
+	// send the XML information about thyself to client
+	sendContent(F("<D:response><D:href>"));
+	// append full file path
+	sendContent(fullResPath);
+	sendContent(F("</D:href><D:propstat><D:status>HTTP/1.1 200 OK</D:status><D:prop><D:getlastmodified>"));
+	// append modified date
+	sendContent(fileTimeStamp);
+	sendContent(F("</D:getlastmodified><D:getetag>"));
+	// append unique tag generated from full path
+	uint8_t hash[20];
+	sha1(fullResPath + fileTimeStamp,&hash[0]);
+	char hexstr[21];
+	int i;
+	for (i=0; i<20; i++) {
+		sprintf(hexstr+i*2, "%02x", hash[i]);
+	}
+	hexstr[i+1]=0;
+	sendContent("\"" + String(hexstr) + "\"");
+	sendContent(F("</D:getetag>"));
+
+	if(curFile->isDir())
+		sendContent(F("<D:resourcetype><D:collection/></D:resourcetype>"));
+	else	{
+		sendContent(F("<D:resourcetype/><D:getcontentlength>"));
+		// append the file size
+		sendContent(String(curFile->fileSize()));
+		sendContent(F("</D:getcontentlength><D:getcontenttype>"));
+		// append correct file mime type
+		sendContent(getMimeType(fullResPath));
+		sendContent(F("</D:getcontenttype>"));
+	}
+	sendContent(F("</D:prop></D:propstat></D:response>"));
+}
+
+
+
+
+// ------------------------
+void ESPWebDAV::handleGet(ResourceType resource, bool isGet)	{
+// ------------------------
+	DBG_PRINTLN("Processing GET");
+
+	// does URI refer to an existing file resource
+	if(resource != RESOURCE_FILE)
+		return handleNotFound();
+
+	SdFile rFile;
+	long tStart = millis();
+	uint8_t buf[1460];
+	rFile.open(uri.c_str(), O_READ);
+
+	sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET");
+	size_t fileSize = rFile.fileSize();
+	setContentLength(fileSize);
+	String contentType = getMimeType(uri);
+	if(uri.endsWith(".gz") && contentType != "application/x-gzip" && contentType != "application/octet-stream")
+		sendHeader("Content-Encoding", "gzip");
+
+	send("200 OK", contentType.c_str(), "");
+
+	if(isGet)	{
+		// disable Nagle if buffer size > TCP MTU of 1460
+		// client.setNoDelay(1);
+
+		// send the file
+		while(rFile.available())	{
+			// SD read speed ~ 17sec for 4.5MB file
+			int numRead = rFile.read(buf, sizeof(buf));
+			client.write(buf, numRead);
+		}
+	}
+
+	rFile.close();
+	DBG_PRINT("File "); DBG_PRINT(fileSize); DBG_PRINT(" bytes sent in: "); DBG_PRINT((millis() - tStart)/1000); DBG_PRINTLN(" sec");
+}
+
+
+
+
+// ------------------------
+void ESPWebDAV::handlePut(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing Put");
+
+	// does URI refer to a directory
+	if(resource == RESOURCE_DIR)
+		return handleNotFound();
+#ifdef GOTEK_SDFAT2
+	FsFile nFile;
+#else
+	SdFile nFile;
+#endif
+	sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET");
+
+	// if file does not exist, create it
+	if(resource == RESOURCE_NONE)	{
+		if(!nFile.open(uri.c_str(), O_CREAT | O_WRITE | O_TRUNC))
+			return handleWriteError("Unable to create a new file", &nFile);
+	}
+
+	// file is created/open for writing at this point
+	DBG_PRINT(uri); DBG_PRINTLN(" - ready for data");
+	// did server send any data in put
+	size_t contentLen = contentLengthHeader.toInt();
+
+	if(contentLen != 0)	{
+		// buffer size is critical *don't change*
+		const size_t WRITE_BLOCK_CONST = 512;
+		uint8_t buf[WRITE_BLOCK_CONST];
+		long tStart = millis();
+		size_t numRemaining = contentLen;
+
+		// high speed raw write implementation
+		// close any previous file
+#ifndef GOTEK_SDFAT2
+		nFile.close();
+		// delete old file
+		sd.remove(uri.c_str());
+#endif
+	
+		// create a contiguous file
+		size_t contBlocks = (contentLen/WRITE_BLOCK_CONST + 1);
+		uint32_t bgnBlock, endBlock;
+#ifndef GOTEK_SDFAT2
+		if (!nFile.createContiguous(sd.vwd(), uri.c_str(), contBlocks * WRITE_BLOCK_CONST))
+			return handleWriteError("File create contiguous sections failed", &nFile);
+		// get the location of the file's blocks
+
+		if (!nFile.contiguousRange(&bgnBlock, &endBlock))
+			return handleWriteError("Unable to get contiguous range", &nFile);
+		
+		if (!sd.card()->writeStart(bgnBlock))
+			return handleWriteError("Unable to start writing contiguous range", &nFile);
+#else
+			nFile.preAllocate(contentLen);
+#endif
+		// read data from stream and write to the file
+		while(numRemaining > 0)	{
+			size_t numToRead = (numRemaining > WRITE_BLOCK_CONST) ? WRITE_BLOCK_CONST : numRemaining;
+			size_t numRead = readBytesWithTimeout(buf, sizeof(buf), numToRead);
+			if(numRead == 0)
+				break;
+
+			// store whole buffer into file regardless of numRead
+#ifdef GOTEK_SDFAT2
+			size_t buffer_w_size =0;
+			if (numRemaining<sizeof(buf)){
+				buffer_w_size=numRemaining;
+			}else{
+				buffer_w_size= sizeof(buf);
+			}
+			if (!nFile.write(buf, buffer_w_size))
+				return handleWriteError("Write data failed", &nFile);
+#else
+			if (!sd.card()->writeData(buf))
+				return handleWriteError("Write data failed", &nFile);
+
+#endif 
+			// reduce the number outstanding
+			numRemaining -= numRead;
+		}
+#ifndef GOTEK_SDFAT2
+		// stop writing operation
+		if (!sd.card()->writeStop())
+			return handleWriteError("Unable to stop writing contiguous range", &nFile);
+#endif
+		// detect timeout condition
+		if(numRemaining)
+			return handleWriteError("Timed out waiting for data", &nFile);
+
+		// truncate the file to right length
+#ifndef GOTEK_SDFAT2
+		if(!nFile.truncate(contentLen))
+			return handleWriteError("Unable to truncate the file", &nFile);
+#endif
+		DBG_PRINT("File "); DBG_PRINT(contentLen - numRemaining); DBG_PRINT(" bytes stored in: "); DBG_PRINT((millis() - tStart)/1000); DBG_PRINTLN(" sec");
+	}
+
+	if(resource == RESOURCE_NONE)
+		send("201 Created", NULL, "");
+	else
+		send("200 OK", NULL, "");
+
+	nFile.close();
+}
+
+
+
+
+// ------------------------
+#ifdef GOTEK_SDFAT2
+void ESPWebDAV::handleWriteError(String message, FsFile *wFile)	{
+#else
+void ESPWebDAV::handleWriteError(String message, FatFile *wFile)	{
+#endif
+// ------------------------
+	// close this file
+	wFile->close();
+	// delete the wrile being written
+	sd.remove(uri.c_str());
+	// send error
+	send("500 Internal Server Error", "text/plain", message);
+	DBG_PRINTLN(message);
+}
+
+
+// ------------------------
+void ESPWebDAV::handleDirectoryCreate(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing MKCOL");
+	
+	// does URI refer to anything
+	if(resource != RESOURCE_NONE)
+		return handleNotFound();
+	
+	// create directory
+	if (!sd.mkdir(uri.c_str(), true)) {
+		// send error
+		send("500 Internal Server Error", "text/plain", "Unable to create directory");
+		DBG_PRINTLN("Unable to create directory");
+		return;
+	}
+
+	DBG_PRINT(uri);	DBG_PRINTLN(" directory created");
+	sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT");
+	send("201 Created", NULL, "");
+}
+
+
+
+// ------------------------
+void ESPWebDAV::handleMove(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing MOVE");
+	
+	// does URI refer to anything
+	if(resource == RESOURCE_NONE)
+		return handleNotFound();
+
+	if(destinationHeader.length() == 0)
+		return handleNotFound();
+	
+	String dest = urlDecode(urlToUri(destinationHeader));
+		
+	DBG_PRINT("Move destination: "); DBG_PRINTLN(dest);
+
+	// move file or directory
+	if ( !sd.rename(uri.c_str(), dest.c_str())	) {
+		// send error
+		send("500 Internal Server Error", "text/plain", "Unable to move");
+		DBG_PRINTLN("Unable to move file/directory");
+		return;
+	}
+
+	DBG_PRINTLN("Move successful");
+	sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT");
+	send("201 Created", NULL, "");
+}
+
+
+
+
+// ------------------------
+void ESPWebDAV::handleDelete(ResourceType resource)	{
+// ------------------------
+	DBG_PRINTLN("Processing DELETE");
+	
+	// does URI refer to anything
+	if(resource == RESOURCE_NONE)
+		return handleNotFound();
+
+	bool retVal;
+	
+	if(resource == RESOURCE_FILE)
+		// delete a file
+		retVal = sd.remove(uri.c_str());
+	else
+		// delete a directory
+		retVal = sd.rmdir(uri.c_str());
+		
+	if(!retVal)	{
+		// send error
+		send("500 Internal Server Error", "text/plain", "Unable to delete");
+		DBG_PRINTLN("Unable to delete file/directory");
+		return;
+	}
+
+	DBG_PRINTLN("Delete successful");
+	sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT");
+	send("200 OK", NULL, "");
+}
+

+ 508 - 0
sw/SweFlops/src/WebOTA.cpp

@@ -0,0 +1,508 @@
+// Arduino build process info: https://github.com/arduino/Arduino/wiki/Build-Process
+
+#include "WebOTA.h"
+#include "Version.h"
+#include <Arduino.h>
+#include "config.h"
+#include <WiFiClient.h>
+
+#ifdef ESP32
+#include <WebServer.h>
+#include <ESPmDNS.h>
+#include <Update.h>
+#include <WiFi.h>
+
+#include "config.h"
+#include "osd.h"
+#include "webcontent.h"
+#include "sdFormat.h"
+#include <SdFat.h>
+#include <sdios.h>
+#include <Wire.h>
+#include <WireSlave.h>
+static const char TAG[] = __FILE__;
+WebServer OTAServer(9999);
+#endif
+WebOTA webota;
+extern Sd2Card card;
+extern uint32_t cardSizeBlocks;
+extern uint32_t cardCapacityMB;
+extern bool isSDInit;
+extern bool nrst_state;
+extern bool masterboot;
+#ifdef ESP8266
+#include <ESP8266WebServer.h>
+#include <ESP8266WiFi.h>
+#include <ESP8266mDNS.h>
+
+ESP8266WebServer OTAServer(9999);
+#endif
+extern struct osd_display_t osd_display;
+extern t_osd_info  osd_info;
+const char* PARAM_INPUT_1 = "state";
+int WebOTA::init(const unsigned int port, const char *path) {
+	this->port = port;
+	this->path = path;
+
+	// Only run this once
+	if (this->init_has_run) {
+		return 0;
+	}
+
+	add_http_routes(&OTAServer, path);
+	OTAServer.begin(port);
+
+	Serial.printf("WebOTA url   : http://%s.local:%d%s\r\n\r\n", this->mdns.c_str(), port, path);
+
+	// Store that init has already run
+	this->init_has_run = true;
+
+	return 1;
+}
+
+// One param
+int WebOTA::init(const unsigned int port) {
+	return WebOTA::init(port, "/webota");
+}
+
+// No params
+int WebOTA::init() {
+	return WebOTA::init(8080, "/webota");
+}
+
+int WebOTA::handle() {
+	// If we haven't run the init yet run it
+	if (!this->init_has_run) {
+		WebOTA::init();
+	}
+
+	OTAServer.handleClient();
+#ifdef ESP8266
+	MDNS.update();
+#endif
+
+	return 1;
+}
+
+long WebOTA::max_sketch_size() {
+	long ret = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+
+	return ret;
+}
+
+// R Macro string literal https://en.cppreference.com/w/cpp/language/string_literal
+const char gotek_version_html[] PROGMEM = "<title>SweProj Info Page</title><h1>" GOTEK_FULL_NAME " Info Page</h1> <br> Firmware version: " GOTEK_VERSION;
+
+const char ota_version_html[] PROGMEM = "<h1>" GOTEK_FULL_NAME " Version " GOTEK_VERSION " Firmware Update</h1>";
+
+const char ota_upload_form[] PROGMEM = R"!^!(
+
+<form method="POST" action="#" enctype="multipart/form-data" id="upload_form">
+    <input type="file" name="update" id="file">
+    <input type="submit" value="Update">
+</form>
+
+<div id="prg_wrap" style="border: 0px solid; width: 100%;">
+   <div id="prg" style="text-shadow: 2px 2px 3px black; padding: 5px 0; display: none; border: 1px solid #008aff; background: #002180; text-align: center; color: white;"></div>
+</div>
+
+<script>
+var domReady = function(callback) {
+	document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback);
+};
+
+domReady(function() {
+	var myform = document.getElementById('upload_form');
+	var filez  = document.getElementById('file');
+
+	myform.onsubmit = function(event) {
+		event.preventDefault();
+
+		var formData = new FormData();
+		var file     = filez.files[0];
+
+		if (!file) { return false; }
+
+		formData.append("files", file, file.name);
+
+		var xhr = new XMLHttpRequest();
+		xhr.upload.addEventListener("progress", function(evt) {
+			if (evt.lengthComputable) {
+				var per = Math.round((evt.loaded / evt.total) * 100);
+				var prg = document.getElementById('prg');
+
+				prg.innerHTML     = per + "%"
+				prg.style.width   = per + "%"
+				prg.style.display = "block"
+			}
+		}, false);
+		xhr.open('POST', location.href, true);
+
+		// Set up a handler for when the request finishes.
+		xhr.onload = function () {
+			if (xhr.status === 200) {
+				//alert('Success');
+			} else {
+				//alert('An error occurred!');
+			}
+		};
+
+		xhr.send(formData);
+   }
+});
+</script>)!^!";
+
+
+#ifdef ESP8266
+int WebOTA::add_http_routes(ESP8266WebServer *server, const char *path) {
+#endif
+#ifdef ESP32
+int WebOTA::add_http_routes(WebServer *server, const char *path) {
+#endif
+	// Index page
+	server->on("/", HTTP_GET, [server]() {
+		String html = stylecss;
+		String outputStateValue="";
+		if (nrst_state){
+			outputStateValue="checked";
+		}else{
+			outputStateValue="";
+		}
+		IPAddress ip = WiFi.localIP();
+		String localIP = String(ip[0]) + "." +  String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]);
+		html += FPSTR(gotek_version_html);
+		//html += "<br><a href=\"https://git.sweproj.com/ABC80/SweFlops\">https://git.sweproj.com/ABC80/SweFlops/</a>";
+		//html += "<br><br>";
+		html += "<div class=\"card\">";
+		html += "<h3>Gotek data</h3>";
+		html += "<h3><span id=\"gotek\">0</span></h3><br>";
+		html += "</div>";
+		html += "<button class=\"button\" onmousedown=\"toggleCheckbox('on','l');\" ontouchstart=\"toggleCheckbox('on','l');\"  \">LEFT</button>&nbsp";
+		html += "<button class=\"button\" onmousedown=\"toggleCheckbox('on','s');\" ontouchstart=\"toggleCheckbox('on','s');\"  \">SELECT</button>&nbsp";
+		html += "<button class=\"button\" onmousedown=\"toggleCheckbox('on','r');\" ontouchstart=\"toggleCheckbox('on','r');\"  \">RIGHT</button>&nbsp";
+		html += "<br><br>";
+		html += "<h4>Enable disc transfer <span id=\"outputState\"></span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox2(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";
+		html += "<br><br>";
+		html += "<b>Supported Services</b>";
+		html += "<ul>";
+		html += "<li>WebDAV at port 8080<br>http://" + localIP + ":8080</li><br>";
+		html += "<li>FTP at port 21 (FileZilla is supported)<br>ftp://" + localIP + "</li><br>";
+		html += "<li>Firmware update<br><a href=\"http://" + localIP + "/webota\">http://" + localIP + "/webota</a></li><br>";
+		html += "<li>Format flash<br><a href=\"http://" + localIP + "/format\">http://" + localIP + "/format</a></li><br>";
+		html += "</ul>";
+		html += "<br>";
+		html += "<br>";
+		html += "HINT: The SweProj Gotek V1.03 device supports up to 32 GB microSD/TF cards.";
+		html += "<br>";
+		html += "<br>";
+		html += "<script>";
+
+		html += "setInterval(function() {";
+  		// Call a function repetatively with .1 Second interval
+  		html += "getData();";
+		html += "}, 100);"; 
+
+		html += "function toggleCheckbox2(element) {";
+  		html += "var xhr = new XMLHttpRequest();";
+  		html += "if(element.checked){xhr.open(\"GET\", \"/onw\", true); }";
+  		html += "else { xhr.open(\"GET\", \"/offw\", true); }";
+  		html += "xhr.send();";
+		html += "}";
+
+		html += "function getData() {";
+  		html += "var xhttp = new XMLHttpRequest();";
+  		html += "xhttp.onreadystatechange = function() {";
+    	html += "if (this.readyState == 4 && this.status == 200) {";
+      	html += "document.getElementById(\"gotek\").innerHTML =";
+      	html += "this.responseText;";
+    	html += "}";
+  		html += "};";
+  		html += "xhttp.open(\"GET\", \"readgotek\", true);";
+  		html += "xhttp.send();";
+		html += "}";
+		html += "function toggleCheckbox(x,b) {";
+     	html += "var xhr = new XMLHttpRequest();";
+     	html += "xhr.open(\"GET\",  \"/\" + x+b, true);";
+     	html += "xhr.send();";
+  		html += "}";
+		html += "</script>";
+		server->send(200, "text/html", html.c_str());
+	});
+server->on("/format", HTTP_GET, [server]() {
+		String html = stylecss;
+		IPAddress ip = WiFi.localIP();
+		String localIP = String(ip[0]) + "." +  String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]);
+		html += FPSTR(gotek_version_html);
+		//html += "<br><a href=\"https://git.sweproj.com/ABC80/SweFlops\">https://git.sweproj.com/ABC80/SweFlops/</a>";
+		//html += "<br><br>";
+		html += "<div class=\"card\">";
+		html += "<h3>Gotek Format</h3>";
+		html += "</div>";
+		html += "<h3><span id=\"gotekformat\">0</span></h3><br>";
+		html += "</div>";
+		html += "<button class=\"button\" onmousedown=\"formatCheckbox('on','f');\" ontouchstart=\"formatCheckbox('on','f');\"  \">FORMAT</button>&nbsp";
+		html += "<br>";
+		html += "<br>";
+		html += "HINT: The SweProj Gotek V1.03 device supports up to 32 GB microSD/TF cards.";
+		html += "<br>";
+		html += "<br>";
+		html += "<script>";
+		html += "function formatCheckbox(x,b) {";
+     	html += "var xhr = new XMLHttpRequest();";
+     	html += "xhr.open(\"GET\",  \"/\" + x+b, true);";
+     	html += "xhr.send();";
+  		html += "}";
+		html += "function getData() {";
+  		html += "var xhttp = new XMLHttpRequest();";
+  		html += "xhttp.onreadystatechange = function() {";
+    	html += "if (this.readyState == 4 && this.status == 200) {";
+      	html += "document.getElementById(\"gotekformat\").innerHTML =";
+      	html += "this.responseText;";
+    	html += "}";
+  		html += "};";
+  		html += "xhttp.open(\"GET\", \"formatgotek\", true);";
+  		html += "xhttp.send();";
+		html += "}";
+		html += "function toggleCheckbox(x,b) {";
+     	html += "var xhr = new XMLHttpRequest();";
+     	html += "xhr.open(\"GET\",  \"/\" + x+b, true);";
+     	html += "xhr.send();";
+  		html += "}";
+		html += "</script>";
+		server->send(200, "text/html", html.c_str());
+	});
+	// Upload firmware page
+	server->on(path, HTTP_GET, [server,this]() {
+		String html = "";
+		if (this->custom_html != NULL) {
+			html += this->custom_html;
+		} else {
+			html += FPSTR(ota_version_html);
+		}
+
+		html += FPSTR(ota_upload_form);
+		server->send_P(200, "text/html", html.c_str());
+	});
+
+	// Handling uploading firmware file
+	server->on(path, HTTP_POST, [server,this]() {
+		server->send(200, "text/plain", (Update.hasError()) ? "Update: fail\n" : "Update: OK!\n");
+		delay(500);
+		ESP.restart();
+	}, [server,this]() {
+		HTTPUpload& upload = server->upload();
+
+		if (upload.status == UPLOAD_FILE_START) {
+			Serial.printf("Firmware update initiated: %s\r\n", upload.filename.c_str());
+
+			//uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+			uint32_t maxSketchSpace = this->max_sketch_size();
+
+			if (!Update.begin(maxSketchSpace)) { //start with max available size
+				Update.printError(Serial);
+			}
+		} else if (upload.status == UPLOAD_FILE_WRITE) {
+			/* flashing firmware to ESP*/
+			if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
+				Update.printError(Serial);
+			}
+
+			// Store the next milestone to output
+			uint16_t chunk_size  = 51200;
+			static uint32_t next = 51200;
+
+			// Check if we need to output a milestone (100k 200k 300k)
+			if (upload.totalSize >= next) {
+				Serial.printf("%dk ", next / 1024);
+				next += chunk_size;
+			}
+		} else if (upload.status == UPLOAD_FILE_END) {
+			if (Update.end(true)) { //true to set the size to the current progress
+				Serial.printf("\r\nFirmware update successful: %u bytes\r\nRebooting...\r\n", upload.totalSize);
+			} else {
+				Update.printError(Serial);
+			}
+		}
+
+	});
+	// Gotek page
+	server->on("/readgotek", HTTP_GET, [server]() {
+		char html[200] ;
+		char br[]="<br>";
+		for(uint i=0;i<osd_display.rows;i++){
+		memcpy(html+osd_display.cols*(i)+(4*i),&osd_display.text[i],osd_display.cols);
+		memcpy(html+osd_display.cols*(i+1)+(4*i),br,4);
+		}
+		memcpy(html+osd_display.cols*(osd_display.rows)+(4*(osd_display.rows)),"\0",1);
+
+		server->send_P(200, "text/html", html);
+	});
+	// Receive an HTTP GET request
+	server->on("/onl", HTTP_GET, [server] () {
+		osd_info.buttons = osd_info.buttons | 0x01;
+		osd_info.buttons2 =osd_info.buttons ;
+		server->send_P(200, "text/plain", "ok");
+	});
+	server->on("/ons", HTTP_GET, [server] () {
+		osd_info.buttons = osd_info.buttons | 0x04;
+		osd_info.buttons2 =osd_info.buttons ;
+		server->send_P(200, "text/plain", "ok");
+	});
+	server->on("/onr", HTTP_GET, [server] () {
+		osd_info.buttons = osd_info.buttons | 0x02;
+		osd_info.buttons2 =osd_info.buttons ;
+		server->send_P(200, "text/plain", "ok");
+	});
+	server->on("/onw", HTTP_GET, [server] () {
+
+		digitalWrite(GOTEK_SD_SEL    ,HIGH);	
+		digitalWrite(GOTEK_STM_RST    ,LOW);	
+
+		digitalWrite(1,HIGH);
+		nrst_state=true;
+		server->send_P(200, "text/plain", "ok");
+    });
+
+    server->on("/offw", HTTP_GET, [server] () {
+		
+		digitalWrite(GOTEK_SD_SEL    ,LOW);	
+		digitalWrite(GOTEK_STM_RST    ,HIGH);
+		masterboot=false;
+		isSDInit = false;
+		digitalWrite(1,LOW);
+		nrst_state=false;
+
+		server->send_P(200, "text/plain", "ok");
+	});
+
+	server->on("/onf", HTTP_GET, [server] () {
+
+		digitalWrite(GOTEK_SD_SEL    ,HIGH);	
+		digitalWrite(GOTEK_STM_RST    ,LOW);	
+
+		digitalWrite(1,HIGH);
+
+		nrst_state=true;
+#if GOTEK_SDFAT2
+		 m_card = cardFactory.newCard(SD_CONFIG);
+		ExFatFormatter exFatFormatter;
+		FatFormatter fatFormatter;
+#else
+if (!card.begin(GOTEK_SD_CS,SD_SCK_MHZ(50))) {
+        ESP_LOGE(TAG,"SD initialization failure!");
+        ESP_LOGE(TAG,"Is the SD card inserted correctly?");
+        ESP_LOGE(TAG,"Is chip select correct at the top of this program?");
+    sdError("card.begin failed");
+}else{
+	ESP_LOGE(TAG,"SD INIT!!!!!");
+}
+#endif
+		uint32_t cardSectorCount = 0;
+		uint8_t sectorBuffer[512];
+#ifdef GOTEK_SDFAT2
+		cardSectorCount = m_card->sectorCount();
+#else
+	cardSectorCount = card.cardSize();
+#endif
+ 	ESP_LOGI(TAG,"Formating.");
+  // Format exFAT if larger than 32GB.
+#if GOTEK_SDFAT2
+   Serial.println("Card size: "+String(cardSectorCount * 5.12e-7));
+   Serial.println(" GB (GB = 1E9 bytes)\n");
+#else
+  cardSizeBlocks = card.cardSize();
+  if (cardSizeBlocks == 0) {
+    sdError("cardSize");
+  }
+  cardCapacityMB = (cardSizeBlocks + 2047)/2048;
+
+   ESP_LOGI(TAG,"Card Size: %s %s",String( 1.048576*cardCapacityMB)," MB");
+
+#endif
+     ESP_LOGI(TAG,"Card will be formated ");
+  if (cardSectorCount > 67108864) {
+    ESP_LOGI(TAG,"exFAT");
+  } else if (cardSectorCount > 4194304) {
+    ESP_LOGI(TAG,"FAT32");
+  } else {
+   ESP_LOGI(TAG,"FAT16");
+  }
+  bool rtn=false;
+#ifdef GOTEK_SDFAT2
+  rtn = cardSectorCount > 67108864 
+                 ? exFatFormatter.format(m_card, sectorBuffer, &Serial)
+                 : fatFormatter.format(m_card, sectorBuffer, &Serial);
+#else
+	formatCard() ;
+	rtn=true;
+#endif
+	digitalWrite(GOTEK_SD_SEL    ,LOW);	
+	digitalWrite(GOTEK_STM_RST    ,HIGH);
+	masterboot=false;
+	isSDInit = false;
+	digitalWrite(1,LOW);
+	nrst_state=false;
+ 	if (!rtn) {
+
+	 	ESP_LOGE(TAG,"Failed");
+	}else{
+		ESP_LOGI(TAG,"Success.");
+		server->send_P(200, "text/plain", "ok");
+	}
+	});
+
+	server->begin();
+
+	return 1;
+}
+
+// If the MCU is in a delay() it cannot respond to HTTP OTA requests
+// We do a "fake" looping delay and listen for incoming HTTP requests while waiting
+void WebOTA::delay(unsigned int ms) {
+	// Borrowed from mshoe007 @ https://github.com/scottchiefbaker/ESP-WebOTA/issues/8
+	decltype(millis()) last = millis();
+
+	while ((millis() - last) < ms) {
+		OTAServer.handleClient();
+		::delay(5);
+	}
+}
+
+void WebOTA::set_custom_html(char const * const html) {
+	this->custom_html = html;
+}
+
+int WebOTA::init_mdns(const char *host) {
+	// Use mdns for host name resolution
+	if (!MDNS.begin(host)) {
+		Serial.println("Error setting up MDNS responder!");
+
+		return 0;
+	}
+
+	Serial.printf("mDNS started : %s.local\r\n", host);
+
+	webota.mdns = host;
+
+	return 1;
+}
+
+String WebOTA::ip2string(IPAddress ip) {
+	String ret = String(ip[0]) + "." +  String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]);
+
+	return ret;
+}
+
+int WebOTA::init_wifi(const char *mdns_hostname) {
+
+	Serial.println("");
+	Serial.printf("Connected to '%s'\r\n\r\n",WiFi.SSID().c_str());
+
+	String ipaddr = ip2string(WiFi.localIP());
+	Serial.printf("IP address   : %s\r\n", ipaddr.c_str());
+	Serial.printf("MAC address  : %s \r\n", WiFi.macAddress().c_str());
+
+	init_mdns(mdns_hostname);
+
+	return 1;
+}

+ 44 - 0
sw/SweFlops/src/WebOTA.h

@@ -0,0 +1,44 @@
+#include <Arduino.h>
+
+#ifdef ESP8266
+#include <ESP8266WebServer.h>
+#endif
+#ifdef ESP32
+#include <WebServer.h>
+#endif
+
+class WebOTA {
+	public:
+		unsigned int port;
+		String path = "";
+		String mdns = "";
+
+		int init(const unsigned int port, const char *path);
+		int init(const unsigned int port);
+		int init();
+		void delay(unsigned int ms);
+
+#ifdef ESP8266
+		int add_http_routes(ESP8266WebServer *server, const char *path);
+#endif
+#ifdef ESP32
+		int add_http_routes(WebServer *server, const char *path);
+#endif
+
+		int handle();
+
+		void set_custom_html(char const * const html);
+
+		int init_wifi(const char *mdns_hostname);
+
+
+	private:
+		bool init_has_run;
+		char const * custom_html = NULL;
+		String get_ota_html();
+		long max_sketch_size();
+		int init_mdns(const char *host);
+		String ip2string(IPAddress ip);
+};
+
+extern WebOTA webota;

+ 343 - 0
sw/SweFlops/src/WebSrv.cpp

@@ -0,0 +1,343 @@
+#include "ESPWebDAV.h"
+
+// Sections are copied from ESP8266Webserver
+
+// ------------------------
+String ESPWebDAV::getMimeType(String path) {
+// ------------------------
+	if(path.endsWith(".html")) return "text/html";
+	else if(path.endsWith(".htm")) return "text/html";
+	else if(path.endsWith(".css")) return "text/css";
+	else if(path.endsWith(".txt")) return "text/plain";
+	else if(path.endsWith(".js")) return "application/javascript";
+	else if(path.endsWith(".json")) return "application/json";
+	else if(path.endsWith(".png")) return "image/png";
+	else if(path.endsWith(".gif")) return "image/gif";
+	else if(path.endsWith(".jpg")) return "image/jpeg";
+	else if(path.endsWith(".ico")) return "image/x-icon";
+	else if(path.endsWith(".svg")) return "image/svg+xml";
+	else if(path.endsWith(".ttf")) return "application/x-font-ttf";
+	else if(path.endsWith(".otf")) return "application/x-font-opentype";
+	else if(path.endsWith(".woff")) return "application/font-woff";
+	else if(path.endsWith(".woff2")) return "application/font-woff2";
+	else if(path.endsWith(".eot")) return "application/vnd.ms-fontobject";
+	else if(path.endsWith(".sfnt")) return "application/font-sfnt";
+	else if(path.endsWith(".xml")) return "text/xml";
+	else if(path.endsWith(".pdf")) return "application/pdf";
+	else if(path.endsWith(".zip")) return "application/zip";
+	else if(path.endsWith(".gz")) return "application/x-gzip";
+	else if(path.endsWith(".appcache")) return "text/cache-manifest";
+
+	return "application/octet-stream";
+}
+
+
+
+
+// ------------------------
+String ESPWebDAV::urlDecode(const String& text)	{
+// ------------------------
+	String decoded = "";
+	char temp[] = "0x00";
+	unsigned int len = text.length();
+	unsigned int i = 0;
+	while (i < len)	{
+		char decodedChar;
+		char encodedChar = text.charAt(i++);
+		if ((encodedChar == '%') && (i + 1 < len))	{
+			temp[2] = text.charAt(i++);
+			temp[3] = text.charAt(i++);
+			decodedChar = strtol(temp, NULL, 16);
+		}
+		else {
+			if (encodedChar == '+')
+				decodedChar = ' ';
+			else
+				decodedChar = encodedChar;  // normal ascii char
+		}
+		decoded += decodedChar;
+	}
+	return decoded;
+}
+
+
+
+
+
+// ------------------------
+String ESPWebDAV::urlToUri(String url)	{
+// ------------------------
+	if(url.startsWith("http://"))	{
+		int uriStart = url.indexOf('/', 7);
+		return url.substring(uriStart);
+	}
+	else
+		return url;
+}
+
+
+
+// ------------------------
+bool ESPWebDAV::isClientWaiting() {
+// ------------------------
+	return server->hasClient();
+}
+
+
+
+
+// ------------------------
+void ESPWebDAV::handleClient(String blank) {
+// ------------------------
+	processClient(&ESPWebDAV::handleRequest, blank);
+}
+
+
+
+// ------------------------
+void ESPWebDAV::rejectClient(String rejectMessage) {
+// ------------------------
+	processClient(&ESPWebDAV::handleReject, rejectMessage);
+}
+
+
+
+// ------------------------
+void ESPWebDAV::processClient(THandlerFunction handler, String message) {
+// ------------------------
+	// Check if a client has connected
+	client = server->available();
+	if(!client)
+		return;
+
+	// Wait until the client sends some data
+	while(!client.available())
+		delay(1);
+	
+	// reset all variables
+	_chunked = false;
+	_responseHeaders = String();
+	_contentLength = CONTENT_LENGTH_NOT_SET;
+	method = String();
+	uri = String();
+	contentLengthHeader = String();
+	depthHeader = String();
+	hostHeader = String();
+	destinationHeader = String();
+
+	// extract uri, headers etc
+	if(parseRequest())
+		// invoke the handler
+		(this->*handler)(message);
+		
+	// finalize the response
+	if(_chunked)
+		sendContent("");
+
+	// send all data before closing connection
+	client.flush();
+	// close the connection
+	client.stop();
+}
+
+
+
+
+
+// ------------------------
+bool ESPWebDAV::parseRequest() {
+// ------------------------
+	// Read the first line of HTTP request
+	String req = client.readStringUntil('\r');
+	client.readStringUntil('\n');
+	
+	// First line of HTTP request looks like "GET /path HTTP/1.1"
+	// Retrieve the "/path" part by finding the spaces
+	int addr_start = req.indexOf(' ');
+	int addr_end = req.indexOf(' ', addr_start + 1);
+	if (addr_start == -1 || addr_end == -1) {
+		return false;
+	}
+
+	method = req.substring(0, addr_start);
+	uri = urlDecode(req.substring(addr_start + 1, addr_end));
+	// DBG_PRINT("method: "); DBG_PRINT(method); DBG_PRINT(" url: "); DBG_PRINTLN(uri);
+	
+	// parse and finish all headers
+	String headerName;
+	String headerValue;
+	
+	while(1) {
+		req = client.readStringUntil('\r');
+		client.readStringUntil('\n');
+		if(req == "") 
+			// no more headers
+			break;
+			
+		int headerDiv = req.indexOf(':');
+		if (headerDiv == -1)
+			break;
+		
+		headerName = req.substring(0, headerDiv);
+		headerValue = req.substring(headerDiv + 2);
+		// DBG_PRINT("\t"); DBG_PRINT(headerName); DBG_PRINT(": "); DBG_PRINTLN(headerValue);
+		
+		if(headerName.equalsIgnoreCase("Host"))
+			hostHeader = headerValue;
+		else if(headerName.equalsIgnoreCase("Depth"))
+			depthHeader = headerValue;
+		else if(headerName.equalsIgnoreCase("Content-Length"))
+			contentLengthHeader = headerValue;
+		else if(headerName.equalsIgnoreCase("Destination"))
+			destinationHeader = headerValue;
+	}
+	
+	return true;
+}
+
+
+
+
+// ------------------------
+void ESPWebDAV::sendHeader(const String& name, const String& value, bool first) {
+// ------------------------
+	String headerLine = name + ": " + value + "\r\n";
+
+	if (first)
+		_responseHeaders = headerLine + _responseHeaders;
+	else
+		_responseHeaders += headerLine;
+}
+
+
+
+// ------------------------
+void ESPWebDAV::send(String code, const char* content_type, const String& content) {
+// ------------------------
+	String header;
+	_prepareHeader(header, code, content_type, content.length());
+
+	client.write(header.c_str(), header.length());
+	if(content.length())
+		sendContent(content);
+}
+
+
+
+// ------------------------
+void ESPWebDAV::_prepareHeader(String& response, String code, const char* content_type, size_t contentLength) {
+// ------------------------
+	response = "HTTP/1.1 " + code + "\r\n";
+
+	if(content_type)
+		sendHeader("Content-Type", content_type, true);
+	
+	if(_contentLength == CONTENT_LENGTH_NOT_SET)
+		sendHeader("Content-Length", String(contentLength));
+	else if(_contentLength != CONTENT_LENGTH_UNKNOWN)
+		sendHeader("Content-Length", String(_contentLength));
+	else if(_contentLength == CONTENT_LENGTH_UNKNOWN) {
+		_chunked = true;
+		sendHeader("Accept-Ranges","none");
+		sendHeader("Transfer-Encoding","chunked");
+	}
+	sendHeader("Connection", "close");
+
+	response += _responseHeaders;
+	response += "\r\n";
+}
+
+
+
+// ------------------------
+void ESPWebDAV::sendContent(const String& content) {
+// ------------------------
+	const char * footer = "\r\n";
+	size_t size = content.length();
+	
+	if(_chunked) {
+		char * chunkSize = (char *) malloc(11);
+		if(chunkSize) {
+			sprintf(chunkSize, "%x%s", size, footer);
+			client.write(chunkSize, strlen(chunkSize));
+			free(chunkSize);
+		}
+	}
+	
+	client.write(content.c_str(), size);
+	
+	if(_chunked) {
+		client.write(footer, 2);
+		if (size == 0) {
+			_chunked = false;
+		}
+	}
+}
+
+
+
+// ------------------------
+void ESPWebDAV::sendContent_P(PGM_P content) {
+// ------------------------
+	const char * footer = "\r\n";
+	size_t size = strlen_P(content);
+	
+	if(_chunked) {
+		char * chunkSize = (char *) malloc(11);
+		if(chunkSize) {
+			sprintf(chunkSize, "%x%s", size, footer);
+			client.write(chunkSize, strlen(chunkSize));
+			free(chunkSize);
+		}
+	}
+	
+	client.write_P(content, size);
+	
+	if(_chunked) {
+		client.write(footer, 2);
+		if (size == 0) {
+			_chunked = false;
+		}
+	}
+}
+
+
+
+// ------------------------
+void ESPWebDAV::setContentLength(size_t len)	{
+// ------------------------
+	_contentLength = len;
+}
+
+
+// ------------------------
+size_t ESPWebDAV::readBytesWithTimeout(uint8_t *buf, size_t bufSize) {
+// ------------------------
+	int timeout_ms = HTTP_MAX_POST_WAIT;
+	size_t numAvailable = 0;
+	while(!(numAvailable = client.available()) && client.connected() && timeout_ms--) 
+		delay(1);
+
+	if(!numAvailable)
+		return 0;
+
+	return client.read(buf, bufSize);
+}
+
+
+// ------------------------
+size_t ESPWebDAV::readBytesWithTimeout(uint8_t *buf, size_t bufSize, size_t numToRead) {
+// ------------------------
+	int timeout_ms = HTTP_MAX_POST_WAIT;
+	size_t numAvailable = 0;
+	
+	while(((numAvailable = client.available()) < numToRead) && client.connected() && timeout_ms--) 
+		delay(1);
+
+	if(!numAvailable)
+		return 0;
+
+	return client.read(buf, bufSize);
+}
+
+

+ 301 - 0
sw/SweFlops/src/main.cpp

@@ -0,0 +1,301 @@
+/*
+ * Firmware for SweProj Gotek
+ *
+ * 2023 - Per Mårtensson (pm@sweproj.com)
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <Arduino.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "config.h"
+#include <WiFi.h>
+#include <WiFiManager.h>
+#include "Version.h"
+#include "ESPWebDAV.h"
+//#include "ESPFtpServer.h"
+#include "WebOTA.h"
+#include <SPI.h>
+#include <NTPClient.h>
+#include <ESP32Time.h>
+#include <Wire.h>
+#include <WireSlave.h>
+#include <osd.h>
+#include <ntp.h>
+#include <task.h>
+#include <timers.h>
+#include <ESPFtpServer.h>
+
+#define HOSTNAME "SweFlops"
+
+static const char TAG[] = __FILE__;
+
+// WebDAV server
+#define IDLE_TASK_SIZE 1000
+ESPWebDAV dav;
+String statusMessage;
+bool initFailed = false;
+bool nrst_state=false;
+WiFiUDP ntpUDP;
+bool masterboot=true;
+NTPClient timeClient(ntpUDP,"pool.ntp.org", 60, 60000);
+// FTP server
+FtpServer ftpSrv;
+ESP32Time rtc(0);
+// BOOT button
+void handleBootButton(void);
+SPISettings spiSettings;
+// Wifi
+WiFiManager wifiManager;
+t_osd_info  osd_info;
+//Timer
+TimerHandle_t xNTPTimer = NULL;
+BaseType_t xNTPTimerStarted;
+#ifdef GOTEK_SDFAT2
+SdCardFactory cardFactory;
+#endif
+const char fw_ver[] = FW_VER;
+TaskHandle_t OSD_Task = NULL;
+gotek::osd gotek_osd;
+bool isSDInit = false;
+struct osd_display_t osd_display;
+struct Button {
+    const uint8_t PIN;
+    bool pressed;
+	unsigned long button_time;
+	unsigned long last_button_time;
+};
+Button button1 = {GOTEK_BUTTON_RIGHT, false,NULL,NULL};
+Button button2 = {GOTEK_BUTTON_LEFT,  false,NULL,NULL};
+void IRAM_ATTR button1_isr() {
+    button1.button_time = millis();
+	if (button1.button_time - button1.last_button_time > 250)
+	{
+		button1.pressed = true;
+		button1.last_button_time = button1.button_time;
+	}
+}
+void IRAM_ATTR button2_isr() {
+    button2.button_time = millis();
+	if (button2.button_time - button2.last_button_time > 250)
+	{
+		button2.pressed = true;
+		button2.last_button_time = button2.button_time;
+	}
+}
+void handleLED(void);
+void IRAM_ATTR NTP_Timer_Handler(TimerHandle_t pxTimer){
+	if (timeClient.update()){
+		rtc.setTime(timeClient.getEpochTime());
+		ESP_LOGI(TAG, "");
+		ESP_LOGI(TAG, "--------------------------------");
+		ESP_LOGI(TAG, "NTP update %s",rtc.getDateTime().c_str());
+		ESP_LOGI(TAG, "--------------------------------");
+
+	}
+
+	
+}
+
+void setup()
+{
+	char *p;
+
+	osd_info.protocol_ver = 0;
+	osd_info.fw_major = 0;//strtol(fw_ver, &p, 10);
+	osd_info.fw_minor = 0;//strtol(p + 1, NULL, 10);
+	osd_info.buttons = 0;
+	osd_info.buttons2 = 0;
+	esp_log_level_set("*", ESP_LOG_INFO);
+
+	pinMode(GOTEK_LED_GREEN, OUTPUT);
+	pinMode(GOTEK_LED_BLUE, OUTPUT);
+	pinMode(GOTEK_LED_ORANGE, OUTPUT);
+	digitalWrite(GOTEK_LED_GREEN, LOW);
+	digitalWrite(GOTEK_LED_BLUE, LOW);
+	digitalWrite(GOTEK_LED_ORANGE, LOW);
+
+	pinMode(GOTEK_STM_POWER,OUTPUT);
+	pinMode(GOTEK_SD_SEL,OUTPUT);
+	pinMode(GOTEK_STM_RST,OUTPUT_OPEN_DRAIN);
+	digitalWrite(GOTEK_SD_SEL    ,LOW);	
+	digitalWrite(GOTEK_STM_POWER  ,HIGH);
+	digitalWrite(GOTEK_STM_RST    ,HIGH);
+
+	
+	GOTEK_LOGGING_SERIAL_PORT.begin(GOTEK_SERIAL_BAUD_RATE);
+	GOTEK_LOGGING_SERIAL_PORT.setDebugOutput(GOTEK_DEBUG);
+
+
+	ESP_LOGI(TAG, "");
+	ESP_LOGI(TAG, "");
+	ESP_LOGI(TAG, "**********************************************");
+	ESP_LOGI(TAG, GOTEK_FULL_NAME);
+	ESP_LOGI(TAG, "Version %s", GOTEK_VERSION);
+	ESP_LOGI(TAG, "**********************************************");
+
+	// Start WiFi
+	ESP_LOGI(TAG, "");
+	ESP_LOGI(TAG, "--------------------------------");
+	ESP_LOGI(TAG, "Connect to WiFi");
+	ESP_LOGI(TAG, "--------------------------------");
+
+	WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
+
+	bool res = wifiManager.autoConnect(HOSTNAME);
+
+	if (!res)
+	{
+		ESP_LOGI(TAG, "Running config portal");
+		// ESP.restart();
+	}
+	else
+	{
+		// if you get here you have connected to the WiFi
+		ESP_LOGI(TAG, "Connected");
+	}
+
+	// Init OTA firmware updater
+	ESP_LOGI(TAG, "");
+	ESP_LOGI(TAG, "--------------------------------");
+	ESP_LOGI(TAG, "Start firmware update server");
+	ESP_LOGI(TAG, "--------------------------------");
+	webota.init_wifi(HOSTNAME);
+	webota.init(WEB_SERVER_PORT, "/webota");
+	
+	// Start the WebDAV server
+	ESP_LOGI(TAG, "");
+	ESP_LOGI(TAG, "--------------------------------");
+	ESP_LOGI(TAG, "Start WebDAV server");
+	ESP_LOGI(TAG, "--------------------------------");
+	if (!dav.init(WEBDAV_SERVER_PORT))
+	{
+		statusMessage = "An error occured while initialization of WebDAV server";
+		ESP_LOGE(TAG, "ERROR: %s", statusMessage);
+		initFailed = true;
+	}
+	ESP_LOGI(TAG, "WebDAV server started");
+
+	// Start FTP server
+	ESP_LOGI(TAG, "");
+	ESP_LOGI(TAG, "--------------------------------");
+	ESP_LOGI(TAG, "Start FTP server");
+	ESP_LOGI(TAG, "--------------------------------");
+	ftpSrv.begin("gotek", "gotek", GOTEK_SD_CS, spiSettings); 
+	ESP_LOGI(TAG, "FTP server started");
+
+	timeClient.setUpdateInterval(NTP_Update_Interval);
+
+	xNTPTimer = xTimerCreate(
+		"NtpTimer",
+		pdMS_TO_TICKS(60 * 1000),
+		pdTRUE,
+		(void *)0,
+		&NTP_Timer_Handler );
+
+	if (xNTPTimer!= NULL){	
+		xNTPTimerStarted = xTimerStart( xNTPTimer , portMAX_DELAY );
+		if (xNTPTimerStarted==pdPASS){
+			//vTaskStartScheduler();	
+		}
+	}
+	
+	// Setup FLASH button and LED
+	pinMode(GOTEK_BUTTON_IO_0, INPUT_PULLUP);
+	pinMode(GOTEK_BUTTON_RIGHT, INPUT);
+	pinMode(GOTEK_BUTTON_LEFT, INPUT);
+	attachInterrupt(button1.PIN, button1_isr, FALLING);
+	attachInterrupt(button2.PIN, button2_isr, FALLING);
+
+	digitalWrite(GOTEK_LED_GREEN, HIGH);
+
+}
+
+void loop()
+{
+
+	if (dav.isClientWaiting())
+	{
+		ESP_LOGI(TAG, "Client connected");
+		if (initFailed)
+			return dav.rejectClient(statusMessage);
+
+		// call handle if server was initialized properly
+
+		dav.initSD(GOTEK_SD_CS, spiSettings);
+		dav.handleClient();
+	}
+	
+	// FTP
+	ftpSrv.handleFTP(); // make sure in loop you call handleFTP()!!
+
+	// Web OTA update
+	webota.handle();
+
+	// Handle WiFi Manager
+	wifiManager.process();
+	//handleLED();
+	handleBootButton();
+	
+	if (button1.pressed) {
+        ESP_LOGD(TAG,"%s","Button1 has been pressed times\n");
+		//osd_info.buttons |= K_RIGHT; 
+        button1.pressed = false;
+    }
+	if (button2.pressed) {
+        ESP_LOGD(TAG,"%s","Button2 has been pressed times\n");
+		//osd_info.buttons |= K_LEFT; 
+        button2.pressed = false;
+    }
+
+}
+
+// The flash button is used to reset the WiFi settings
+void handleBootButton(void)
+{
+	if (digitalRead(GOTEK_BUTTON_IO_0) == 0)
+	{
+		delay(50); // Debounce
+
+		if (digitalRead(GOTEK_BUTTON_IO_0) == 0)
+		{
+			ESP_LOGI(TAG, "BOOT button pressed");
+
+			delay(5000); // Wait 5 seconds
+
+			if (digitalRead(GOTEK_BUTTON_IO_0) == 0)
+			{
+				ESP_LOGI(TAG, "BOOT button pressed for 5 seconds ");
+
+				// Let the LED 5 seconds blinking
+				for (int i = 0; i < 25; i++)
+				{
+					digitalWrite(GOTEK_LED_GREEN, LOW);
+					delay(200);
+					digitalWrite(GOTEK_LED_GREEN, HIGH);
+					delay(200);
+				}
+
+				ESP_LOGI(TAG, "Reset WiFi settings");
+				wifiManager.resetSettings();
+
+				ESP_LOGI(TAG, "Restarting ...");
+				ESP.restart();
+			}
+		}
+	}
+}
+
+

+ 11 - 0
sw/SweFlops/src/ntp.cpp

@@ -0,0 +1,11 @@
+#include <Arduino.h>
+#include "config.h"
+static const char TAG[] = __FILE__;
+
+namespace gotek {
+    void NTP_Timer_Handler(TimerHandle_t xTimer){
+
+        ESP_LOGI(TAG,"NTP update");
+
+    }
+}

+ 116 - 0
sw/SweFlops/src/osd.cpp

@@ -0,0 +1,116 @@
+#include <Arduino.h>
+#include <osd.h>
+#include <Wire.h>
+#include <WireSlave.h>
+#include "config.h"
+static const char TAG[] = __FILE__;
+
+extern TaskHandle_t OSD_Task;
+extern t_osd_info osd_info;
+extern struct osd_display_t osd_display;
+extern bool masterboot;
+namespace gotek {
+    byte RxByte;
+    byte RxByte2;
+    uint8_t osd_buttons_rx;
+
+    void OSD_Start(void * parameter){
+
+        while(1){
+        vTaskDelay(1000);
+        Serial.println("!");
+        }
+    }
+
+    void OSD_RxHandler(int numBytes)
+    {
+        
+        bool command_end=false;
+        uint x=0;
+        uint y=0;
+        while (Wire.available())
+        { // Read Any Received Data
+            RxByte = Wire.read();
+            if (!command_end){
+                if ((RxByte & 0xc0) == OSD_COLUMNS) {
+                    /* 0-40 */
+                    osd_display.cols = (RxByte & 0x3f);
+                } else {
+                    switch (RxByte & 0xf0) {
+                    case OSD_BUTTONS:
+                        osd_buttons_rx=(RxByte & 0x0f);
+                        break;
+                    case OSD_ROWS:
+                        /* 0-3 */
+                        osd_display.rows=(RxByte & 0x03);
+                        break;
+                    case OSD_HEIGHTS:
+                        osd_display.heights= (RxByte & 0x0d);
+                        break;
+                    case OSD_BACKLIGHT:
+                        switch (RxByte & 0x0f) {
+                        case 0:
+                            osd_display.on=false;
+                            break;
+                        case 1:
+                            osd_display.on=true;
+                            break;
+                        case 2:
+                            command_end=true;
+                            break;
+                        }
+                    }
+                }
+            }else{
+                if (x<osd_display.cols){
+                    osd_display.text[y][x++]  = RxByte;
+                }else{
+                    ESP_LOGV(TAG,"%s",(char *)osd_display.text[y]);//osd_display.text[y]);
+                    x=0;
+                    y++;
+                    osd_display.text[y][x++]  = RxByte;
+                }
+            }
+            
+        }
+        ESP_LOGV(TAG,"%s",((char *)osd_display.text[y]));
+
+    }
+    void OSD_RxRequest()
+    {
+        uint8_t *info = (uint8_t *)&osd_info;
+        if (masterboot)
+            Wire.write(info , 4);
+        else
+            Wire.write(info ,5 );
+        ESP_LOGV(TAG,"Button %i",info[3]); //"PER"
+        osd_info.buttons=0x00;
+        osd_info.buttons2=0x00;
+    }
+    void OSD_RxHandler2(int numBytes)
+    {
+        while (Wire1.available())
+        { // Read Any Received Data
+            RxByte2 = Wire1.read();
+        }
+    }
+    void OSD_RxRequest2()
+    {
+        uint8_t *info = (uint8_t *)&osd_info;
+        Wire1.write(info , 4);
+        Wire1.write(0x00);
+        Serial.println("YYYYYYYYYYYYYYYYYYYYYYY");
+    }
+    void osd::start(){
+        xTaskCreate(
+        OSD_Start,
+        "OSD_Task_1",
+        2000,
+        NULL,
+        1,
+        &OSD_Task);
+    };
+    
+    osd gotek_osd;
+}
+    

+ 353 - 0
sw/SweFlops/src/sdFormat.cpp

@@ -0,0 +1,353 @@
+#include "Version.h"
+#include <Arduino.h>
+#include "config.h"
+#include <SdFat.h>
+#include <sdios.h>
+static const char TAG[] = __FILE__;
+#ifdef GOTEK_SDFAT2
+extern SdCardFactory cardFactory;
+SdCard* m_card = nullptr;
+#else
+extern uint32_t cardSizeBlocks;
+extern uint32_t cardCapacityMB;
+extern Sd2Card card;
+#define sdError(msg) {sdErrorHalt();}
+#endif
+void sdErrorHalt() {
+  if (card.errorCode()) {
+    ESP_LOGI(TAG,"SD error: %s", String(card.errorCode()));
+    ESP_LOGI(TAG,"SD error: %s",  String(card.errorData()));
+  }
+  SysCall::halt();
+}
+
+
+
+#ifndef GOTEK_SDFAT2
+// Fake disk geometry
+uint8_t numberOfHeads;
+uint8_t sectorsPerTrack;
+
+uint16_t reservedSectors;
+uint8_t sectorsPerCluster;
+uint32_t fatStart;
+uint32_t fatSize;
+uint32_t dataStart;
+// constants for file system structure
+uint16_t const BU16 = 128;
+uint16_t const BU32 = 8192;
+// cache for SD block
+cache_t cache;
+// MBR information
+uint8_t partType;
+uint32_t relSector;
+uint32_t partSize;
+//  strings needed in file system structures
+char noName[] = "NO NAME    ";
+char fat16str[] = "FAT16   ";
+char fat32str[] = "FAT32   ";
+// initialize appropriate sizes for SD capacity
+void initSizes() {
+  if (cardCapacityMB <= 6) {
+     ESP_LOGE(TAG,"Card is too small.");
+  } else if (cardCapacityMB <= 16) {
+    sectorsPerCluster = 2;
+  } else if (cardCapacityMB <= 32) {
+    sectorsPerCluster = 4;
+  } else if (cardCapacityMB <= 64) {
+    sectorsPerCluster = 8;
+  } else if (cardCapacityMB <= 128) {
+    sectorsPerCluster = 16;
+  } else if (cardCapacityMB <= 1024) {
+    sectorsPerCluster = 32;
+  } else if (cardCapacityMB <= 32768) {
+    sectorsPerCluster = 64;
+  } else {
+    // SDXC cards
+    sectorsPerCluster = 128;
+  }
+
+   ESP_LOGI(TAG,"Blocks/Cluster: %s",String( int(sectorsPerCluster) ));;
+  // set fake disk geometry
+  sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63;
+
+  if (cardCapacityMB <= 16) {
+    numberOfHeads = 2;
+  } else if (cardCapacityMB <= 32) {
+    numberOfHeads = 4;
+  } else if (cardCapacityMB <= 128) {
+    numberOfHeads = 8;
+  } else if (cardCapacityMB <= 504) {
+    numberOfHeads = 16;
+  } else if (cardCapacityMB <= 1008) {
+    numberOfHeads = 32;
+  } else if (cardCapacityMB <= 2016) {
+    numberOfHeads = 64;
+  } else if (cardCapacityMB <= 4032) {
+    numberOfHeads = 128;
+  } else {
+    numberOfHeads = 255;
+  }
+}
+//------------------------------------------------------------------------------
+// zero cache and optionally set the sector signature
+void clearCache(uint8_t addSig) {
+  memset(&cache, 0, sizeof(cache));
+  if (addSig) {
+    cache.mbr.mbrSig0 = BOOTSIG0;
+    cache.mbr.mbrSig1 = BOOTSIG1;
+  }
+}
+//------------------------------------------------------------------------------
+// return cylinder number for a logical block number
+uint16_t lbnToCylinder(uint32_t lbn) {
+  return lbn / (numberOfHeads * sectorsPerTrack);
+}
+//------------------------------------------------------------------------------
+// return head number for a logical block number
+uint8_t lbnToHead(uint32_t lbn) {
+  return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack;
+}
+//------------------------------------------------------------------------------
+// return sector number for a logical block number
+uint8_t lbnToSector(uint32_t lbn) {
+  return (lbn % sectorsPerTrack) + 1;
+}
+//------------------------------------------------------------------------------
+// write cached block to the card
+uint8_t writeCache(uint32_t lbn) {
+  return card.writeBlock(lbn, cache.data);
+}
+//------------------------------------------------------------------------------
+// generate serial number from card size and micros since boot
+uint32_t volSerialNumber() {
+  return (cardSizeBlocks << 8) + micros();
+}
+//------------------------------------------------------------------------------
+// zero FAT and root dir area on SD
+void clearFatDir(uint32_t bgn, uint32_t count) {
+  clearCache(false);
+
+  if (!card.writeStart(bgn, count)) {
+     ESP_LOGE(TAG,"Clear FAT/DIR writeStart failed");
+  }
+  for (uint32_t i = 0; i < count; i++) {
+    if ((i & 0XFF) == 0) {
+      Serial.print(".");
+    }
+    if (!card.writeData(cache.data)) {
+       ESP_LOGE(TAG,"Clear FAT/DIR writeData failed");
+    }
+  }
+  if (!card.writeStop()) {
+     ESP_LOGE(TAG,"Clear FAT/DIR writeStop failed");
+  }
+
+  Serial.println("");
+}
+//------------------------------------------------------------------------------
+// format and write the Master Boot Record
+void writeMbr() {
+  clearCache(true);
+  part_t* p = cache.mbr.part;
+  p->boot = 0;
+  uint16_t c = lbnToCylinder(relSector);
+  if (c > 1023) {
+     ESP_LOGE(TAG,"MBR CHS");
+  }
+  p->beginCylinderHigh = c >> 8;
+  p->beginCylinderLow = c & 0XFF;
+  p->beginHead = lbnToHead(relSector);
+  p->beginSector = lbnToSector(relSector);
+  p->type = partType;
+  uint32_t endLbn = relSector + partSize - 1;
+  c = lbnToCylinder(endLbn);
+  if (c <= 1023) {
+    p->endCylinderHigh = c >> 8;
+    p->endCylinderLow = c & 0XFF;
+    p->endHead = lbnToHead(endLbn);
+    p->endSector = lbnToSector(endLbn);
+  } else {
+    // Too big flag, c = 1023, h = 254, s = 63
+    p->endCylinderHigh = 3;
+    p->endCylinderLow = 255;
+    p->endHead = 254;
+    p->endSector = 63;
+  }
+  p->firstSector = relSector;
+  p->totalSectors = partSize;
+  if (!writeCache(0)) {
+     ESP_LOGE(TAG,"write MBR");
+  }
+}
+//------------------------------------------------------------------------------
+// format the SD as FAT16
+void makeFat16() {
+  uint32_t nc;
+  for (dataStart = 2 * BU16;; dataStart += BU16) {
+    nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
+    fatSize = (nc + 2 + 255)/256;
+    uint32_t r = BU16 + 1 + 2 * fatSize + 32;
+    if (dataStart < r) {
+      continue;
+    }
+    relSector = dataStart - r + BU16;
+    break;
+  }
+  // check valid cluster count for FAT16 volume
+  if (nc < 4085 || nc >= 65525) {
+     ESP_LOGE(TAG,"Bad cluster count");
+  }
+  reservedSectors = 1;
+  fatStart = relSector + reservedSectors;
+  partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32;
+  if (partSize < 32680) {
+    partType = 0X01;
+  } else if (partSize < 65536) {
+    partType = 0X04;
+  } else {
+    partType = 0X06;
+  }
+  // write MBR
+  writeMbr();
+  clearCache(true);
+  fat_boot_t* pb = &cache.fbs;
+  pb->jump[0] = 0XEB;
+  pb->jump[1] = 0X00;
+  pb->jump[2] = 0X90;
+  for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
+    pb->oemId[i] = ' ';
+  }
+  pb->bytesPerSector = 512;
+  pb->sectorsPerCluster = sectorsPerCluster;
+  pb->reservedSectorCount = reservedSectors;
+  pb->fatCount = 2;
+  pb->rootDirEntryCount = 512;
+  pb->mediaType = 0XF8;
+  pb->sectorsPerFat16 = fatSize;
+  pb->sectorsPerTrack = sectorsPerTrack;
+  pb->headCount = numberOfHeads;
+  pb->hidddenSectors = relSector;
+  pb->totalSectors32 = partSize;
+  pb->driveNumber = 0X80;
+  pb->bootSignature = EXTENDED_BOOT_SIG;
+  pb->volumeSerialNumber = volSerialNumber();
+  memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
+  memcpy(pb->fileSystemType, fat16str, sizeof(pb->fileSystemType));
+  // write partition boot sector
+  if (!writeCache(relSector)) {
+     ESP_LOGE(TAG,"FAT16 write PBS failed");
+  }
+  // clear FAT and root directory
+  clearFatDir(fatStart, dataStart - fatStart);
+  clearCache(false);
+  cache.fat16[0] = 0XFFF8;
+  cache.fat16[1] = 0XFFFF;
+  // write first block of FAT and backup for reserved clusters
+  if (!writeCache(fatStart)
+      || !writeCache(fatStart + fatSize)) {
+     ESP_LOGE(TAG,"FAT16 reserve failed");
+  }
+}
+//------------------------------------------------------------------------------
+// format the SD as FAT32
+void makeFat32() {
+  uint32_t nc;
+  relSector = BU32;
+  for (dataStart = 2 * BU32;; dataStart += BU32) {
+    nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
+    fatSize = (nc + 2 + 127)/128;
+    uint32_t r = relSector + 9 + 2 * fatSize;
+    if (dataStart >= r) {
+      break;
+    }
+  }
+  // error if too few clusters in FAT32 volume
+  if (nc < 65525) {
+     ESP_LOGE(TAG,"Bad cluster count");
+  }
+  reservedSectors = dataStart - relSector - 2 * fatSize;
+  fatStart = relSector + reservedSectors;
+  partSize = nc * sectorsPerCluster + dataStart - relSector;
+  // type depends on address of end sector
+  // max CHS has lbn = 16450560 = 1024*255*63
+  if ((relSector + partSize) <= 16450560) {
+    // FAT32
+    partType = 0X0B;
+  } else {
+    // FAT32 with INT 13
+    partType = 0X0C;
+  }
+  writeMbr();
+  clearCache(true);
+
+  fat32_boot_t* pb = &cache.fbs32;
+  pb->jump[0] = 0XEB;
+  pb->jump[1] = 0X00;
+  pb->jump[2] = 0X90;
+  for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
+    pb->oemId[i] = ' ';
+  }
+  pb->bytesPerSector = 512;
+  pb->sectorsPerCluster = sectorsPerCluster;
+  pb->reservedSectorCount = reservedSectors;
+  pb->fatCount = 2;
+  pb->mediaType = 0XF8;
+  pb->sectorsPerTrack = sectorsPerTrack;
+  pb->headCount = numberOfHeads;
+  pb->hidddenSectors = relSector;
+  pb->totalSectors32 = partSize;
+  pb->sectorsPerFat32 = fatSize;
+  pb->fat32RootCluster = 2;
+  pb->fat32FSInfo = 1;
+  pb->fat32BackBootBlock = 6;
+  pb->driveNumber = 0X80;
+  pb->bootSignature = EXTENDED_BOOT_SIG;
+  pb->volumeSerialNumber = volSerialNumber();
+  memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
+  memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType));
+  // write partition boot sector and backup
+  if (!writeCache(relSector)
+      || !writeCache(relSector + 6)) {
+     ESP_LOGE(TAG,"FAT32 write PBS failed");
+  }
+  clearCache(true);
+  // write extra boot area and backup
+  if (!writeCache(relSector + 2)
+      || !writeCache(relSector + 8)) {
+    ESP_LOGE(TAG,"FAT32 PBS ext failed");
+  }
+  fat32_fsinfo_t* pf = &cache.fsinfo;
+  pf->leadSignature = FSINFO_LEAD_SIG;
+  pf->structSignature = FSINFO_STRUCT_SIG;
+  pf->freeCount = 0XFFFFFFFF;
+  pf->nextFree = 0XFFFFFFFF;
+  // write FSINFO sector and backup
+  if (!writeCache(relSector + 1)
+      || !writeCache(relSector + 7)) {
+    ESP_LOGE(TAG,"FAT32 FSINFO failed");
+  }
+  clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster);
+  clearCache(false);
+  cache.fat32[0] = 0x0FFFFFF8;
+  cache.fat32[1] = 0x0FFFFFFF;
+  cache.fat32[2] = 0x0FFFFFFF;
+  // write first block of FAT and backup for reserved clusters
+  if (!writeCache(fatStart)
+      || !writeCache(fatStart + fatSize)) {
+    ESP_LOGE(TAG,"FAT32 reserve failed");
+  }
+}
+void formatCard() {
+  ESP_LOGI(TAG,"Formatting");
+  initSizes();
+  if (card.type() != SD_CARD_TYPE_SDHC) {
+    ESP_LOGI(TAG,"FAT16");
+    makeFat16();
+  } else {
+    ESP_LOGI(TAG,"FAT32");
+    makeFat32();
+  }
+  ESP_LOGI(TAG,"Format done");
+}
+#endif