├── .gitignore ├── README.md ├── data ├── logo.jpg ├── netpass ├── favicon.bmp ├── favicon.ico ├── upload.html ├── settings.html └── directory.html ├── .gitattributes ├── docs └── Reinagalerij-nl.docx ├── listFS.h ├── settings.ino ├── ReinaGalerij.ino ├── fsx.h ├── apptft.ino ├── MultiEncrypt.ino ├── listFS.ino ├── OpenSCAD └── reinagalerij3.scad ├── fsxcore.ino └── fsxweb.ino /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reinagalerij 2 | An image viewer with web based filemanager 3 | -------------------------------------------------------------------------------- /data/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitpeut/Reinagalerij/main/data/logo.jpg -------------------------------------------------------------------------------- /data/netpass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitpeut/Reinagalerij/main/data/netpass -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /data/favicon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitpeut/Reinagalerij/main/data/favicon.bmp -------------------------------------------------------------------------------- /data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitpeut/Reinagalerij/main/data/favicon.ico -------------------------------------------------------------------------------- /docs/Reinagalerij-nl.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitpeut/Reinagalerij/main/docs/Reinagalerij-nl.docx -------------------------------------------------------------------------------- /listFS.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTFS_H 2 | #define LISTFS_H 3 | 4 | class listFS : public Stream 5 | { 6 | public: 7 | listFS(); 8 | ~listFS(); 9 | bool open( String oname = "", const char *mode = FILE_READ); 10 | const char *name(){ return( listname ); }; 11 | void close(); 12 | size_t readBytes( uint8_t *buffer, size_t length ) override; 13 | size_t size() { return( psbuffer_length ); }; 14 | int available() override { return ( (int)(endpsbuffer - readpointer) ); }; 15 | int read() override; 16 | int peek() override; 17 | void flush() override{return;}; 18 | size_t write( uint8_t ) override { return(0); }; 19 | void fs2json( fs::FS &fs, const char *fsmount, const char *dirname, char **s , int depth); 20 | void allfs2json( char **s ); 21 | private: 22 | uint8_t *psbuffer = NULL; 23 | uint8_t *endpsbuffer = NULL; 24 | uint8_t *readpointer = NULL; 25 | size_t psbuffer_length=0; 26 | uint8_t abort_listing; 27 | char listname[64]; 28 | 29 | void addtos ( char **s, const char *added); 30 | }; 31 | extern listFS lister; 32 | extern listFS directory; 33 | extern listFS uploadform; 34 | extern listFS settingsform; 35 | extern listFS logo; 36 | 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /settings.ino: -------------------------------------------------------------------------------- 1 | #include "fsx.h" 2 | 3 | struct appconfig settings; 4 | 5 | 6 | /*-----------------------------------------------*/ 7 | bool write_config() 8 | { 9 | fs::FS *fs = fsdlist.back().f; 10 | File out=fs->open( "/settings.bin", "w"); 11 | const uint8_t *b; 12 | size_t r,rt, size = sizeof( struct appconfig ); 13 | 14 | if ( !out ) return false; 15 | 16 | b = ( uint8_t *)&settings; 17 | for ( r= 0, rt=0; rt < size; rt += r ){ 18 | r = out.write( b,size - rt); 19 | b += r; 20 | } 21 | out.close(); 22 | return true; 23 | } 24 | 25 | 26 | /*-----------------------------------------------*/ 27 | bool read_config() 28 | { 29 | fs::FS *fs = fsdlist.back().f; 30 | uint8_t *b = (uint8_t *) &settings; 31 | int r, rt; 32 | 33 | File in = fs->open("/settings.bin", "r"); 34 | if (!in) { 35 | return( false ); 36 | } 37 | int s = in.size(); 38 | 39 | for ( b = (uint8_t *)&settings, r=0,rt=0; rt < s; rt +=r ){ 40 | r = in.read( b, s - rt ); 41 | b += r; 42 | } 43 | 44 | in.close(); 45 | return true; 46 | } 47 | 48 | /*---------------------------------------------------*/ 49 | void show_settings(){ 50 | 51 | Serial.printf( settings_format, settings.portland, settings.pauseseconds, settings.adapt_rotation, settings.portrait, settings.landscape); 52 | 53 | } 54 | /*---------------------------------------------------*/ 55 | void init_config(){ 56 | 57 | if ( !read_config() ){ 58 | Serial.printf("couldn't open settings,use defaults\n"); 59 | settings.portland = 0; 60 | settings.pauseseconds = 5; 61 | settings.adapt_rotation = 1; 62 | settings.portrait=0;//0 or 2 63 | settings.landscape=1; //1 or 3 64 | 65 | write_config(); 66 | } 67 | 68 | show_settings(); 69 | } 70 | -------------------------------------------------------------------------------- /ReinaGalerij.ino: -------------------------------------------------------------------------------- 1 | #include "fsx.h" 2 | 3 | 4 | const char* PROGMEM esphostname = "reinASgalerij"; 5 | const char* PROGMEM sourcefile = __FILE__; 6 | const char* PROGMEM compile_time = __DATE__ " " __TIME__; 7 | 8 | SemaphoreHandle_t fsSemaphore; 9 | 10 | //--------------------------------------------------------------------------- 11 | int make_dirtree( fs::FS &ff, String path ){ 12 | 13 | ff.mkdir( path ); 14 | 15 | String fname = path + "/" + "een.txt"; 16 | File file = ff.open(fname, FILE_WRITE); 17 | file.close(); 18 | 19 | fname = path + "/" + "twee.txt"; 20 | file = ff.open(fname, FILE_WRITE); 21 | file.close(); 22 | 23 | fname = path + "/" + "drie.txt"; 24 | file = ff.open(fname, FILE_WRITE); 25 | file.close(); 26 | 27 | path = path + "/" + "sub1"; 28 | 29 | Serial.printf( "Making directory %s , result : %d \n", path.c_str(), ff.mkdir( path ) ); 30 | String sub1path = path; 31 | 32 | fname = path + "/" + "vier.txt"; 33 | file = ff.open(fname, FILE_WRITE); 34 | file.close(); 35 | 36 | path = path + "/" + "sub2"; 37 | Serial.printf( "Making directory %s , result : %d \n", path.c_str(), ff.mkdir( path ) ); 38 | 39 | fname = path + "/" + "vijf.txt"; 40 | file = ff.open(fname, FILE_WRITE); 41 | file.close(); 42 | 43 | path = path + "/" + "sub3"; 44 | Serial.printf( "Making directory %s , result : %d \n", path.c_str(), ff.mkdir( path ) ); 45 | String sub3path = path; 46 | 47 | fname = path + "/" + "zes.txt"; 48 | file = ff.open(fname, FILE_WRITE); 49 | file.close(); 50 | 51 | 52 | String newpath = sub1path + "/sub3"; 53 | Serial.println( "Result of rename "+ sub3path + " to " + newpath + " " + String( ff.rename( sub3path, newpath ) ) ); 54 | 55 | } 56 | 57 | //--------------------------------------------------------------------------- 58 | 59 | 60 | void setup(void) { 61 | 62 | enableCore0WDT(); 63 | enableCore1WDT(); 64 | 65 | Serial.begin(115200); 66 | Serial.print("\n"); 67 | Serial.setDebugOutput(true); 68 | Serial.printf( "Sourcefile: %s\n",sourcefile ); 69 | Serial.printf( "Compile time: %s\n",compile_time ); 70 | 71 | updateSemaphore = xSemaphoreCreateMutex();; 72 | xSemaphoreTake(updateSemaphore, 10); 73 | xSemaphoreGive(updateSemaphore); 74 | 75 | tftSemaphore = xSemaphoreCreateMutex();; 76 | xSemaphoreTake(tftSemaphore, 10); 77 | xSemaphoreGive(tftSemaphore); 78 | 79 | 80 | mount_fs(); 81 | startTFT(); 82 | startWiFi(); 83 | 84 | } 85 | 86 | void loop(void) { 87 | delay(5000); 88 | 89 | xSemaphoreTake( updateSemaphore, portMAX_DELAY); 90 | xSemaphoreGive( updateSemaphore); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /fsx.h: -------------------------------------------------------------------------------- 1 | #ifndef FSX_H 2 | #define FSX_H 3 | 4 | //#define HTTP_UPLOAD_BUFLEN (1436 * 4) 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "soc/timer_group_struct.h" 15 | #include "soc/timer_group_reg.h" 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | 39 | #include "listFS.h" 40 | 41 | #define FSDSD_MMC 0 42 | #define FSDSPIFFS 1 43 | #define FSDLITTLEFS 2 44 | #define FSDFFAT 3 45 | 46 | struct fsd{ 47 | fs::FS *f; 48 | char fsname[16]; // file system name (without trailing ':') for use with Arduino C++ interface 49 | char fsmount[16]; // topdirectoryname (with preceding '/') when using C-style file operations 50 | uint8_t fsnumber; // fs number one of FSD... defines above 51 | uint64_t totalBytes; 52 | uint64_t freeBytes; 53 | uint64_t usedBytes; 54 | }; 55 | 56 | struct appconfig{ 57 | uint32_t pauseseconds; 58 | bool portland; 59 | bool adapt_rotation; 60 | uint8_t portrait; 61 | uint8_t landscape; 62 | }; 63 | 64 | extern struct appconfig settings; 65 | static const char* settings_format PROGMEM="{\n\t\"portland\" : %d,\n\t\"pauseseconds\" : %d,\n \t\"adapt_rotation\" : %d,\n \t\"portrait\" : %d,\n\t\"landscape\" : %d\n}\n"; 66 | static const char* updateform PROGMEM= "
"; 67 | 68 | static const char *cperror[7] PROGMEM = {"ok" , 69 | "source directory not found" , 70 | "source file not found" , 71 | "source directory in targetdirectory" , 72 | "error allocating memory for readbuffer" 73 | "error reading source file" , 74 | "error writing target file", 75 | "error creating target directory"}; 76 | 77 | static const char uploadpage[] PROGMEM = 78 | R"( 79 | 80 | 81 | 82 | 83 | 84 | 85 | 91 | 92 | 93 |

Upload file

94 |
95 | 96 | 97 | 98 |
Local file to upload
99 |
100 | )"; 101 | 102 | 103 | extern std::vector fsdlist; 104 | extern AsyncWebServer fsxserver; 105 | extern TaskHandle_t startwifiTask; 106 | extern TaskHandle_t FSXServerTask; 107 | extern TaskHandle_t tftshowTask; 108 | extern SemaphoreHandle_t updateSemaphore; 109 | extern SemaphoreHandle_t tftSemaphore; 110 | 111 | 112 | void fs2json( fs::FS &fs, const char *fsmount, const char *dirname, char **s , int depth = 0); 113 | void allfs2json( char **s); 114 | bool write_list( char *readbuffer, size_t len ); 115 | void mount_fs(); 116 | fs::FS *fsfromfile( const char *fullfilename ); 117 | char *nameoffs( fs::FS *ff ); 118 | size_t fileoffset( const char *fullfilename ); 119 | bool isdir( const char *fullfilename ); 120 | bool sourceintarget( String& sourcepath, String& targetpath ); 121 | int makepath( fs::FS &ff, String &pstring ); 122 | void maketargetpath( String& filename, String& targetdir, String& newpath ); 123 | void deltree( fs::FS *ff, const char *path ); 124 | int copyfile( fs::FS *sourcefs, String &sourcefspath, fs::FS *targetfs, String &targetfspath); 125 | int copytree( fs::FS *sourcefs, String &sourcefspath, fs::FS *targetfs, String &targetfspath); 126 | void startWiFi(); 127 | void startFSXServer(); 128 | void startTFT(); 129 | 130 | void init_config(); 131 | bool read_config(); 132 | bool write_config(); 133 | 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /data/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 41 | 45 | 46 | 47 | 48 |
49 | 50 |
51 | 52 |
53 | 54 | 58 |
59 | 60 |
61 | 62 |
Select input, files or directory
63 | 64 |
65 | Directory
66 | Files
67 |
68 | 69 |
Local files to upload
70 | 71 |
72 |
 
73 | 74 | 75 | 76 | 77 | 185 | 186 | -------------------------------------------------------------------------------- /data/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 73 | 74 | 75 | 76 | 77 |
78 | 79 |
80 | 81 |
82 |
83 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /apptft.ino: -------------------------------------------------------------------------------- 1 | #include "fsx.h" 2 | 3 | #include "SPI.h" 4 | #include 5 | #include 6 | 7 | TFT_eSPI tft = TFT_eSPI(); 8 | TFT_eSprite img = TFT_eSprite(&tft); 9 | 10 | TaskHandle_t tftshowTask; 11 | SemaphoreHandle_t tftSemaphore; 12 | 13 | /* 14 | * #define TFT_BACKLIGHT_ON HIGH 15 | * #define TFT_MOSI 23 16 | * #define TFT_SCLK 19 // was 18 17 | * #define TFT_CS 5 // from 15 to avoid sd card conflict Chip select control pin 18 | * #define TFT_DC 0 // from 2 to avoid sd card conflict Data Command control pin 19 | * #define TFT_RST 22 // Reset pin (could connect to RST pin) 20 | * #define TFT_BL 21 21 | */ 22 | //--------------------------------------------------------------------- 23 | void tft_message( const char *message1, const char *message2 ){ 24 | 25 | img.createSprite( tft.width(), 100); 26 | img.setTextColor( TFT_WHITE, TFT_BLACK ); 27 | img.setTextSize(2); 28 | img.fillSprite(TFT_BLACK); 29 | 30 | 31 | img.drawString( message1, 0,10, 2); 32 | img.drawString( message2, 0,60, 2); 33 | 34 | 35 | xSemaphoreTake( updateSemaphore, portMAX_DELAY); 36 | img.pushSprite( 0, tft.height()/2 - 50); 37 | delay( 10000 ); 38 | xSemaphoreGive( updateSemaphore); 39 | 40 | img.deleteSprite(); 41 | } 42 | 43 | //--------------------------------------------------------------------- 44 | bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) 45 | { 46 | // Stop further decoding as image is running off bottom of screen 47 | if ( y >= tft.height() ) return 0; 48 | 49 | // This function will clip the image block rendering automatically at the TFT boundaries 50 | tft.pushImage(x, y, w, h, bitmap); 51 | 52 | // This might work instead if you adapt the sketch to use the Adafruit_GFX library 53 | // tft.drawRGBBitmap(x, y, bitmap, w, h); 54 | 55 | // Return 1 to decode next block 56 | return 1; 57 | } 58 | //----------------------------------------------------------------------- 59 | void fsjpgsize( fs::FS &fs, const char *filename, uint16_t *w, uint16_t *h){ 60 | 61 | fs::File jpgfile = fs.open( filename); 62 | TJpgDec.getFsJpgSize(w,h, jpgfile); 63 | // file should be closed by getFsJpgSize 64 | } 65 | //----------------------------------------------------------------------- 66 | void fsdrawjpg( fs::FS &fs, const char *filename, uint32_t x_offset, uint32_t y_offset ){ 67 | 68 | fs::File jpgfile = fs.open( filename); 69 | TJpgDec.drawFsJpg (x_offset, y_offset, jpgfile); 70 | // file should be closed by drawFsJpg 71 | } 72 | 73 | //----------------------------------------------------------------------- 74 | void showpicture( fs::FS &fs, const char *filename){ 75 | 76 | uint16_t w = 0, h = 0, scale; 77 | uint32_t xo = 0, yo = 0; 78 | 79 | 80 | //TJpgDec.getFsJpgSize(&w, &h, filename); // Note name preceded with "/" 81 | 82 | fsjpgsize( fs, filename, &w, &h); 83 | 84 | if ( w==0 && h ==0 ) return; 85 | 86 | tft.setRotation( settings.portrait ); 87 | if( settings.portland )tft.setRotation( settings.landscape ); 88 | 89 | if ( settings.adapt_rotation ){ 90 | if ( w > h ){ 91 | if ( !settings.portland )tft.setRotation( settings.landscape); 92 | }else{ 93 | if ( settings.portland )tft.setRotation( settings.portrait); 94 | } 95 | } 96 | 97 | for (scale = 1; scale <= 8; scale <<= 1) { 98 | // if ( w < ((tft.width() * scale* 2)/3) && scale > 1 )scale>>=1; 99 | //if ( w < ((tft.width() * scale* 2)/3) && scale > 1 ){ 100 | if ( w < ((tft.width() * scale* 2)/3) && scale > 1 ){ 101 | scale>>=1; 102 | break; 103 | } 104 | } 105 | 106 | TJpgDec.setJpgScale(scale); 107 | 108 | // if picture is smaller, then center 109 | // if ( w < tft.width()*scale ) xo = (tft.width() - w/scale)/2; 110 | //if ( h < tft.height()*scale ) yo = (tft.height() - h/scale)/2; 111 | 112 | if ( w < tft.width()*scale ) xo = ( tft.width() - w/scale)/2; 113 | if ( h < tft.height()*scale ) yo = ( tft.height() - h/scale)/2; 114 | 115 | if ( xo || yo)tft.fillScreen(TFT_BLACK); 116 | 117 | //Serial.printf("%s Width = %d,height %d xo = %d, yo = %d\n",filename, w/scale,h/scale, xo,yo); 118 | 119 | // Draw the image, top left at xo, yo 120 | //TJpgDec.drawFsJpg(xo, yo, filename); 121 | 122 | xSemaphoreTake( updateSemaphore, portMAX_DELAY); 123 | fsdrawjpg( fs, filename, xo, yo ); 124 | xSemaphoreGive( updateSemaphore); 125 | 126 | delay( settings.pauseseconds * 1000 ); 127 | 128 | return; 129 | } 130 | //----------------------------------------------------------------------------- 131 | void traversefs( fs::FS &fs, const char *dirname ){ 132 | 133 | fs::File dir = fs.open( dirname ); 134 | 135 | if(!dir.isDirectory()){ 136 | //Serial.println(" - not a directory"); 137 | return; 138 | } 139 | 140 | fs::File entry; 141 | 142 | while ( (entry = dir.openNextFile()) ){ 143 | 144 | xSemaphoreTake( updateSemaphore, portMAX_DELAY); 145 | xSemaphoreGive( updateSemaphore); 146 | 147 | char *tmpname= strdup( entry.name() ); 148 | if ( entry.isDirectory() ) { 149 | entry.close(); 150 | traversefs( fs, tmpname ); 151 | }else{ 152 | entry.close(); 153 | showpicture ( fs, tmpname ); 154 | } 155 | free( tmpname ); 156 | } 157 | 158 | dir.close(); 159 | 160 | } 161 | 162 | //------------------------------------------------------- 163 | 164 | 165 | void initTFT(){ 166 | 167 | init_config(); 168 | 169 | tft.begin(); 170 | tft.setTextColor(0xFFFF, 0x0000); 171 | tft.fillScreen(TFT_BLACK); 172 | tft.setSwapBytes(true); // We need to swap the colour bytes (endianess) 173 | tft.setRotation( settings.portrait ); 174 | // The jpeg image can be scaled by a factor of 1, 2, 4, or 8 175 | TJpgDec.setJpgScale(1); 176 | 177 | // The decoder must be given the exact name of the rendering function above 178 | TJpgDec.setCallback(tft_output); 179 | 180 | showpicture ( *(fsdlist.back().f) , "/logo.jpg" ); 181 | 182 | } 183 | 184 | //--------------------------------------------------------------------------------------- 185 | void runtftShow( void *param){ 186 | 187 | 188 | initTFT(); 189 | 190 | while(1){ 191 | refresh_fsd_usage(); 192 | for ( auto &efes : fsdlist ){ 193 | Serial.printf ("Now traversing fs %s\n", efes.fsname); 194 | traversefs( *(efes.f), "/" ); 195 | } 196 | } 197 | } 198 | 199 | //--------------------------------------------------------------------------------------- 200 | 201 | void startTFT(){ 202 | 203 | xTaskCreatePinnedToCore( 204 | runtftShow, // Task to handle special functions. 205 | "TFTshower", // name of task. 206 | 1024*4, // Stack size of task 207 | NULL, // parameter of the task 208 | 2, // priority of the task 209 | &tftshowTask, // Task handle to keep track of created task 210 | 1); //core to run it on 211 | 212 | 213 | } 214 | -------------------------------------------------------------------------------- /MultiEncrypt.ino: -------------------------------------------------------------------------------- 1 | #include "fsx.h" 2 | 3 | struct netp{ 4 | uint8_t ssid[48]; // max 32 5 | uint8_t pass[64]; //max 63 6 | }; 7 | 8 | TaskHandle_t startwifiTask; 9 | std::vector netpass; 10 | 11 | uint8_t key[32]; 12 | uint8_t iv[16]; 13 | 14 | WiFiMulti wifiMulti; 15 | 16 | fs::FS multifs = LITTLEFS; 17 | 18 | //--------------------------------------------------------------------------------------- 19 | 20 | void WiFiLostIP(WiFiEvent_t event, WiFiEventInfo_t info) 21 | { 22 | stopFSXServer(); 23 | tft_message( "Niet meer verbonden", "met een netwerk"); 24 | return; 25 | } 26 | //--------------------------------------------------------------------------------------- 27 | 28 | void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) 29 | { 30 | Serial.println("WiFi connected"); 31 | Serial.print("Obtained IP address: "); 32 | Serial.print(WiFi.localIP()); 33 | Serial.print( " on WiFi network with SSID "); 34 | Serial.println(WiFi.SSID() ); 35 | 36 | startFSXServer(); 37 | 38 | bool foundssid = false; 39 | for ( auto n : netpass ){ 40 | if ( !strcmp( (char *)n.ssid, WiFi.SSID().c_str() ) ){ 41 | // Maybe the pasword changed? 42 | if ( strcmp( (char *)n.pass, WiFi.psk().c_str() ) ){ 43 | memset( n.pass, 0, sizeof( n.pass ) ); 44 | strcpy( (char *)n.pass, WiFi.psk().c_str() ); 45 | }else{ 46 | foundssid = true; 47 | } 48 | break; 49 | } 50 | } 51 | if ( !foundssid ) { 52 | struct netp n; 53 | 54 | strcpy( (char *)n.pass, WiFi.psk().c_str() ); 55 | strcpy( (char *)n.ssid, WiFi.SSID().c_str() ); 56 | 57 | netpass.push_back( n ); 58 | netp2file(); 59 | }else{ 60 | Serial.println( "network already known"); 61 | } 62 | //displaynetp(); 63 | tft_message( "Goed. IP adres:", WiFi.localIP().toString().c_str() ); 64 | 65 | 66 | } 67 | 68 | //--------------------------------------------------------------------------------------- 69 | 70 | void runWiFi( void *param){ 71 | 72 | //deleteFile(multifs, "/netpass"); 73 | 74 | file2netp(); 75 | //displaynetp(); 76 | 77 | WiFi.onEvent(WiFiGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); 78 | WiFi.onEvent(WiFiLostIP, WiFiEvent_t::SYSTEM_EVENT_STA_LOST_IP); 79 | 80 | WiFi.mode(WIFI_STA); 81 | 82 | Serial.println("Waiting for WiFi"); 83 | 84 | for ( int i=0 ; i < 10; ++i){ 85 | int mstatus; 86 | if ( (mstatus = wifiMulti.run()) == WL_CONNECTED) break; 87 | Serial.printf( "%d mstatus = %d\n", i, mstatus); 88 | if ( mstatus == 6) break; 89 | delay(2000); 90 | } 91 | 92 | if ( WiFi.status() != WL_CONNECTED ){ 93 | 94 | //Init WiFi as Station, start SmartConfig 95 | //WiFi.mode(WIFI_AP_STA); 96 | tft_message("Onbekend netwerk. Start ", "ESP Touch op telefoon" ); 97 | WiFi.beginSmartConfig(); 98 | 99 | 100 | 101 | Serial.println("Started smartconfig"); 102 | 103 | // 2 minutes to connect using smartconfig 104 | for (int i=0; !WiFi.smartConfigDone() && i < 120 ; ++i ) { 105 | delay(500); 106 | Serial.print("."); 107 | } 108 | 109 | if ( !WiFi.smartConfigDone() ){ 110 | Serial.printf( "\n%s stop of smartconfig. No WiFi.", WiFi.stopSmartConfig()?"Successful":"Failed"); 111 | } 112 | 113 | } 114 | 115 | delay(100); 116 | netpass.clear(); 117 | netpass.shrink_to_fit(); 118 | vTaskDelete( NULL ); 119 | 120 | } 121 | //--------------------------------------------------------------------------------------- 122 | 123 | void startWiFi(){ 124 | 125 | xTaskCreatePinnedToCore( 126 | runWiFi, // Task to handle special functions. 127 | "WiFiStart", // name of task. 128 | 1024*4, // Stack size of task 129 | NULL, // parameter of the task 130 | 2, // priority of the task 131 | &startwifiTask, // Task handle to keep track of created task 132 | 0 ); //core to run it on 133 | 134 | 135 | } 136 | 137 | //------------------------------------------------------------------------------ 138 | void netp2file(){ 139 | 140 | size_t netpsize = netpass.size() * sizeof ( struct netp ); 141 | 142 | memset( iv, 0, sizeof( iv ) ); 143 | snprintf( (char *) iv, sizeof( iv) , RGiv); // RGkey and RGiv defined in WiFicredentials.h 144 | memset( key, 0, sizeof( key ) ); 145 | snprintf( (char *)key, sizeof( key), RGkey); 146 | 147 | uint8_t* cryptbuf = (uint8_t *) ps_calloc( netpsize , sizeof( uint8_t) ); 148 | 149 | esp_aes_context ctx; 150 | esp_aes_init( &ctx ); 151 | esp_aes_setkey( &ctx, key, 256 ); 152 | 153 | uint8_t *plain = (uint8_t *)netpass.data(); 154 | esp_aes_crypt_cbc( &ctx, ESP_AES_ENCRYPT, netpsize, iv, plain, cryptbuf ); 155 | 156 | /* //debug 157 | Serial.printf("Encrypted\n"); 158 | for ( int i =0; i < netpsize; ++i ){ 159 | Serial.printf( "%02x",cryptbuf[i]); 160 | if ( 0 == (i+1)%32)Serial.printf("\n"); 161 | } 162 | */ 163 | 164 | File file = multifs.open("/netpass", FILE_WRITE); 165 | if(!file){ 166 | Serial.println("- failed to open file for writing"); 167 | }else{ 168 | Serial.printf("\nwritten %d bytes to file\n", file.write( cryptbuf, netpsize ) ); 169 | } 170 | 171 | free( cryptbuf ); 172 | esp_aes_free( &ctx ); 173 | 174 | } 175 | 176 | //------------------------------------------------------------------------------ 177 | 178 | void file2netp(){ 179 | 180 | 181 | memset( iv, 0, sizeof( iv ) ); 182 | snprintf( (char *) iv, sizeof( iv) ,"wlJIJ*d0kwdxm"); 183 | memset( key, 0, sizeof( key ) ); 184 | snprintf( (char *)key, sizeof( key), "woidjcowijc(*&(GUJHGV"); 185 | 186 | File file = multifs.open("/netpass"); 187 | if ( ! file ) { 188 | Serial.println( "No file found "); 189 | return; 190 | } 191 | size_t filesize = file.size(); 192 | uint8_t* cryptbuf = (uint8_t *) ps_calloc( filesize, sizeof( uint8_t) ); 193 | 194 | file.read( cryptbuf, filesize ); 195 | 196 | file.close(); 197 | /* //debug 198 | Serial.printf("Encrypted - from file with %d bytes\n", filesize); 199 | for ( int i =0; i < filesize ; ++i ){ 200 | Serial.printf( "%02x",cryptbuf[i]); 201 | if ( 0 == (i+1)%32)Serial.printf("\n"); 202 | } 203 | Serial.printf( "\n"); 204 | */ 205 | 206 | esp_aes_context ctx; 207 | esp_aes_init( &ctx ); 208 | esp_aes_setkey( &ctx, key, 256 ); 209 | 210 | 211 | uint8_t* plainbuf = (uint8_t *) ps_calloc( filesize, sizeof( uint8_t) ); 212 | 213 | esp_aes_crypt_cbc( &ctx, ESP_AES_DECRYPT, filesize, iv, cryptbuf, plainbuf ); 214 | 215 | struct netp* n = (struct netp *) plainbuf; 216 | size_t plaincount = filesize / sizeof ( struct netp); 217 | 218 | for ( int i = 0; i < plaincount; ++i ){ 219 | netpass.push_back( *n ); 220 | wifiMulti.addAP((char *)n->ssid, (char *)n->pass); 221 | ++n; 222 | } 223 | 224 | esp_aes_free( &ctx ); 225 | free( cryptbuf ); 226 | free( plainbuf ); 227 | 228 | } 229 | 230 | /* 231 | //----debug-------------------------------------------------------------------------- 232 | 233 | void displaynetp(){ 234 | Serial.println("Display netpass"); 235 | for ( auto n: netpass ){ 236 | Serial.printf( "%s - %s\n", n.ssid, n.pass ); 237 | } 238 | } 239 | 240 | //----debug----------------------------------------------------------------------------- 241 | void deleteFile(fs::FS &fs, const char * path){ 242 | Serial.printf("Deleting file: %s\r\n", path); 243 | if(fs.remove(path)){ 244 | Serial.println("- file deleted"); 245 | } else { 246 | Serial.println("- delete failed"); 247 | } 248 | } 249 | */ 250 | -------------------------------------------------------------------------------- /listFS.ino: -------------------------------------------------------------------------------- 1 | #include "fsx.h" 2 | 3 | listFS lister; 4 | listFS directory; 5 | listFS uploadform; 6 | listFS settingsform; 7 | listFS logo; 8 | 9 | listFS::listFS(){ 10 | psbuffer = NULL; 11 | endpsbuffer = NULL; 12 | readpointer = NULL; 13 | psbuffer_length = -1; 14 | abort_listing=0; 15 | listname[0] = 0; 16 | } 17 | 18 | listFS::~listFS(){ 19 | close(); 20 | } 21 | 22 | //----------------------------------------------------- 23 | 24 | bool listFS::open( String oname, const char *mode) 25 | { 26 | 27 | if ( mode != FILE_READ ) return( false ); 28 | if ( psbuffer_length != -1 ){ 29 | readpointer = psbuffer; 30 | return(true); 31 | } 32 | 33 | fs::FS *sourcefs; 34 | File sourcefile; 35 | size_t filesize; 36 | 37 | if( oname[0] ) { 38 | Serial.printf("listFS-Starting file buffer for %s\n", oname.c_str()); 39 | 40 | strcpy( listname, oname.c_str() ); 41 | 42 | sourcefs = fsfromfile( listname ); 43 | if ( sourcefs == NULL ) sourcefs = fsdlist.back().f; 44 | 45 | sourcefile = sourcefs->open ( listname, FILE_READ ); 46 | if ( !sourcefile ){ Serial.printf( "Error opening %s for read ", listname); return( false );} 47 | 48 | filesize = sourcefile.size(); 49 | Serial.printf( "Opened %s for read, filesize %u \n",listname, filesize); 50 | 51 | psbuffer = (uint8_t *) ps_calloc( filesize ,1 ); 52 | 53 | }else{ 54 | Serial.println("listFS-Starting lister"); 55 | 56 | strcpy( listname, "lister" ); 57 | psbuffer = (uint8_t *) ps_calloc( 1000000,1 ); 58 | } 59 | 60 | if( !psbuffer){ 61 | Serial.println("Could not calloc psram\n"); 62 | if ( oname[0] ) sourcefile.close(); 63 | return(false ); 64 | } 65 | 66 | endpsbuffer = psbuffer; 67 | readpointer = psbuffer; 68 | 69 | 70 | if( oname[0] == 0) { 71 | Serial.println("Starting all2fsjson"); 72 | allfs2json( (char **) &endpsbuffer ); 73 | psbuffer_length = endpsbuffer-psbuffer; 74 | 75 | }else{ 76 | Serial.println("Reading file"); 77 | 78 | size_t bytesread = 0, totalbytesread=0; 79 | 80 | while( sourcefile.available() ){ 81 | bytesread = sourcefile.read( endpsbuffer, (filesize - totalbytesread) ); 82 | if ( bytesread < 0 ){ 83 | sourcefile.close(); 84 | close(); 85 | Serial.printf( "Read returned < 0 while reading file %s ",listname ); 86 | return( false); 87 | } 88 | 89 | totalbytesread += bytesread; 90 | endpsbuffer += bytesread; 91 | } 92 | sourcefile.close(); 93 | psbuffer_length = endpsbuffer-psbuffer; 94 | 95 | } 96 | 97 | Serial.printf("Buffered %u bytes\n", psbuffer_length); 98 | 99 | return( true ); 100 | } 101 | 102 | //----------------------------------------------------- 103 | 104 | void listFS::close(){ 105 | if ( psbuffer ){ 106 | free( psbuffer ); 107 | psbuffer = NULL; 108 | endpsbuffer = NULL; 109 | readpointer = NULL; 110 | psbuffer_length = -1; 111 | } 112 | } 113 | 114 | //------------------------------------------------------------------------------ 115 | 116 | size_t listFS::readBytes( uint8_t *buffer, size_t length = 1 ){ 117 | size_t copylength; 118 | //Serial.println("readBytes"); 119 | 120 | if( readpointer == NULL ) return 0; 121 | 122 | if( readpointer + length < endpsbuffer ){ 123 | copylength = length; 124 | }else{ 125 | copylength = endpsbuffer - readpointer; 126 | } 127 | 128 | for( int i=0; i < copylength ; ++i ){ 129 | *(buffer + i) = *readpointer++; 130 | } 131 | 132 | return copylength; 133 | } 134 | //------------------------------------------------------------- 135 | int listFS::read(){ 136 | //Serial.println("read"); 137 | if ( readpointer < endpsbuffer ){ 138 | return *readpointer++; 139 | } 140 | return( -1 ); 141 | } 142 | //------------------------------------------------------------- 143 | 144 | int listFS::peek(){ 145 | if ( readpointer < endpsbuffer ){ 146 | return *readpointer; 147 | } 148 | return( -1 ); 149 | } 150 | 151 | //------------------------------------------------------------------------------ 152 | 153 | void listFS::addtos ( char **s, const char *added){ 154 | strcpy( *s, added); //only works with readable text like json data 155 | *s += strlen(added); 156 | } 157 | 158 | //------------------------------------------------------------------------------ 159 | 160 | void listFS::fs2json( fs::FS &fs, const char *fsmount, const char *dirname, char **s , int depth){ 161 | 162 | fs::File dir = fs.open( dirname ); 163 | String output; 164 | 165 | if(!dir.isDirectory()){ 166 | Serial.println(" - not a directory"); 167 | return; 168 | } 169 | 170 | String tabs = ""; 171 | for ( int i=0 ; i < depth; ++i ) tabs += " "; 172 | 173 | output = "\n" + tabs +"[";addtos ( s, output.c_str() ); 174 | 175 | bool first = true; 176 | 177 | fs::File entry; 178 | 179 | while ( (entry = dir.openNextFile()) ){ 180 | if ( abort_listing ) return; 181 | if ( first ){ 182 | first = false; 183 | }else{ 184 | output = ",\n" + tabs;addtos ( s, output.c_str() ); 185 | 186 | } 187 | output = "{\"name\" : \""+ String(fsmount) + String(entry.name()) + "\","; 188 | addtos ( s, output.c_str() ); 189 | 190 | if ( entry.isDirectory() ){ 191 | output = "\"type\": 2,\"files\": "; 192 | addtos ( s, output.c_str() ); 193 | //Serial.printf( "%s is a directory\n", entry.name() ); 194 | fs2json(fs,fsmount, entry.name(), s, depth+1 ); 195 | output = tabs + "}"; addtos ( s, output.c_str() ); 196 | } else{ 197 | output = "\"type\": 3, \"size\": " + String( entry.size() ) + "}" ; 198 | addtos ( s, output.c_str() ); 199 | } 200 | esp_task_wdt_reset(); 201 | entry.close(); 202 | } 203 | 204 | addtos ( s, "]\n" ); 205 | dir.close(); 206 | 207 | } 208 | 209 | //------------------------------------------------------------------------------ 210 | 211 | void listFS::allfs2json( char **s ){ 212 | String output; 213 | 214 | refresh_fsd_usage(); 215 | getFS(); 216 | 217 | output = "{\"name\" : \"" + String(esphostname) + "\", \"type\": 0,\"filesystems\" : \n ["; 218 | addtos ( s, output.c_str() ); 219 | 220 | bool first = true; 221 | for ( auto &efes : fsdlist ){ 222 | if ( abort_listing ) return; 223 | 224 | if ( first ){ 225 | first = false; 226 | }else{ 227 | output = ",\n";addtos ( s, output.c_str() ); 228 | } 229 | 230 | //The arduino String (and std::string ) class cannot automatically convert 64 bit integers 231 | //Good old C to the rescue. 232 | 233 | char longlong[32]; 234 | output = "{\"name\": \"" + String( efes.fsname ) + "\", \"mount\": \"" + String( efes.fsmount ) + "\",\"type\": 1"; 235 | 236 | 237 | sprintf( longlong,"%llu", efes.totalBytes ); 238 | output += ",\"size\": " + String( longlong); 239 | 240 | 241 | sprintf( longlong,"%llu", efes.usedBytes ); 242 | output += ",\"used\": " + String( longlong ); 243 | 244 | sprintf( longlong,"%llu", efes.freeBytes ); 245 | output += ",\"free\": " + String( longlong ); 246 | output += ",\"files\" : \n"; 247 | 248 | output += " [{\"name\" : \"" + String(efes.fsmount) + "\","; 249 | output += "\"type\": 2,\"files\": "; 250 | 251 | addtos ( s, output.c_str() ); 252 | 253 | fs2json( *(efes.f), efes.fsmount, "/",s , 3); 254 | 255 | output = " }]}"; 256 | addtos ( s, output.c_str() ); 257 | 258 | } 259 | addtos ( s, "]}\n" ); 260 | xSemaphoreGive(updateSemaphore); 261 | } 262 | 263 | 264 | -------------------------------------------------------------------------------- /OpenSCAD/reinagalerij3.scad: -------------------------------------------------------------------------------- 1 | 2 | //screen 3 | visiblel = 41; 4 | visiblew = 31; 5 | 6 | visibleconnecto = 4.4; // offset from physicaledge at the connectorside 7 | visibletopo = 1.6; 8 | 9 | physicall = 49.2+0.2; 10 | physicalw = 35.7+0.2; 11 | physicalh = 2.3; 12 | 13 | physicalconnecto = 6.5; // offset from the screen where the screen connector is to the side of the pcs 14 | physicaltopo = 5.6; // offsetfrom the side with the pcb connector to the side of the screen 15 | physicalsideo = 1.5; // actually 1.5 and 1.8 16 | 17 | pcbl = 60.6 +0.2; 18 | pcbw = 37.6 +0.2; 19 | pcbh = 1.6 +0.2; 20 | 21 | conl = 2.5; 22 | conw = 22+1; 23 | conh = 12.8 - pcbh; 24 | cono = 8.0; 25 | 26 | //switch 27 | swknobd = 5.4 + 0.2; 28 | swknobh = 2.5; 29 | swringd = 7.7 + 0.2; 30 | swringh = 2.2; 31 | swl = 18.0; 32 | swminl = 12.3; 33 | swminw = 5.2; 34 | sww = 12.2; 35 | swh = 4.5; 36 | 37 | //esp 38 | esppcbl = 51; 39 | esppcbw = 28; 40 | esppcbh = 1.6; 41 | 42 | espantl = 6.8; 43 | espantw = 18+0.2; 44 | espanth = 1; 45 | espanto = 4.9; 46 | 47 | espl = 18.7; 48 | esph = 4.9 - esppcbh; 49 | 50 | espsdl = 3.4; 51 | espsdw = 14.8; 52 | espsdh = 1.9; 53 | espsdo = 6.6; 54 | 55 | esptopcomph = 9.9 - esppcbh; 56 | espbotcomph = 2.0; 57 | 58 | //lipo 59 | lipow = 19.5; 60 | lipol = 40; 61 | lipoh = 7.8; 62 | 63 | //usb 64 | 65 | usbl = 14.1+0.2; 66 | usbw = 15.0+0.2; 67 | usbconl = 7.5+0.2; 68 | usbconw = 5.5; 69 | usbcone = 1.0; // sticks 1 mm out over the edge 70 | usbpcbh = 1.4; 71 | usbconh = 4.0 - usbpcbh + 0.3; 72 | usbcono = 3.4 - 0.1; 73 | 74 | cushion = 5; 75 | 76 | ww = 2; 77 | h = 23; 78 | w = 70; 79 | l = 70; 80 | r = 3; 81 | clickd = 1.2; 82 | 83 | module usb(){ 84 | color("green") 85 | cube([ usbl, usbw, usbpcbh]); 86 | 87 | color("grey") 88 | translate([ usbcono, usbw - usbconw + usbcone, usbpcbh]) 89 | cube([ usbconl, usbconw, usbconh]); 90 | 91 | } 92 | 93 | module lipo(){ 94 | color("purple", 0.6) 95 | cube([ lipol, lipow, lipoh] ); 96 | } 97 | 98 | 99 | module esp(){ 100 | 101 | color("grey") 102 | translate([ esppcbl, espsdo,espbotcomph - espsdh]) 103 | cube([ espsdl, espsdw, espsdh]); 104 | 105 | color("yellow",0.3) 106 | cube([ esppcbl, esppcbw, espbotcomph]); 107 | 108 | 109 | //pcb 110 | color("green") 111 | translate([0,0,espbotcomph]) 112 | cube([ esppcbl, esppcbw, esppcbh]); 113 | 114 | //top components 115 | color("yellow",0.3) 116 | translate([0,0,espbotcomph + esppcbh]) 117 | cube([ esppcbl, esppcbw, esptopcomph]); 118 | // antenna 119 | color("red",0.7) 120 | translate([ -espantl, espanto,espbotcomph + esppcbh]) 121 | cube([ espantl, espantw, espanth] ); 122 | //esp32 123 | color("white",0.9) 124 | translate([ 0, espanto,espbotcomph + esppcbh]) 125 | cube([ espl, espantw, esph] ); 126 | 127 | 128 | 129 | } 130 | 131 | 132 | module screen(){ 133 | color("blue",0.2) 134 | cube( [visiblel, visiblew, 10] ); 135 | 136 | translate( [-visibleconnecto, -(physicalw-visiblew)/2, 10] ) 137 | color("white") 138 | cube( [physicall, physicalw, physicalh ]); 139 | 140 | 141 | translate( [-visibleconnecto - physicalconnecto, -(physicalw-visiblew)/2-(pcbw-physicalw)/2, 10 + physicalh] ) 142 | color("green",0.8) 143 | cube( [pcbl, pcbw, pcbh + 1]); 144 | 145 | 146 | 147 | translate( [-visibleconnecto - physicalconnecto + pcbl - conl , -(physicalw-visiblew)/2-(pcbw-physicalw)/2 + cono, 10 + physicalh + pcbh] ) 148 | cube([ conl, conw,conh]); 149 | 150 | 151 | translate( [-visibleconnecto - physicalconnecto, -(physicalw-visiblew)/2-(pcbw-physicalw)/2, 10 + physicalh +pcbh+1] ) 152 | color("blue",0.1) 153 | cube( [pcbl, pcbw, pcbh + cushion]); 154 | 155 | } 156 | 157 | module switch(){ 158 | 159 | color("blue", 0.2 ) 160 | cylinder( d= swknobd, h=swknobh); 161 | 162 | color("red") 163 | translate([0,0,swknobh]) 164 | cylinder( d= swringd, h=swringh, $fn=64); 165 | 166 | color("black",0.9) 167 | translate([0,0,swknobh + swringh + swh/2]) 168 | cube([ swl, sww,swh], center=true); 169 | 170 | color("blue",0.1) 171 | translate([0,0,swknobh + swringh + swh/2 + swh]) 172 | cube([ swl, sww, cushion + 6], center=true); 173 | 174 | } 175 | 176 | 177 | 178 | module box(){ 179 | translate([ r, r,r]){ 180 | difference(){ 181 | 182 | //cube( [l+2*ww,w+2*ww,h+2*ww ]); 183 | 184 | hull(){ 185 | translate( [0,0,0] ) 186 | sphere( r ); 187 | translate( [l,0,0] ) 188 | sphere( r ); 189 | translate( [l,w, 0]) 190 | sphere( r ); 191 | translate( [0,w,0 ]) 192 | sphere( r ); 193 | 194 | translate( [0,0,h] ) 195 | sphere( r ); 196 | translate( [l,0,h] ) 197 | sphere( r ); 198 | translate( [l,w, h]) 199 | sphere( r ); 200 | translate( [0,w,h] ) 201 | sphere( r ); 202 | } 203 | 204 | //clicks only on the l sides 205 | 206 | translate([ r, w, h - r ]) 207 | rotate([0,90,0]) 208 | cylinder(d=clickd,h=l-2*r,$fn=12); 209 | 210 | 211 | translate([ r,0, h - r ]) 212 | rotate([0,90,0]) 213 | cylinder(d=clickd,h=l-2*r,$fn=12); 214 | 215 | //cut off top 216 | translate( [-r,-r,h]) 217 | cube( [ l+2*r, w+2*r, r] ); 218 | 219 | 220 | // innerbox thicker bottom 221 | translate( [0,0,pcbh*2 + cushion] ) 222 | cube( [l,w,h+ww] ); // +ww to h to make a hole 223 | 224 | 225 | translate( [ (l - (pcbl/2))/2 - r -r/2, (w-pcbw/2)/2 -r - r/2, -10 + physicalh -r] ) 226 | screen(); 227 | 228 | translate( [ l/2, sww/2, -swknobh -r] ) 229 | switch(); 230 | 231 | translate( [ l/2 -2*r, w - esppcbl, physicalh + pcbh + 2 ] ) 232 | translate([ esppcbw,0,0] ) 233 | rotate( [ 0,0,90 ]) 234 | esp(); 235 | 236 | translate( [0, 0, pcbh*2 + 2 -r] ) 237 | translate([ lipow,0,0] ) 238 | rotate( [ 0,0,90 ]) 239 | lipo(); 240 | 241 | translate([ r , w +r- usbw -usbcone/2,pcbh*2-usbpcbh +cushion]) 242 | usb(); 243 | 244 | } 245 | 246 | } 247 | } 248 | 249 | 250 | module lid(){ 251 | 252 | translate([ r, r,r]){ 253 | difference(){ 254 | 255 | hull(){ 256 | translate( [0,0,0] ) 257 | sphere( r ); 258 | translate( [l,0,0] ) 259 | sphere( r ); 260 | translate( [l,w, 0]) 261 | sphere( r ); 262 | translate( [0,w,0 ]) 263 | sphere( r ); 264 | } 265 | 266 | //cut off top 267 | translate( [-r,-r, 0]) 268 | cube( [ l+2*r, w+2*r, r] ); 269 | 270 | 271 | } 272 | 273 | //inner rim 274 | difference(){ 275 | cube([l,w,r*3]); 276 | 277 | translate( [r/2,r/2,0 ] )cube([l-r,w-r,r*3]); 278 | 279 | 280 | 281 | 282 | } 283 | //clicks only on the l sides 284 | 285 | translate([ 2*r, w, r ]) 286 | rotate([0,90,0]) 287 | cylinder(d=clickd,h=l-4*r,$fn=12); 288 | 289 | 290 | translate([ 2*r,0, r ]) 291 | rotate([0,90,0]) 292 | cylinder(d=clickd,h=l-4*r,$fn=12); 293 | } 294 | } 295 | 296 | 297 | box(); 298 | translate([l +10,0,0]) 299 | lid(); 300 | 301 | 302 | /* 303 | translate( [ (l - (pcbl/2))/2 -ww/2, (w-pcbw/2)/2 -ww , -10 + physicalh] ) 304 | screen(); 305 | 306 | translate( [ ww +l/2, sww -ww, -swknobh ] ) 307 | switch(); 308 | 309 | translate( [ l/2-2*ww, ww + w - ( esppcbl), ww+pcbh*2 + 2] ) 310 | translate([ esppcbw,0,0] ) 311 | rotate( [ 0,0,90 ]) 312 | esp(); 313 | 314 | translate( [ww+1, ww , ww+pcbh*2 + 2] ) 315 | translate([ lipow,0,0] ) 316 | rotate( [ 0,0,90 ]) 317 | lipo(); 318 | 319 | translate([ 2*ww, 2*ww + w - usbw -usbcone/2,ww+pcbh*2-usbpcbh +cushion]) 320 | usb(); 321 | */ -------------------------------------------------------------------------------- /fsxcore.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "fsx.h" 3 | 4 | // SD Card | ESP32 5 | // D2 12 // not for onebit 6 | // D3 13 // not for onebit 7 | // CMD 15 8 | // VSS GND 9 | // VDD 3.3V 10 | // CLK 14 11 | // VSS GND 12 | // D0 2 (add 1K pull up after flashing) 13 | // D1 43 14 | 15 | std::vector fsdlist; 16 | //------------------------------------------------------------------------------ 17 | void refresh_fsd_usage(){ 18 | for ( auto &efes : fsdlist ){ 19 | 20 | switch ( efes.fsnumber ){ 21 | 22 | Serial.printf("fsnumber : %d\n", efes.fsnumber); 23 | 24 | 25 | case FSDSD_MMC: 26 | efes.totalBytes = SD_MMC.totalBytes(); efes.freeBytes = SD_MMC.totalBytes() - SD_MMC.usedBytes(); efes.usedBytes = SD_MMC.usedBytes(); 27 | break; 28 | case FSDSPIFFS: 29 | efes.totalBytes = SPIFFS.totalBytes(); efes.freeBytes = SPIFFS.totalBytes() - SPIFFS.usedBytes(); efes.usedBytes = SPIFFS.usedBytes(); 30 | break; 31 | case FSDLITTLEFS: 32 | efes.totalBytes = LITTLEFS.totalBytes(); efes.freeBytes = LITTLEFS.totalBytes() - LITTLEFS.usedBytes(); efes.usedBytes = LITTLEFS.usedBytes(); 33 | break; 34 | 35 | case FSDFFAT: 36 | efes.totalBytes = FFat.totalBytes(); efes.freeBytes = FFat.freeBytes(); efes.usedBytes = FFat.totalBytes()- FFat.freeBytes(); 37 | break; 38 | default: 39 | break; 40 | } 41 | } 42 | } 43 | 44 | 45 | 46 | //------------------------------------------------------------------------------ 47 | void mount_fs(){ 48 | 49 | String output; 50 | 51 | 52 | if( SD_MMC.begin("/sdcard", true) ){ // onebit mode change to fals for 2bitmode 53 | fsdlist.push_back( (struct fsd){ &SD_MMC,"SD_MMC","/sdcard",FSDSD_MMC,SD_MMC.totalBytes(),SD_MMC.totalBytes()-SD_MMC.usedBytes(), SD_MMC.usedBytes()} ); 54 | Serial.printf( "sd mmc totalbytes: %llu, used bytes %llu\n", SD_MMC.totalBytes(), SD_MMC.usedBytes() ); 55 | // delay(1000); 56 | } 57 | 58 | if( SPIFFS.begin() ){ 59 | fsdlist.push_back( (struct fsd){ &SPIFFS,"SPIFFS","/spiffs",FSDSPIFFS,SPIFFS.totalBytes(),SPIFFS.totalBytes()-SPIFFS.usedBytes(), SPIFFS.usedBytes()} ); 60 | // delay(1000); 61 | } 62 | if( LITTLEFS.begin() ){ 63 | fsdlist.push_back( (struct fsd){ &LITTLEFS, "LITTLEFS","/littlefs",FSDLITTLEFS, LITTLEFS.totalBytes(), LITTLEFS.totalBytes()-LITTLEFS.usedBytes(), LITTLEFS.usedBytes()} ); 64 | // delay(1000); 65 | } 66 | 67 | if( FFat.begin() ){ 68 | fsdlist.push_back( (struct fsd){ &FFat,"FFat","/ffat",FSDFFAT,FFat.totalBytes(),FFat.freeBytes(), FFat.totalBytes()- FFat.freeBytes()} ); 69 | // delay(1000); 70 | } 71 | 72 | if ( psramFound() ){ 73 | lister.open(); 74 | directory.open("/directory.html"); 75 | uploadform.open("/upload.html"); 76 | settingsform.open("/settings.html"); 77 | logo.open("/favicon.ico"); 78 | } 79 | } 80 | //------------------------------------------------------------------------------ 81 | char *nameoffs( fs::FS *ff ){ 82 | 83 | for ( auto &efes : fsdlist ){ 84 | if ( efes.f == ff )return efes.fsname; 85 | } 86 | 87 | return( NULL ); 88 | } 89 | //------------------------------------------------------------------------------ 90 | void maketargetpath( String& filename, String& targetdir, String& newpath ){ 91 | 92 | newpath = targetdir + "/"; 93 | newpath += filename.substring( filename.lastIndexOf( '/' ) + 1 ); 94 | } 95 | //------------------------------------------------------------------------------ 96 | fs::FS *fsfromfile( const char *fullfilename ){ 97 | 98 | for ( auto &efes : fsdlist ){ 99 | if ( strncmp( efes.fsmount, fullfilename,strlen( efes.fsmount ) ) == 0 )return efes.f; 100 | } 101 | 102 | return( NULL ); 103 | } 104 | 105 | //--------------------------------------------------------------------------- 106 | 107 | bool isdir( const char *fullfilename ){ 108 | DIR *dir = opendir(fullfilename); 109 | if (!dir){ 110 | //Serial.printf( "%s is not a directory. Cannot list.\n", basepath); 111 | return(false); 112 | } 113 | closedir( dir ); 114 | return(true); 115 | } 116 | //--------------------------------------------------------------------------- 117 | bool sourceintarget( String& sourcepath, String& targetpath ){ 118 | if ( !isdir( sourcepath.c_str() ) )return(false); 119 | 120 | if ( targetpath.startsWith( sourcepath + "/" ) ) return(true); 121 | return(false); 122 | } 123 | 124 | //--------------------------------------------------------------------------- 125 | void deltree( fs::FS *ff, const char *path ){ 126 | 127 | fs::File dir = ff->open( path ); 128 | 129 | if ( !dir )return; 130 | 131 | if(!dir.isDirectory()){ 132 | Serial.printf("%s is a file\n", path); 133 | dir.close(); 134 | Serial.printf( "result of removing file %s: %d\n", path, ff->remove( path ) ); 135 | return; 136 | } 137 | 138 | Serial.printf("%s is a directory\n", path); 139 | 140 | fs::File entry, nextentry; 141 | 142 | while ( entry = dir.openNextFile() ){ 143 | 144 | if ( entry.isDirectory() ){ 145 | deltree( ff, entry.name() ); 146 | esp_task_wdt_reset(); 147 | } else{ 148 | char* tmpname = strdup( entry.name() ); 149 | entry.close();// openNextFile opens the file, LittleFS will refuse to delete the file if open; 150 | // FAT doesn't mind 151 | Serial.printf( "result of removing file %s: %d\n", tmpname, ff->remove( tmpname ) ); 152 | free( tmpname ); 153 | esp_task_wdt_reset(); 154 | } 155 | 156 | } 157 | 158 | dir.close(); 159 | Serial.printf( "result of removing directory %s: %d\n", path, ff->rmdir( path ) ); 160 | } 161 | 162 | //------------------------------------------------------------------------------ 163 | // returns 0 - ok 164 | // returns 1 - source directory not found 165 | // returns 2 - source file not found by copyfile 166 | // return 3 - source directory in target directory 167 | // return 4 - no memory for read buffer 168 | // return 5 - error reading source file 169 | // return 6 - error writing target file 170 | // return 7 - error removing target directory 171 | 172 | 173 | int copytree( fs::FS *sourcefs, String &sourcefspath, fs::FS *targetfs, String &targetfspath){ 174 | int result; 175 | 176 | if ( sourcefspath.length() == 0 ) sourcefspath = "/"; 177 | Serial.println("Copying tree from [" + sourcefspath + "]"); 178 | fs::File dir = sourcefs->open( sourcefspath ); 179 | if ( !dir ){ 180 | Serial.println("failed to open inputdirectory" + sourcefspath ); 181 | return(1); 182 | } 183 | 184 | // copyfile will copy the file if source is a file, or create a directory if source is a directory 185 | result = copyfile( sourcefs, sourcefspath, targetfs, targetfspath); 186 | esp_task_wdt_reset(); 187 | 188 | if(!dir.isDirectory() || result ){ 189 | if( !dir.isDirectory() ) Serial.println(sourcefspath + " is a file\n"); 190 | dir.close(); 191 | return( result); 192 | } 193 | 194 | Serial.println( sourcefspath + " is a directory\n"); 195 | 196 | fs::File entry; 197 | while ( entry = dir.openNextFile() ){ 198 | 199 | String tmpname = String(entry.name()); 200 | entry.close();// openNextFile opens the file, LittleFS will refuse to delete the file if open; 201 | // FAT doesn't mind 202 | result = copytree( sourcefs, tmpname, targetfs, targetfspath + "/" + tmpname.substring( tmpname.lastIndexOf( '/' ) + 1 ) ); 203 | if ( result ) return( result ); 204 | 205 | } 206 | 207 | dir.close(); 208 | return(0); 209 | } 210 | 211 | 212 | //------------------------------------------------------------------------------ 213 | int copyfile( fs::FS *sourcefs, String &sourcefspath, fs::FS *targetfs, String &targetfspath){ 214 | 215 | size_t filesize, bytesread=0, totalbytesread=0,totalbyteswritten=0, byteswritten=0, writeresult; 216 | size_t readbuffer_size = (1024*32); 217 | uint8_t *readbuffer; 218 | 219 | Serial.println( "Copy "+ sourcefspath + " to "+ targetfspath ); 220 | 221 | File sourcefile = sourcefs->open ( sourcefspath, FILE_READ ); 222 | if ( !sourcefile ){ Serial.println( "Error opening "+ sourcefspath + "for read "); return( 2 );} 223 | 224 | if ( sourcefile.isDirectory() ){ 225 | sourcefile.close(); 226 | if ( sourcefs == targetfs && sourceintarget( sourcefspath, targetfspath ) ){ 227 | return( 3); 228 | } 229 | 230 | if ( !targetfs->exists( targetfspath ) ){ 231 | if ( ! targetfs->mkdir( targetfspath ) )return( 7 ); 232 | } 233 | } 234 | 235 | if ( psramFound() ){ 236 | Serial.printf( "Allocating %u bytes of psram\n", readbuffer_size); 237 | readbuffer = (uint8_t *)ps_malloc( readbuffer_size ); 238 | }else{ 239 | readbuffer_size = 1024; 240 | Serial.printf( "Allocating %u bytes from heap\n", readbuffer_size); 241 | readbuffer = (uint8_t *)malloc( readbuffer_size ); 242 | } 243 | 244 | if ( ! readbuffer ){ 245 | sourcefile.close(); 246 | Serial.println( "Error allocating memory "); 247 | return( 4 ); 248 | } 249 | 250 | File targetfile = targetfs->open ( targetfspath, FILE_WRITE ); 251 | if ( !targetfile ){ sourcefile.close(); Serial.println( "Error opening "+ targetfspath + "for write"); return( false);} 252 | 253 | filesize = sourcefile.size(); 254 | 255 | while( totalbyteswritten < filesize ){ 256 | 257 | bytesread = sourcefile.read( readbuffer, (filesize - totalbytesread)> readbuffer_size ? readbuffer_size :(filesize - totalbytesread) ); 258 | if ( bytesread < 0 ){ 259 | sourcefile.close(); 260 | targetfile.close(); 261 | free(readbuffer); 262 | targetfs->remove( targetfspath ); 263 | Serial.println( "Error reading "+ sourcefspath); 264 | return(5); 265 | } 266 | 267 | totalbytesread += bytesread; 268 | esp_task_wdt_reset(); 269 | 270 | byteswritten = 0; 271 | while( byteswritten < bytesread ){ 272 | writeresult= targetfile.write( &readbuffer[ byteswritten ], bytesread - byteswritten ); 273 | if ( writeresult == 0 ){ 274 | sourcefile.close(); 275 | targetfile.close(); 276 | free(readbuffer); 277 | targetfs->remove( targetfspath ); 278 | Serial.println( "Error writing "+ targetfspath); 279 | return(6); 280 | } 281 | byteswritten += writeresult; 282 | } 283 | totalbyteswritten += byteswritten; 284 | } 285 | 286 | sourcefile.close(); 287 | targetfile.close(); 288 | free(readbuffer); 289 | 290 | return(0); 291 | } 292 | //------------------------------------------------------------------------------ 293 | size_t fileoffset( const char *fullfilename ){ 294 | const char *s; 295 | for ( s= fullfilename + 1; *s != '/';++s ); 296 | return( s - fullfilename ); 297 | } 298 | 299 | 300 | //------------------------------------------------------------------------------ 301 | 302 | int makepath( fs::FS &ff, String &pstring ){ 303 | char *s, *pathstring = (char *)pstring.c_str(); 304 | int mode=0, slashcount=0; 305 | 306 | s = pathstring; 307 | 308 | while ( *s ){ 309 | switch( mode ){ 310 | case 0: // if file system included switch mode at slashcount 2 instead of 1 311 | if ( *s == '/')++slashcount; 312 | if ( slashcount == 1 ){ mode = 1; } 313 | break; 314 | case 1: 315 | if ( *s == '/'){ 316 | *s = 0; 317 | 318 | if ( !ff.mkdir( pathstring ) ){ 319 | Serial.printf( "Failed to create directory %s\n", pathstring); 320 | return(0); 321 | } 322 | Serial.printf( "Created directory %s\n", pathstring); 323 | *s = '/'; 324 | } 325 | break; 326 | } 327 | ++s; 328 | } 329 | 330 | Serial.printf( "Directories created for %s\n", pathstring); 331 | return(1); 332 | } 333 | -------------------------------------------------------------------------------- /fsxweb.ino: -------------------------------------------------------------------------------- 1 | #include "fsx.h" 2 | 3 | AsyncWebServer fsxserver(80); 4 | TaskHandle_t FSXServerTask; 5 | SemaphoreHandle_t updateSemaphore; 6 | 7 | //------------------------------------------------------------------------------------ 8 | // added esp_task_wdt_reset(); in some tight loops against task watchdog panics. 9 | 10 | void getFS(){ 11 | xSemaphoreTake( updateSemaphore, portMAX_DELAY); 12 | } 13 | 14 | void loseFS(){ 15 | xSemaphoreGive( updateSemaphore); 16 | } 17 | 18 | 19 | //============================================================== 20 | // URL Encode Decode Functions 21 | //https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/ 22 | //============================================================== 23 | String urldecode(String str) 24 | { 25 | 26 | String encodedString=""; 27 | char c; 28 | char code0; 29 | char code1; 30 | for (int i =0; i < str.length(); i++){ 31 | c=str.charAt(i); 32 | if (c == '+'){ 33 | encodedString+=' '; 34 | }else if (c == '%') { 35 | i++; 36 | code0=str.charAt(i); 37 | i++; 38 | code1=str.charAt(i); 39 | c = (h2int(code0) << 4) | h2int(code1); 40 | encodedString+=c; 41 | } else{ 42 | 43 | encodedString+=c; 44 | } 45 | 46 | yield(); 47 | } 48 | 49 | return encodedString; 50 | } 51 | 52 | String urlencode(String str) 53 | { 54 | String encodedString=""; 55 | char c; 56 | char code0; 57 | char code1; 58 | char code2; 59 | for (int i =0; i < str.length(); i++){ 60 | c=str.charAt(i); 61 | if (c == ' '){ 62 | encodedString+= '+'; 63 | } else if (isalnum(c)){ 64 | encodedString+=c; 65 | } else{ 66 | code1=(c & 0xf)+'0'; 67 | if ((c & 0xf) >9){ 68 | code1=(c & 0xf) - 10 + 'A'; 69 | } 70 | c=(c>>4)&0xf; 71 | code0=c+'0'; 72 | if (c > 9){ 73 | code0=c - 10 + 'A'; 74 | } 75 | code2='\0'; 76 | encodedString+='%'; 77 | encodedString+=code0; 78 | encodedString+=code1; 79 | //encodedString+=code2; 80 | } 81 | yield(); 82 | } 83 | return encodedString; 84 | 85 | } 86 | //------------------------------------------------------------------------------------ 87 | 88 | unsigned char h2int(char c) 89 | { 90 | if (c >= '0' && c <='9'){ 91 | return((unsigned char)c - '0'); 92 | } 93 | if (c >= 'a' && c <='f'){ 94 | return((unsigned char)c - 'a' + 10); 95 | } 96 | if (c >= 'A' && c <='F'){ 97 | return((unsigned char)c - 'A' + 10); 98 | } 99 | return(0); 100 | } 101 | //------------------------------------------------------------------------------------ 102 | //format bytes 103 | String formatBytes(size_t bytes) { 104 | if (bytes < 1024) { 105 | return String(bytes) + "B"; 106 | } else if (bytes < (1024 * 1024)) { 107 | return String(bytes / 1024.0) + "KB"; 108 | } else if (bytes < (1024 * 1024 * 1024)) { 109 | return String(bytes / 1024.0 / 1024.0) + "MB"; 110 | } else { 111 | return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; 112 | } 113 | } 114 | //--------------------------------------------------------------------------- 115 | 116 | String getContentType(String filename) { 117 | 118 | if (filename.endsWith(".htm")) { 119 | return "text/html"; 120 | } else if (filename.endsWith(".html")) { 121 | return "text/html"; 122 | } else if (filename.endsWith(".css")) { 123 | return "text/css"; 124 | } else if (filename.endsWith(".js")) { 125 | return "application/javascript"; 126 | } else if (filename.endsWith(".png")) { 127 | return "image/png"; 128 | } else if (filename.endsWith(".gif")) { 129 | return "image/gif"; 130 | } else if (filename.endsWith(".bmp")) { 131 | return "image/bmp"; 132 | } else if (filename.endsWith(".jpg")) { 133 | return "image/jpeg"; 134 | } else if (filename.endsWith(".ico")) { 135 | return "image/x-icon"; 136 | } else if (filename.endsWith(".xml")) { 137 | return "text/xml"; 138 | } else if (filename.endsWith(".pdf")) { 139 | return "application/x-pdf"; 140 | } else if (filename.endsWith(".zip")) { 141 | return "application/x-zip"; 142 | } else if (filename.endsWith(".gz")) { 143 | return "application/x-gzip"; 144 | }else if (filename.endsWith(".wav")) { 145 | return "audio/wav"; 146 | }else if (filename.endsWith(".mp3")) { 147 | return "audio/mp3"; 148 | }else if (filename.endsWith(".m4a")) { 149 | return "audio/m4a"; 150 | }else if (filename.endsWith(".flac")) { 151 | return "audio/flac"; 152 | } 153 | return "text/plain"; 154 | } 155 | 156 | 157 | //-------------------------------------------------------------------------------- 158 | 159 | void handleFileRead( AsyncWebServerRequest *request ) { 160 | 161 | String path = request->url(); 162 | 163 | Serial.println("handleFileRead: " + path); 164 | if (path.endsWith("/")) { 165 | path += "index.htm"; 166 | } 167 | path = urldecode( path); 168 | 169 | fs::FS *ff = fsfromfile( path.c_str() ); 170 | 171 | String fspath; 172 | 173 | if ( ff == NULL ){ 174 | ff = fsdlist.back().f; 175 | //flash filesystems added last to fsdlist, due ffat bug 176 | Serial.printf("Fallback to filesystem %s\n", fsdlist.back().fsname ); 177 | fspath = path; 178 | }else{ 179 | fspath = path.substring( fileoffset( path.c_str()) ); 180 | } 181 | 182 | 183 | 184 | String contentType = getContentType(path); 185 | String pathWithGz = fspath + ".gz"; 186 | 187 | getFS(); 188 | 189 | if ( ff->exists(pathWithGz) || ff->exists(fspath)) { 190 | if ( ff->exists(pathWithGz)) { 191 | fspath += ".gz"; 192 | } 193 | 194 | if ( request->hasParam("download") )contentType = "application/octet-stream"; 195 | 196 | request->send( *ff, fspath, contentType ); 197 | 198 | loseFS(); 199 | Serial.printf("File has been streamed\n"); 200 | 201 | return; 202 | } 203 | loseFS(); 204 | request->send( 404, "text/plain", "FileNotFound"); 205 | } 206 | 207 | //-------------------------------------------------------------------------------- 208 | void handleRename( AsyncWebServerRequest *request ) { 209 | 210 | String oldname,newname; 211 | String sourcefspath, targetfspath; 212 | fs::FS *sourcefs=NULL, *targetfs=NULL; 213 | 214 | 215 | lister.close(); 216 | 217 | if ( request->hasParam("oldname") && request->hasParam("newname") ) { 218 | 219 | oldname = request->getParam("oldname")->value(); 220 | newname = request->getParam("newname")->value(); 221 | 222 | if ( oldname == newname){ 223 | request->send(400, "text/plain", "old and new filename are the same, no rename necessary" ); 224 | return; 225 | } 226 | 227 | getFS(); 228 | 229 | sourcefspath = oldname.substring( fileoffset( oldname.c_str()) ); 230 | targetfspath = newname.substring( fileoffset( newname.c_str()) ); 231 | 232 | sourcefs = fsfromfile( oldname.c_str() ); 233 | targetfs = fsfromfile( newname.c_str() ); 234 | 235 | if ( targetfs == NULL || sourcefs == NULL || sourcefs != targetfs){ 236 | loseFS(); 237 | request->send(500, "text/plain", "Filesystem name invalid or not the same" ); 238 | return; 239 | } 240 | 241 | if ( !targetfs->rename( sourcefspath, targetfspath ) ){ 242 | loseFS(); 243 | request->send(500, "text/plain", "Rename of " + oldname + " to " + newname + " failed "); 244 | return; 245 | } 246 | }else{ 247 | request->send(500, "text/plain", "Argument oldname and newname are missing"); 248 | return; 249 | } 250 | 251 | loseFS(); 252 | 253 | request->send(200, "text/plain", "Renamed " + oldname +" to " + newname); 254 | } 255 | 256 | //-------------------------------------------------------------------------------- 257 | void handleMultiDelete( AsyncWebServerRequest *request ) { 258 | 259 | String varname; 260 | int i,filecount; 261 | String path, fspath; 262 | fs::FS *ff; 263 | 264 | getFS(); 265 | lister.close(); 266 | 267 | if ( request->hasParam("filecount", true) ){ 268 | filecount = atoi( request->getParam("filecount",true)->value().c_str() ) ; 269 | Serial.printf("Deleting %d files\n", filecount); 270 | }else{ 271 | loseFS(); 272 | request->send(500, "text/plain", "Argument filecount is missing"); 273 | return; 274 | } 275 | 276 | for( i=0; i< filecount; ++i ){ 277 | varname = "file" + String(i); 278 | path = request->getParam( varname,true)->value(); 279 | 280 | ff = fsfromfile( path.c_str() ); 281 | if ( ff == NULL ){ 282 | Serial.println("No filesystem found in " + varname + "=" + path); 283 | loseFS(); 284 | request->send(400, "text/plain", "No mounted filesystem in path, full filename required starting with mounted filesystem, either /sd,/littlefs,/spiffs or /ffat"); 285 | return; 286 | } 287 | 288 | fspath = path.substring( fileoffset( path.c_str()) ); 289 | 290 | if (fspath == "/") { 291 | loseFS(); 292 | request->send(500, "text/plain", "/ is not a valid filename"); 293 | return; 294 | } 295 | 296 | if (!ff->exists(fspath)) { 297 | loseFS(); 298 | request->send(404, "text/plain", path + " Not Found " ); 299 | return; 300 | } 301 | 302 | deltree( ff, fspath.c_str() ); 303 | } 304 | 305 | loseFS(); 306 | request->send(200, "text/plain", "All files succesfully deleted"); 307 | 308 | } 309 | 310 | //-------------------------------------------------------------------------------- 311 | void handleFileDelete(AsyncWebServerRequest *request) { 312 | 313 | if ( request->params() == 0) { 314 | request->send(500, "text/plain", "BAD ARGS for delete"); 315 | return; 316 | } 317 | 318 | AsyncWebParameter* p = request->getParam(0); 319 | String path = p->value(); 320 | 321 | getFS(); 322 | lister.close(); 323 | 324 | path = urldecode( path); 325 | Serial.println("handleFileDelete: " + path); 326 | 327 | fs::FS *ff = fsfromfile( path.c_str() ); 328 | int returncode = 200; 329 | String fspath; 330 | 331 | if ( ff == NULL ){ 332 | Serial.printf("No filesystem found\n"); 333 | loseFS(); 334 | request->send(400, "text/plain", "No valid filesystem in path, full filename required starting with /sd,/littlefs,/spiffs or /ffat"); 335 | return; 336 | } 337 | 338 | fspath = path.substring( fileoffset( path.c_str()) ); 339 | 340 | 341 | if (fspath == "/") { 342 | loseFS(); 343 | request->send(500, "text/plain", "/ is not a valid filename"); 344 | return; 345 | } 346 | if (!ff->exists(fspath)) { 347 | loseFS(); 348 | request->send(404, "text/plain", "FileNotFound"); 349 | return; 350 | } 351 | 352 | deltree( ff, fspath.c_str() ); 353 | 354 | loseFS(); 355 | request->send(200, "text/plain", path + " succesfully deleted"); 356 | 357 | } 358 | 359 | //------------------------------------------------------------------------- 360 | 361 | void handleMkdir(AsyncWebServerRequest *request){ 362 | 363 | if ( request->params() == 0) { 364 | request->send(500, "text/plain", "BAD ARGS for mkdir"); 365 | return; 366 | } 367 | 368 | getFS(); 369 | lister.close(); 370 | 371 | String path = request->getParam("subdir")->value(); 372 | 373 | path = urldecode( path); 374 | Serial.println("handleMkdir: " + path); 375 | 376 | fs::FS *ff = fsfromfile( path.c_str() ); 377 | String fspath; 378 | 379 | if ( ff == NULL ){ 380 | loseFS(); 381 | Serial.printf("No filesystem found\n"); 382 | request->send(400, "text/plain", "No valid mounted filesystem in path, full filename required starting with /sd,/littlefs,/spiffs or /ffat"); 383 | return; 384 | } 385 | 386 | fspath = path.substring( fileoffset( path.c_str()) ); 387 | if (fspath == "/") { 388 | loseFS(); 389 | request->send(400, "text/plain", "/ is not a valid filename"); 390 | return; 391 | } 392 | 393 | makepath ( *ff, fspath ); 394 | if ( ! ff->mkdir( fspath ) ){ 395 | loseFS(); 396 | request->send(400, "text/plain", "failed to create subdirectory " + path ); 397 | return; 398 | } 399 | 400 | loseFS(); 401 | request->send(200, "text/plain", "Directory " + path + " succesfully created"); 402 | 403 | } 404 | //------------------------------------------------------------------------------------ 405 | void handleMove(AsyncWebServerRequest *request) { 406 | 407 | String varname; 408 | int i,filecount; 409 | String targetdir, sourcepath, targetpath; 410 | String sourcefspath, targetfspath; 411 | bool removesource, docopy; 412 | fs::FS *sourcefs=NULL, *targetfs=NULL; 413 | 414 | getFS(); 415 | lister.close(); 416 | 417 | if ( request->hasParam("filecount", true) && request->hasParam("targetdirectory",true) && request->hasParam("removesource",true) ){ 418 | 419 | filecount = atoi( request->getParam("filecount",true)->value().c_str() ) ; 420 | targetdir = request->getParam("targetdirectory",true)->value(); 421 | targetfs = fsfromfile( targetdir.c_str() ); 422 | removesource = (bool) atoi(request->getParam("removesource",true)->value().c_str()); 423 | if ( targetfs == NULL ) { 424 | loseFS(); 425 | request->send(500, "text/plain", "No such target directory, filesystem not found" ); 426 | return; 427 | } 428 | 429 | 430 | Serial.printf( "%s %d file%s to %s\n", removesource?"Moving":"Copying", filecount,(filecount == 1)?"":"s", targetdir.c_str()); 431 | }else{ 432 | loseFS(); 433 | return request->send(500, "text/plain", "Argument filecount, targetdiretory and removesource are missing"); 434 | } 435 | 436 | for( i=0; i< filecount; ++i ){ 437 | varname = "file" + String(i); 438 | 439 | sourcepath = request->getParam( varname,true)->value(); 440 | 441 | sourcefs = fsfromfile( sourcepath.c_str() ); 442 | 443 | if ( sourcefs == NULL ){ 444 | Serial.println("No filesystem found in " + varname + "=" + sourcepath); 445 | loseFS(); 446 | request->send(400, "text/plain", "No mounted filesystem in path, full filename required starting with mounted filesystem, either /sd,/littlefs,/spiffs or /ffat"); 447 | return; 448 | } 449 | 450 | maketargetpath( sourcepath, targetdir, targetpath ); 451 | targetfs = fsfromfile( targetpath.c_str() ); 452 | 453 | docopy = false; 454 | if ( sourcefs != targetfs || !removesource ) docopy = true; 455 | 456 | sourcefspath = sourcepath.substring( fileoffset( sourcepath.c_str()) ); 457 | targetfspath = targetpath.substring( fileoffset( targetpath.c_str()) ); 458 | 459 | if ( sourceintarget( sourcepath, targetpath ) ){ 460 | loseFS(); 461 | request->send(400, "text/plain", "Cannot pretzel source in target path"); 462 | return; 463 | } 464 | 465 | if ( docopy ){ 466 | int result; 467 | if ( result = copytree( sourcefs, sourcefspath, targetfs, targetfspath) ){ 468 | loseFS(); 469 | request->send(500, "text/plain", "Copy of " + sourcepath + " to " + targetpath + " failed " + FPSTR( cperror[ result ] ) ); 470 | return; 471 | } 472 | }else{ 473 | makepath ( *targetfs, targetfspath ); 474 | if ( !targetfs->rename( sourcefspath, targetfspath ) ){ 475 | loseFS(); 476 | request->send(500, "text/plain", "Move of " + sourcepath + " to " + targetpath + " failed "); 477 | return; 478 | } 479 | esp_task_wdt_reset(); 480 | } 481 | 482 | if ( docopy && removesource ){ 483 | deltree( sourcefs, sourcefspath.c_str() ); 484 | } 485 | 486 | } 487 | loseFS(); 488 | request->send(200, "text/plain", "All files succesfully moved"); 489 | 490 | } 491 | 492 | 493 | //---------------------------------------------------------------------------- 494 | void send_logo( AsyncWebServerRequest *request ){ 495 | if(!logo.open("/favicon.ico") ){ 496 | request->send(503, "text/plain", "Error while listing logo" ); 497 | }else{ 498 | Serial.printf("Streaming logo, size = %d\n", logo.size()); 499 | request->send( logo, "image/x-icon", logo.size() ); 500 | } 501 | } 502 | //---------------------------------------------------------------------------- 503 | void showUploadform(AsyncWebServerRequest *request) { 504 | 505 | if( ! uploadform.open() ){ 506 | request->send(503, "text/plain", "Error while listing uploadform" ); 507 | }else{ 508 | request->send( uploadform, "text/html", uploadform.size() ); 509 | } 510 | } 511 | 512 | //--------------------------------------------------------------------------- 513 | 514 | void handleFileUpload(AsyncWebServerRequest * request, String filename, size_t index, uint8_t *data, size_t len, bool final){ 515 | 516 | static File fsUploadFile; 517 | String fspath; 518 | static int bcount, bmultiplier; 519 | 520 | if(!index){ 521 | 522 | getFS(); 523 | 524 | Serial.printf("UploadStart: %s\n", filename.c_str()); 525 | lister.close(); 526 | 527 | String path = filename; 528 | if (!path.startsWith("/")) { 529 | path = "/" + path; 530 | } 531 | 532 | Serial.print("handleFileUpload Name: "); 533 | Serial.println(path); 534 | 535 | fs::FS *ff = fsfromfile( path.c_str() ); 536 | 537 | if ( ff == NULL ){ 538 | ff = fsdlist.back().f; 539 | //flash filesystems added last to fsdlist, due ffat bug 540 | path = fsdlist.back().fsmount + path; 541 | Serial.printf("Upload fallen back to filesystem %s\n", fsdlist.back().fsname ); 542 | } 543 | 544 | fspath = path.substring( fileoffset( path.c_str()) ); 545 | 546 | if (fspath == "/") { 547 | loseFS(); 548 | request->send(500, "text/plain", "/ is not a valid filename"); 549 | return; 550 | } 551 | 552 | Serial.printf( "Making all directories for %s on %s\n", fspath.c_str(), nameoffs( ff ) ); 553 | Serial.println( "Length of filename " + fspath + " " + String( fspath.length()) ); 554 | 555 | makepath ( *ff, fspath ); 556 | bcount=0; bmultiplier = 1; 557 | 558 | fsUploadFile = ff->open(fspath, "w"); 559 | if ( !fsUploadFile ){ 560 | loseFS(); 561 | Serial.println("Error opening file " + fspath + " " + String (strerror(errno)) ); 562 | request->send(400, "text/plain", String("Error opening ") + fspath + String(" ") + String ( strerror(errno) ) ); 563 | return; 564 | } 565 | } 566 | int olderrno = errno; 567 | if (fsUploadFile) { 568 | if ( fsUploadFile.write( data, len) < len ){ 569 | fsUploadFile.close(); 570 | if ( olderrno != errno ){ 571 | loseFS(); 572 | request->send(500, "text/plain", "Error during write : " + String ( strerror(errno) ) ); 573 | Serial.println("Error writing file " +fspath + " " + String (strerror(errno)) ); 574 | return; 575 | } 576 | } 577 | esp_task_wdt_reset(); 578 | bcount +=len; 579 | while ( bcount > (bmultiplier * 4096) ){ 580 | Serial.print("*"); 581 | if ( (bmultiplier%60) == 0) Serial.println(""); 582 | bmultiplier++; 583 | } 584 | 585 | } 586 | 587 | if ( final ){ 588 | if (fsUploadFile) { 589 | fsUploadFile.close(); 590 | } 591 | loseFS(); 592 | 593 | Serial.printf("Uploaded file of %u bytes\n", bcount); 594 | } 595 | 596 | } 597 | 598 | //--------------------------------------------------------------------- 599 | void send_json_status(AsyncWebServerRequest *request) 600 | { 601 | 602 | char uptime[32]; 603 | int sec = millis() / 1000; 604 | int upsec,upminute,uphr,updays; 605 | 606 | upminute = (sec / 60) % 60; 607 | uphr = (sec / (60*60)) % 24; 608 | updays = sec / (24*60*60); 609 | upsec = sec % 60; 610 | 611 | sprintf( uptime, "%d %02d:%02d:%02d", updays, uphr, upminute,upsec); 612 | 613 | String output = "{\r\n"; 614 | 615 | output += "\t\"Hostname\" : \""; 616 | output += esphostname; 617 | output += "\",\r\n"; 618 | 619 | output += "\t\"Compiletime\" : \""; 620 | output += compile_time; 621 | output += "\",\r\n"; 622 | 623 | String srcfile = String( sourcefile ); 624 | srcfile.replace("\\", "\\\\"); 625 | output += "\t\"Sourcefile\" : \""; 626 | output += srcfile; 627 | output += "\",\r\n"; 628 | 629 | output += "\t\"uptime\" : \""; 630 | output += uptime; 631 | output += "\",\r\n"; 632 | 633 | output += "\t\"uptime\" : \""; 634 | output += uptime; 635 | output += "\",\r\n"; 636 | 637 | output += "\t\"RAMsize\" : "; 638 | output += ESP.getHeapSize(); 639 | output += ",\r\n"; 640 | 641 | output += "\t\"RAMfree\" : "; 642 | output += ESP.getFreeHeap(); 643 | output += ",\r\n"; 644 | 645 | output += "\t\"Settings\" : "; 646 | 647 | char* stmp = (char *) ps_calloc( strlen( settings_format ) + 32, 1 ); 648 | sprintf( stmp,settings_format, 649 | settings.portland, 650 | settings.pauseseconds, 651 | settings.adapt_rotation, 652 | settings.portrait, 653 | settings.landscape); 654 | 655 | output += stmp; 656 | output += "\r\n"; 657 | 658 | output += "}" ; 659 | 660 | request->send(200, "application/json;charset=UTF-8", output); 661 | free( stmp); 662 | } 663 | 664 | //---------------------------------------------------------------------------- 665 | void handleRoot(AsyncWebServerRequest *request) { 666 | 667 | if( ! directory.open() ){ 668 | request->send(502, "txt/plain", "Error while sending directory.html" ); 669 | }else{ 670 | request->send( directory, "text/html", directory.size() ); 671 | } 672 | } 673 | 674 | //---------------------------------------------------------------------------- 675 | void handleFileList( AsyncWebServerRequest *request ) { 676 | 677 | 678 | if( ! lister.open() ) { 679 | request->send(500, "txt/plain", "Error while listing" ); 680 | }else{ 681 | request->send( lister, "text/html", lister.size() ); 682 | } 683 | } 684 | 685 | //--------------------------------------------------------------------------------------- 686 | 687 | void send_settings( AsyncWebServerRequest *request ) { 688 | 689 | if( ! settingsform.open() ){ 690 | request->send(503, "text/plain", "Error while opening settingsform" ); 691 | }else{ 692 | request->send( settingsform, "text/html", settingsform.size() ); 693 | } 694 | } 695 | //---------------------------------------------------------------------------- 696 | void handleSettings(AsyncWebServerRequest *request){ 697 | struct appconfig z = settings; 698 | AsyncWebParameter* p; 699 | 700 | if ( request->hasParam("portland", true) ){ 701 | int pl = atoi( request->getParam("portland",true)->value().c_str() ) ; 702 | if ( pl != 0 && pl != 1 ){ request->send(400, "txt/plain", "portland can only be 0 or 1" ); return;} 703 | z.portland = pl; 704 | } 705 | 706 | if ( request->hasParam("pauseseconds",true) ){ 707 | z.pauseseconds = atoi( request->getParam("adapt_rotation",true)->value().c_str() ) ; //p = request->getParam("pauseseconds" ); 708 | if ( z.pauseseconds < 0 ) {request->send(400, "txt/plain", "pauseseconds must be greater than 0" ); return;} 709 | } 710 | 711 | if ( request->hasParam("adapt_rotation",true) ){ 712 | int ar = atoi( request->getParam("adapt_rotation",true)->value().c_str() ) ; 713 | if ( ar != 0 && ar != 1 ) {request->send(400, "txt/plain", "adapt_rotation can only be 0 or 1" );return;} 714 | z.adapt_rotation = ar; 715 | } 716 | 717 | if ( request->hasParam("portrait",true) ){ 718 | z.portrait = atoi( request->getParam("portrait",true)->value().c_str() ) ; 719 | if ( z.portrait != 0 && z.portrait != 2 ) {request->send(400, "txt/plain", "portrait can only be 0 or 2" );return;} 720 | } 721 | 722 | if ( request->hasParam("landscape",true) ){ 723 | z.landscape = atoi( request->getParam("landscape",true)->value().c_str() ) ; 724 | if ( z.landscape != 1 && z.landscape != 3 ) {request->send(400, "txt/plain", "landscape can only be 1 or 3" );return;} 725 | } 726 | 727 | request->send(200, "txt/plain", "ok" ); 728 | settings = z; 729 | getFS(); 730 | write_config(); 731 | loseFS(); 732 | Serial.println("Written new settings"); 733 | } 734 | //-------------------------------------------------------------------------- 735 | 736 | void handleUpdate(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { 737 | 738 | uint32_t free_space = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; 739 | 740 | if (!index){ 741 | 742 | Serial.println("Update"); 743 | 744 | getFS(); 745 | //Update.runAsync(true); 746 | //content_len = request->contentLength(); 747 | // if filename includes spiffs, update the spiffs partition 748 | //int cmd = (filename.indexOf("spiffs") > -1) ? U_PART : U_FLASH; 749 | if (!Update.begin( free_space )) { 750 | Update.printError(Serial); 751 | } 752 | } 753 | 754 | if (Update.write(data, len) != len) { 755 | Update.printError(Serial); 756 | } 757 | esp_task_wdt_reset(); 758 | 759 | if (final) { 760 | loseFS(); 761 | AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots"); 762 | response->addHeader("Refresh", "20"); 763 | response->addHeader("Location", "/"); 764 | request->send(response); 765 | if (!Update.end(true)){ 766 | Update.printError(Serial); 767 | } else { 768 | Serial.println("\nUpdate complete"); 769 | Serial.flush(); 770 | delay(100); 771 | ESP.restart(); 772 | } 773 | } 774 | } 775 | //--------------------------------------------------------------------------------------- 776 | void printProgress(size_t progress, size_t size) { 777 | size_t percent = (progress*1000)/size; 778 | if ( (percent%100) == 0 )Serial.printf(" %u%%\r", percent/10); 779 | } 780 | 781 | //--------------------------------------------------------------------------------------- 782 | void FSXServer(void *param){ 783 | 784 | fsxserver.on("/", handleRoot); 785 | 786 | fsxserver.on("/list", HTTP_GET, handleFileList); 787 | fsxserver.on("/move", HTTP_POST, handleMove); 788 | fsxserver.on("/rename", HTTP_GET, handleRename); 789 | 790 | fsxserver.on("/mkdir", HTTP_GET, handleMkdir); 791 | fsxserver.on("/delete", HTTP_GET, handleFileDelete); 792 | fsxserver.on("/delete", HTTP_POST, handleMultiDelete); 793 | 794 | fsxserver.on("/upload", HTTP_GET, showUploadform); 795 | fsxserver.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ request->send(200, "text/html", uploadpage); }, handleFileUpload); 796 | 797 | fsxserver.on("/settings", HTTP_GET, send_settings); 798 | fsxserver.on("/settings", HTTP_POST, handleSettings); 799 | 800 | fsxserver.on("/status", HTTP_GET, send_json_status ); 801 | fsxserver.on("/favicon.ico", send_logo ); 802 | 803 | fsxserver.onNotFound( handleFileRead ); 804 | 805 | fsxserver.on("/reset", HTTP_GET, []( AsyncWebServerRequest *request ) { 806 | request->send(200, "text/plain", "Doej!"); 807 | delay(20); 808 | ESP.restart(); 809 | }); 810 | 811 | fsxserver.on("/update", HTTP_GET, []( AsyncWebServerRequest *request ) { 812 | request->send( 200, "text/html", updateform ); 813 | }); 814 | fsxserver.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ request->send( 200, "text/html",updateform); }, handleUpdate); 815 | 816 | fsxserver.begin(); 817 | Serial.printf( "sd mmc totalbytes: %llu, used bytes %llu\n", SD_MMC.totalBytes(), SD_MMC.usedBytes() ); 818 | Update.onProgress(printProgress); 819 | 820 | while(1){ 821 | // fsxserver.handleClient(); 822 | delay(100); 823 | } 824 | 825 | } 826 | 827 | //---------------------------------------------------------------------------- 828 | void stopFSXServer(){ 829 | 830 | fsxserver.end(); 831 | vTaskDelete( FSXServerTask ); 832 | 833 | return; 834 | } 835 | //---------------------------------------------------------------------------- 836 | 837 | void startFSXServer(){ 838 | 839 | xTaskCreatePinnedToCore( 840 | FSXServer, // Task to handle special functions. 841 | "FSX Webserver", // name of task. 842 | 1024*8, // Stack size of task 843 | NULL, // parameter of the task 844 | 4, // priority of the task 845 | &FSXServerTask, // Task handle to keep track of created task 846 | 0 ); //core to run it on 847 | 848 | } 849 | -------------------------------------------------------------------------------- /data/directory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Reinagalerij 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 306 | 307 | 308 | 309 | 310 |
311 | 312 |
313 | 314 | 317 | 318 |
319 | 320 |
321 | 322 |
323 |
324 | 325 |
326 | 327 | 328 | 329 |
330 | 331 | 332 | 335 | 336 | 337 | 338 |
339 | Made thanks to the Treejs javascript at Github. 340 |
341 |
342 | 343 | 1209 | 1210 | 1211 | 1212 | --------------------------------------------------------------------------------