/* * Webserver demo using IFS * */ #include #include #include #include #include #include #include #include #include // If you want, you can define WiFi settings globally in Eclipse Environment Variables #ifndef WIFI_SSID #define WIFI_SSID "PleaseEnterSSID" // Put your SSID and password here #define WIFI_PWD "PleaseEnterPass" #endif #ifdef ENABLE_SDCARD #include #include // Chip selects independent of SPI controller in use #ifdef ARCH_ESP32 #define PIN_CARD_CS 21 #else // Esp8266 cannot use GPIO15 as this affects boot mode #define PIN_CARD_CS 5 #endif #define SPI_FREQ_LIMIT 0 //2000000 #endif #ifdef ENABLE_USB_STORAGE #include #include USB::MSC::HostDevice usbStorage; #endif namespace { #ifdef ENABLE_FLASHSTRING_IMAGE IMPORT_FSTR(fwfsImage, PROJECT_DIR "/out/fwfs1.bin") #endif IMPORT_FSTR(listing_html, PROJECT_DIR "/resource/listing.html") IMPORT_FSTR(listing_txt, PROJECT_DIR "/resource/listing.txt") IMPORT_FSTR(listing_json, PROJECT_DIR "/resource/listing.json") HttpServer server; FtpServer ftp; int requestCount; Profiling::TaskStat taskStat(Serial); SimpleTimer statTimer; /* * Handle any custom fields here */ String getValue(const char* name) { if(FS("webpage") == name) { return "https://github.com/SmingHub/Sming"; } if(FS("request-count") == name) { return String(requestCount); // Doesn't require escaping } return nullptr; } void onFile(HttpRequest& request, HttpResponse& response) { ++requestCount; String file = request.uri.getRelativePath(); String fmt = request.uri.getQueryParameter("format"); if(dirExist(file)) { if(fmt.equalsIgnoreCase("archive")) { debug_i("Sending streaming archive"); IFS::FileSystem::NameInfo fsinfo; fileGetSystemInfo(fsinfo); ArchiveStream::VolumeInfo volumeInfo; volumeInfo.name = F("Backup of '") + fsinfo.name + "'"; if(file.length() != 0) { volumeInfo.name += F("; root = '") + file + "'"; } auto archive = new ArchiveStream(volumeInfo, file, ArchiveStream::Flag::IncludeMountPoints); response.sendDataStream(archive, archive->getMimeType()); return; } auto dir = new Directory; IFS::DirectoryTemplate* tmpl; if(fmt.equalsIgnoreCase("json")) { auto source = new FlashMemoryStream(listing_json); tmpl = new IFS::JsonDirectoryTemplate(source, dir); } else if(fmt.equalsIgnoreCase("text")) { auto source = new FlashMemoryStream(listing_txt); tmpl = new IFS::DirectoryTemplate(source, dir); } else { auto source = new FlashMemoryStream(listing_html); tmpl = new IFS::HtmlDirectoryTemplate(source, dir); } tmpl->onGetValue(getValue); dir->open(file); tmpl->gotoSection(0); response.sendDataStream(tmpl, tmpl->getMimeType()); return; } if(fmt) { debug_e("'format' option only supported for directories"); response.code = HTTP_STATUS_BAD_REQUEST; return; } // response.setCache(86400, true); // It's important to use cache for better performance. auto stream = new FileStream; if(!stream->open(file)) { int err = stream->getLastError(); response.code = (err == IFS::Error::NotFound) ? HTTP_STATUS_NOT_FOUND : HTTP_STATUS_INTERNAL_SERVER_ERROR; delete stream; return; } FileStat stat; stream->stat(stat); if(stat.compression.type == IFS::Compression::Type::GZip) { response.headers[HTTP_HEADER_CONTENT_ENCODING] = F("gzip"); } else if(stat.compression.type != IFS::Compression::Type::None) { debug_e("Unsupported compression type: %u", stat.compression.type); } auto mimeType = ContentType::fromFullFileName(file, MIME_TEXT); response.sendDataStream(stream, mimeType); } void startWebServer() { server.listen(80); server.paths.setDefault(onFile); Serial.println("\r\n=== WEB SERVER STARTED ==="); Serial.println(WifiStation.getIP()); Serial.println("==============================\r\n"); } void gotIP(IpAddress ip, IpAddress netmask, IpAddress gateway) { startWebServer(); } bool initFileSystem() { fileFreeFileSystem(); auto initialFreeheap = system_get_free_heap_size(); debug_i("Initial freeheap = %u", initialFreeheap); #ifdef ENABLE_FLASHSTRING_IMAGE // Create a partition wrapping some flashstring data auto part = Storage::progMem.editablePartitions().add(F("fwfsMem"), fwfsImage, Storage::Partition::SubType::Data::fwfs); #else auto part = Storage::findDefaultPartition(Storage::Partition::SubType::Data::fwfs); #endif // Read-only auto fs = IFS::createFirmwareFilesystem(part); if(fs == nullptr) { debug_e("Failed to created filesystem object"); return false; } auto mount = [&](IFS::FileSystem* fs) { int res = fs->mount(); debug_i("heap used: %u, mount() returned %d (%s)", initialFreeheap - system_get_free_heap_size(), res, fs->getErrorString(res).c_str()); return res == FS_OK; }; if(!mount(fs)) { delete fs; return false; } // Make this the default filesystem fileSetFileSystem(fs); // Let's mount an LFS volume as well initialFreeheap = system_get_free_heap_size(); part = Storage::findDefaultPartition(Storage::Partition::SubType::Data::littlefs); auto lfs = IFS::createLfsFilesystem(part); if(lfs == nullptr) { debug_e("Failed to create LFS filesystem"); } else if(mount(lfs)) { // Place the root of this volume at index #0 (the corresponding directory is given in `fwimage.fwfs`) fs->setVolume(0, lfs); } else { delete lfs; } // And we'll mount a SPIFFS volume too initialFreeheap = system_get_free_heap_size(); part = Storage::findDefaultPartition(Storage::Partition::SubType::Data::spiffs); auto spiffs = IFS::createSpiffsFilesystem(part); if(spiffs == nullptr) { debug_e("Failed to create SPIFFS filesystem"); } else if(mount(spiffs)) { // Place the root of this volume at index #1 fs->setVolume(1, spiffs); } else { delete spiffs; } #ifdef ENABLE_SDCARD auto card = new Storage::SD::Card("card1", SPI); Storage::registerDevice(card); // Buffering allows byte read/write card->allocateBuffers(2); if(card->begin(PIN_CARD_CS, SPI_FREQ_LIMIT)) { Serial << "CSD" << endl << card->csd << endl; Serial << "CID" << endl << card->cid; auto part = *card->partitions().begin(); auto fatfs = IFS::createFatFilesystem(part); if(fatfs != nullptr) { if(fatfs->mount() == FS_OK) { fs->setVolume(2, fatfs); } else { delete fatfs; delete card; } } } else { delete card; } #endif #ifdef ENABLE_USB_STORAGE USB::begin(true); USB::MSC::onMount([](auto inst) { usbStorage.begin(inst); usbStorage.enumerate([](auto& unit, const USB::MSC::Inquiry& inquiry) { #define OUT(name, value) Serial << String(name).padLeft(30) << ": " << value << endl; #define OUTR(name) OUT(#name, inquiry.resp.name) Serial << _F("USB device '") << unit.getName() << _F("' mounted") << endl; OUT("Vendor ID", inquiry.vendorId()) OUT("Product ID", inquiry.productId()) OUT("Product Revision", inquiry.productRev()) OUTR(peripheral_device_type) OUTR(peripheral_qualifier) OUTR(is_removable) OUTR(version) OUTR(response_data_format) OUTR(hierarchical_support) OUTR(normal_aca) OUTR(additional_length) OUTR(protect) OUTR(third_party_copy) OUTR(target_port_group_support) OUTR(access_control_coordinator) OUTR(scc_support) OUTR(addr16) OUTR(multi_port) OUTR(enclosure_service) OUTR(cmd_que) OUTR(sync) OUTR(wbus16) #undef OUTR #undef OUT Storage::registerDevice(&unit); unit.allocateBuffers(16); for(auto part : unit.partitions()) { Serial << part << endl; } auto part = Storage::findDefaultPartition(Storage::Partition::SubType::Data::fat); auto fatfs = IFS::createFatFilesystem(part); if(fatfs && fatfs->mount() == FS_OK) { getFileSystem()->setVolume(3, fatfs); Serial << F("FAT partition mounted") << endl; } else { Serial << F("FAT mount failed") << endl; delete fatfs; } return false; // Ignore other LUNs }); return &usbStorage; }); USB::MSC::onUnmount([](USB::MSC::HostDevice& dev) { if(dev == usbStorage) { getFileSystem()->setVolume(3, nullptr); Serial << _F("USB '") << dev.getName() << _F("' unmounted") << endl; } }); #endif debug_i("File system initialised"); return true; } void printDirectory(const char* path) { auto printStream = [](IDataSourceStream& stream) { // Use an intermediate memory stream so debug information doesn't get mixed into output // MemoryDataStream mem; // mem.copyFrom(&stream); // Serial.copyFrom(&mem); Serial.copyFrom(&stream); }; { auto dir = new Directory; if(!dir->open(path)) { debug_e("Open '%s' failed: %s", path, dir->getLastErrorString().c_str()); delete dir; return; } auto source = new FlashMemoryStream(listing_txt); IFS::DirectoryTemplate tmpl(source, dir); printStream(tmpl); } { auto dir = new Directory; if(!dir->open(path)) { debug_e("Open '%s' failed: %s", path, dir->getLastErrorString().c_str()); delete dir; return; } auto source = new FlashMemoryStream(listing_json); IFS::JsonDirectoryTemplate tmpl(source, dir); printStream(tmpl); } } void copySomeFiles() { auto part = *Storage::findPartition(Storage::Partition::SubType::Data::fwfs); if(!part) { return; } auto fs = IFS::createFirmwareFilesystem(part); if(fs == nullptr) { return; } fs->mount(); IFS::Directory dir(fs); if(!dir.open()) { return; } while(dir.next()) { auto& stat = dir.stat(); if(stat.isDir()) { continue; } IFS::File src(fs); auto filename = stat.name.c_str(); if(src.open(filename)) { File dst; if(dst.open(filename, File::CreateNewAlways | File::WriteOnly)) { auto len = src.readContent([&dst](const char* buffer, size_t size) -> int { return dst.write(buffer, size); }); (void)len; debug_w("Wrote '%s', %d bytes", filename, len); // Copy metadata auto callback = [&](IFS::AttributeEnum& e) -> bool { if(!dst.setAttribute(e.tag, e.buffer, e.size)) { m_printf(_F("setAttribute(%s) failed: %s"), toString(e.tag).c_str(), dst.getLastErrorString().c_str()); } return true; }; char buffer[1024]; src.enumAttributes(callback, buffer, sizeof(buffer)); } else { debug_w("%s", dst.getLastErrorString().c_str()); } } } } bool isVolumeEmpty() { Directory dir; dir.open(); return !dir.next(); } void listAttributes() { Directory dir; if(dir.open()) { while(dir.next()) { auto filename = dir.stat().name.c_str(); File f; if(!f.open(filename)) { continue; } m_printf("%s:\r\n", filename); auto callback = [](IFS::AttributeEnum& e) -> bool { m_printf(" attr 0x%04x %s, %u bytes\r\n", unsigned(e.tag), toString(e.tag).c_str(), e.attrsize); m_printHex(" ATTR", e.buffer, e.size); return true; }; char buffer[64]; int res = f.enumAttributes(callback, buffer, sizeof(buffer)); debug_i("res: %d", res); } } } void fstest() { // Various ways to initialise a filesystem /* * Mount regular SPIFFS volume */ // spiffs_mount(); /* * Mount LittleFS volume */ // lfs_mount(); /* * Mount default Firmware Filesystem */ // fwfs_mount(); /* * Mount default FWFS/SPIFFS as hybrid */ // hyfs_mount(); /* * Explore some alternative methods of mounting filesystems */ initFileSystem(); if(isVolumeEmpty()) { Serial.print(F("Volume appears to be empty, writing some files...\r\n")); copySomeFiles(); } printDirectory(nullptr); listAttributes(); } } // namespace void init() { Serial.begin(COM_SPEED_SERIAL); Serial.systemDebugOutput(true); debug_i("\n\n********************************************************\n" "Hello\n"); // Delay at startup so terminal gets time to start auto timer = new AutoDeleteTimer; timer->initializeMs<1000>(fstest); timer->startOnce(); WifiStation.enable(true); WifiStation.config(WIFI_SSID, WIFI_PWD); WifiAccessPoint.enable(false); WifiEvents.onStationGotIP(gotIP); statTimer.initializeMs<2000>([]() { taskStat.update(); }); statTimer.start(); }