#include #include #include #include #include WiFiMulti WiFiMulti; WebServer server(80); //Starting a server on port 80 //Home Dynamic IoT Communication Protocol Headers and Information const String uuid = "d9ed122a-b359-4564-8bf1-6cd27ec517b2"; //Please change this UUIDv4 if you have more than one identical setup in the same subnet const String driver="3dprcu.ext.hd.imuslab"; const String devName="3D Printer Remote Control Unit"; //WebServer settings const String webroot = "/web"; //System Settings bool forceCORS = true; //Some versions of WebServer.h do not include Access Control Allow Origin * by default. Set this to true to send it out manually void setup(){ Serial.begin(115200); delay(10); Serial.println(""); Serial.println("[info] ESP32 3D Printer Remote Controller Unit"); Serial.println("[info] Open source prototype for the aCloud 3D Printer Control System."); // Add more WiFi AP for backups WiFiMulti.addAP("Toby Room Automation", "homedynamicsystem"); //WiFiMulti.addAP("Backup-SSID", "password"); if (initSDcard() == false){ //Init SD card failed. Terminate the startup process. return; } Serial.print("[info] Waiting for WiFi... "); while(WiFiMulti.run() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(""); Serial.println("[OK] WiFi connected"); Serial.print("[info] IP address: "); Serial.println(WiFi.localIP()); delay(500); //Define handler for index server.on("/", handle_OnConnect); //Define Web Server handler for Home Dynamic Protocol server.on("/uuid", handle_uuid); server.on("/info", handle_info); server.on("/status", handle_status); //Define handler for file operations server.on("/list", HTTP_GET, printDirectory); //Usage: /list?dir=/{dirname} server.on("/upload", HTTP_POST, []() { sendCORS(); server.send(200, "text/plain", ""); }, handleFileUpload); //Define handler for NOT FOUND exception server.onNotFound(handle_NotFound); server.begin(); Serial.println("[info] RESTFUL API server started"); Serial.println("[OK] System Ready!"); } void loop() { server.handleClient(); } //Home Dynamic Communication Protocol related response handler void handle_uuid(){ if (forceCORS){ server.sendHeader("Access-Control-Allow-Origin", "*"); } server.send(200, "text/plain", uuid); } void handle_info(){ if (forceCORS){ server.sendHeader("Access-Control-Allow-Origin", "*"); } server.send(200, "text/plain", devName + "_" + driver); } void handle_status(){ //Report printing progress here. Work in progress } void handle_OnConnect(){ server.sendHeader("Location", "/web/index.html",true); server.send(302, "text/plane",""); } File uploadFile; void handleFileUpload() { if (server.uri() != "/upload") { return; } HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { if (SD_MMC.exists((char *)upload.filename.c_str())) { SD_MMC.remove((char *)upload.filename.c_str()); } uploadFile = SD_MMC.open(upload.filename.c_str(), FILE_WRITE); Serial.print("[info] Upload: START, filename: "); Serial.println(upload.filename); } else if (upload.status == UPLOAD_FILE_WRITE) { if (uploadFile) { uploadFile.write(upload.buf, upload.currentSize); } Serial.print("[info] Upload: WRITE, Bytes: "); Serial.println(upload.currentSize); } else if (upload.status == UPLOAD_FILE_END) { if (uploadFile) { uploadFile.close(); } Serial.print("[info] Upload: END, Size: "); Serial.println(upload.totalSize); } } void handleHTMLDelivery(String path){ if (!loadFromSdCard(path)){ //This file do not exists or it is empty. Deliver 404 page instead. sendCORS(); server.send(404, "text/plain", "Page not found"); } } void handle_NotFound(){ String path = server.uri(); int index = path.indexOf("/web"); int deleteIndicator = path.indexOf("/delete"); if (index >= 0 && deleteIndicator == -1) { handleHTMLDelivery(path); }else if (deleteIndicator >= 0 ){ handleDeleteRequest(path); }else{ sendCORS(); server.send(404, "text/plain", "Page not found"); } } bool initSDcard(){ Serial.println("[info] Trying to initiate SD card file system."); if(!SD_MMC.begin()){ Serial.println("[error] Card Mount Failed"); return false; } uint8_t cardType = SD_MMC.cardType(); if(cardType == CARD_NONE){ Serial.println("[error] No SD_MMC card attached"); return false; } Serial.print("[info] SD_MMC Card Type: "); if(cardType == CARD_MMC){ Serial.println("MMC"); } else if(cardType == CARD_SD){ Serial.println("SDSC"); } else if(cardType == CARD_SDHC){ Serial.println("SDHC"); } else { Serial.println("UNKNOWN"); } uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024); Serial.printf("[OK] SD_MMC Card Size: %lluMB\n", cardSize); return true; } void printDirectory() { if (!server.hasArg("dir")) { server.send(500, "text/plain", "Not directory."); return; } String path = server.arg("dir"); if (path != "/" && !SD_MMC.exists((char *)path.c_str())) { server.send(500, "text/plain", "Bad path"); return; } File dir = SD_MMC.open((char *)path.c_str()); path = String(); if (!dir.isDirectory()) { dir.close(); server.send(500, "text/plain", "Not directory"); return; } dir.rewindDirectory(); if (forceCORS){ server.sendHeader("Access-Control-Allow-Origin", "*"); } server.setContentLength(CONTENT_LENGTH_UNKNOWN); server.send(200, "application/json", ""); WiFiClient client = server.client(); server.sendContent("["); for (int cnt = 0; true; ++cnt) { File entry = dir.openNextFile(); if (!entry) { break; } String output; if (cnt > 0) { output = ','; } output += "{\"type\":\""; output += (entry.isDirectory()) ? "dir" : "file"; output += "\",\"name\":\""; output += entry.name(); output += "\""; output += "}"; server.sendContent(output); entry.close(); } server.sendContent("]"); dir.close(); } //Test read and write speed using test.txt file void testFileIO(fs::FS &fs, const char * path){ File file = fs.open(path); static uint8_t buf[512]; size_t len = 0; uint32_t start = millis(); uint32_t end = start; if(file){ len = file.size(); size_t flen = len; start = millis(); while(len){ size_t toRead = len; if(toRead > 512){ toRead = 512; } file.read(buf, toRead); len -= toRead; } end = millis() - start; Serial.printf("%u bytes read for %u ms\n", flen, end); file.close(); } else { Serial.println("Failed to open file for reading"); } file = fs.open(path, FILE_WRITE); if(!file){ Serial.println("Failed to open file for writing"); return; } size_t i; start = millis(); for(i=0; i<2048; i++){ file.write(buf, 512); } end = millis() - start; Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); file.close(); } bool loadFromSdCard(String path){ String dataType = "text/plain"; if(path.endsWith("/")) path += "index.htm"; if(path.endsWith(".src")) path = path.substring(0, path.lastIndexOf(".")); else if(path.endsWith(".htm")) dataType = "text/html"; else if(path.endsWith(".html")) dataType = "text/html"; else if(path.endsWith(".css")) dataType = "text/css"; else if(path.endsWith(".js")) dataType = "application/javascript"; else if(path.endsWith(".png")) dataType = "image/png"; else if(path.endsWith(".gif")) dataType = "image/gif"; else if(path.endsWith(".jpg")) dataType = "image/jpeg"; else if(path.endsWith(".ico")) dataType = "image/x-icon"; else if(path.endsWith(".svg")) dataType = "image/svg+xml"; else if(path.endsWith(".xml")) dataType = "text/xml"; else if(path.endsWith(".pdf")) dataType = "application/pdf"; else if(path.endsWith(".zip")) dataType = "application/zip"; else if(path.endsWith(".mp3")) dataType = "audio/mpeg"; else if(path.endsWith(".mp4")) dataType = "video/mp4"; else if(path.endsWith(".otf")) dataType = "font/otf"; else if(path.endsWith(".ttf")) dataType = "font/ttf"; File dataFile = SD_MMC.open(path.c_str()); if(dataFile.isDirectory()){ path += "/index.html"; dataType = "text/html"; dataFile = SD_MMC.open(path.c_str()); } if (!dataFile) return false; if (server.hasArg("download")) dataType = "application/octet-stream"; if (server.streamFile(dataFile, dataType) != dataFile.size()) { Serial.println("[error] Sent less data than expected!"); } dataFile.close(); return true; } void deleteRecursive(String path) { File file = SD_MMC.open((char *)path.c_str()); if (!file.isDirectory()) { file.close(); SD_MMC.remove((char *)path.c_str()); return; } file.rewindDirectory(); while (true) { File entry = file.openNextFile(); if (!entry) { break; } String entryPath = path + "/" + entry.name(); if (entry.isDirectory()) { entry.close(); deleteRecursive(entryPath); } else { entry.close(); SD_MMC.remove((char *)entryPath.c_str()); } yield(); } SD_MMC.rmdir((char *)path.c_str()); file.close(); } void handleDeleteRequest(String fullPath) { int index = fullPath.indexOf(","); String path = fullPath.substring(index + 1, fullPath.length()); Serial.println("[info] Request delete path: " + path); if (path == "/" || !SD_MMC.exists((char *)path.c_str())) { sendCORS(); server.send(500, "text/plain", "Bad path"); return; } deleteRecursive(path); sendCORS(); server.send(200, "text/plain", "DONE"); } void sendCORS(){ if (forceCORS){ server.sendHeader("Access-Control-Allow-Origin", "*"); server.sendHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); server.sendHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); } }