diff --git a/cores/esp8266/FS.cpp b/cores/esp8266/FS.cpp
index d7baa43069..fa841c64fa 100644
--- a/cores/esp8266/FS.cpp
+++ b/cores/esp8266/FS.cpp
@@ -180,6 +180,19 @@ String File::readString()
return ret;
}
+time_t File::getLastWrite() {
+ if (!_p)
+ return 0;
+
+ return _p->getLastWrite();
+}
+
+void File::setTimeCallback(time_t (*cb)(void)) {
+ if (!_p)
+ return;
+ _p->setTimeCallback(cb);
+}
+
File Dir::openFile(const char* mode) {
if (!_impl) {
return File();
@@ -192,7 +205,9 @@ File Dir::openFile(const char* mode) {
return File();
}
- return File(_impl->openFile(om, am), _baseFS);
+ File f(_impl->openFile(om, am), _baseFS);
+ f.setTimeCallback(timeCallback);
+ return f;
}
String Dir::fileName() {
@@ -203,6 +218,12 @@ String Dir::fileName() {
return _impl->fileName();
}
+time_t Dir::fileTime() {
+ if (!_impl)
+ return 0;
+ return _impl->fileTime();
+}
+
size_t Dir::fileSize() {
if (!_impl) {
return 0;
@@ -241,6 +262,20 @@ bool Dir::rewind() {
return _impl->rewind();
}
+time_t Dir::getLastWrite() {
+ if (!_impl)
+ return 0;
+
+ return _impl->getLastWrite();
+}
+
+void Dir::setTimeCallback(time_t (*cb)(void)) {
+ if (!_impl)
+ return;
+ _impl->setTimeCallback(cb);
+}
+
+
bool FS::setConfig(const FSConfig &cfg) {
if (!_impl) {
return false;
@@ -315,7 +350,9 @@ File FS::open(const char* path, const char* mode) {
DEBUGV("FS::open: invalid mode `%s`\r\n", mode);
return File();
}
- return File(_impl->open(path, om, am), this);
+ File f(_impl->open(path, om, am), this);
+ f.setTimeCallback(timeCallback);
+ return f;
}
bool FS::exists(const char* path) {
@@ -334,7 +371,9 @@ Dir FS::openDir(const char* path) {
return Dir();
}
DirImplPtr p = _impl->openDir(path);
- return Dir(p, this);
+ Dir d(p, this);
+ d.setTimeCallback(timeCallback);
+ return d;
}
Dir FS::openDir(const String& path) {
@@ -385,6 +424,11 @@ bool FS::rename(const String& pathFrom, const String& pathTo) {
return rename(pathFrom.c_str(), pathTo.c_str());
}
+void FS::setTimeCallback(time_t (*cb)(void)) {
+ if (!_impl)
+ return;
+ _impl->setTimeCallback(cb);
+}
static bool sflags(const char* mode, OpenMode& om, AccessMode& am) {
diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h
index 669287876d..39fd7c0f06 100644
--- a/cores/esp8266/FS.h
+++ b/cores/esp8266/FS.h
@@ -110,12 +110,16 @@ class File : public Stream
String readString() override;
+ time_t getLastWrite();
+ void setTimeCallback(time_t (*cb)(void));
+
protected:
FileImplPtr _p;
// Arduino SD class emulation
std::shared_ptr
_fakeDir;
FS *_baseFS;
+ time_t (*timeCallback)(void) = nullptr;
};
class Dir {
@@ -126,15 +130,21 @@ class Dir {
String fileName();
size_t fileSize();
+ time_t fileTime();
bool isFile() const;
bool isDirectory() const;
bool next();
bool rewind();
+ time_t getLastWrite();
+ void setTimeCallback(time_t (*cb)(void));
+
protected:
DirImplPtr _impl;
FS *_baseFS;
+ time_t (*timeCallback)(void) = nullptr;
+
};
// Backwards compatible, <4GB filesystem usage
@@ -161,12 +171,10 @@ struct FSInfo64 {
class FSConfig
{
public:
- FSConfig(bool autoFormat = true) {
- _type = FSConfig::fsid::FSId;
- _autoFormat = autoFormat;
- }
+ static constexpr uint32_t FSId = 0x00000000;
+
+ FSConfig(uint32_t type = FSId, bool autoFormat = true) : _type(type), _autoFormat(autoFormat) { }
- enum fsid { FSId = 0x00000000 };
FSConfig setAutoFormat(bool val = true) {
_autoFormat = val;
return *this;
@@ -179,17 +187,17 @@ class FSConfig
class SPIFFSConfig : public FSConfig
{
public:
- SPIFFSConfig(bool autoFormat = true) {
- _type = SPIFFSConfig::fsid::FSId;
- _autoFormat = autoFormat;
- }
- enum fsid { FSId = 0x53504946 };
+ static constexpr uint32_t FSId = 0x53504946;
+ SPIFFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
+
+ // Inherit _type and _autoFormat
+ // nothing yet, enableTime TBD when SPIFFS has metadate
};
class FS
{
public:
- FS(FSImplPtr impl) : _impl(impl) { }
+ FS(FSImplPtr impl) : _impl(impl) { timeCallback = _defaultTimeCB; }
bool setConfig(const FSConfig &cfg);
@@ -225,10 +233,14 @@ class FS
bool gc();
bool check();
+ void setTimeCallback(time_t (*cb)(void));
+
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
protected:
FSImplPtr _impl;
FSImplPtr getImpl() { return _impl; }
+ time_t (*timeCallback)(void);
+ static time_t _defaultTimeCB(void) { return time(NULL); }
};
} // namespace fs
diff --git a/cores/esp8266/FSImpl.h b/cores/esp8266/FSImpl.h
index b7cf4a7a65..9715c65a8b 100644
--- a/cores/esp8266/FSImpl.h
+++ b/cores/esp8266/FSImpl.h
@@ -41,6 +41,19 @@ class FileImpl {
virtual const char* fullName() const = 0;
virtual bool isFile() const = 0;
virtual bool isDirectory() const = 0;
+
+ // Filesystems *may* support a timestamp per-file, so allow the user to override with
+ // their own callback for *this specific* file (as opposed to the FSImpl call of the
+ // same name. The default implementation simply returns time(&null)
+ virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
+
+ // Return the last written time for a file. Undefined when called on a writable file
+ // as the FS is allowed to return either the time of the last write() operation or the
+ // time present in the filesystem metadata (often the last time the file was closed)
+ virtual time_t getLastWrite() { return 0; } // Default is to not support timestamps
+
+protected:
+ time_t (*timeCallback)(void) = nullptr;
};
enum OpenMode {
@@ -62,10 +75,24 @@ class DirImpl {
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
virtual const char* fileName() = 0;
virtual size_t fileSize() = 0;
+ virtual time_t fileTime() { return 0; } // By default, FS doesn't report file times
virtual bool isFile() const = 0;
virtual bool isDirectory() const = 0;
virtual bool next() = 0;
virtual bool rewind() = 0;
+
+ // Filesystems *may* support a timestamp per-file, so allow the user to override with
+ // their own callback for *this specific* file (as opposed to the FSImpl call of the
+ // same name. The default implementation simply returns time(&null)
+ virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
+
+ // Return the last written time for a file. Undefined when called on a writable file
+ // as the FS is allowed to return either the time of the last write() operation or the
+ // time present in the filesystem metadata (often the last time the file was closed)
+ virtual time_t getLastWrite() { return 0; } // Default is to not support timestamps
+
+protected:
+ time_t (*timeCallback)(void) = nullptr;
};
class FSImpl {
@@ -86,6 +113,14 @@ class FSImpl {
virtual bool rmdir(const char* path) = 0;
virtual bool gc() { return true; } // May not be implemented in all file systems.
virtual bool check() { return true; } // May not be implemented in all file systems.
+
+ // Filesystems *may* support a timestamp per-file, so allow the user to override with
+ // their own callback for all files on this FS. The default implementation simply
+ // returns the present time as reported by time(&null)
+ virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
+
+protected:
+ time_t (*timeCallback)(void) = nullptr;
};
} // namespace fs
diff --git a/cores/esp8266/spiffs_api.h b/cores/esp8266/spiffs_api.h
index c19dbc2c4e..44f34cd1d4 100644
--- a/cores/esp8266/spiffs_api.h
+++ b/cores/esp8266/spiffs_api.h
@@ -162,7 +162,7 @@ class SPIFFSImpl : public FSImpl
bool setConfig(const FSConfig &cfg) override
{
- if ((cfg._type != SPIFFSConfig::fsid::FSId) || (SPIFFS_mounted(&_fs) != 0)) {
+ if ((cfg._type != SPIFFSConfig::FSId) || (SPIFFS_mounted(&_fs) != 0)) {
return false;
}
_cfg = *static_cast(&cfg);
diff --git a/doc/filesystem.rst b/doc/filesystem.rst
index d70655f2fe..1e7ae14227 100644
--- a/doc/filesystem.rst
+++ b/doc/filesystem.rst
@@ -92,6 +92,27 @@ and ``SPIFFS.open()`` to ``LittleFS.open()`` with the rest of the
code remaining untouched.
+SDFS and SD
+-----------
+FAT filesystems are supported on the ESP8266 using the old Arduino wrapper
+"SD.h" which wraps the "SDFS.h" filesystem transparently.
+
+Any commands discussed below pertaining to SPIFFS or LittleFS are
+applicable to SD/SDFS.
+
+For legacy applications, the classic SD filesystem may continue to be used,
+but for new applications, directly accessing the SDFS filesystem is
+recommended as it may expose additional functionality that the old Arduino
+SD filesystem didn't have.
+
+Note that in earlier releases of the core, using SD and SPIFFS in the same
+sketch was complicated and required the use of ``NO_FS_GLOBALS``. The
+current design makes SD, SDFS, SPIFFS, and LittleFS fully source compatible
+and so please remove any ``NO_FS_GLOBALS`` definitions in your projects
+when updgrading core versions.
+
+
+
SPIFFS file system limitations
------------------------------
@@ -198,8 +219,8 @@ use esptool.py.
- To upload a LittleFS filesystem use Tools > ESP8266 LittleFS Data Upload
-File system object (SPIFFS/LittleFS)
-------------------------------------
+File system object (SPIFFS/LittleFS/SD/SDFS)
+--------------------------------------------
setConfig
~~~~~~~~~
@@ -369,6 +390,31 @@ rename
Renames file from ``pathFrom`` to ``pathTo``. Paths must be absolute.
Returns *true* if file was renamed successfully.
+gc
+~~
+
+.. code:: cpp
+
+ SPIFFS.gc()
+
+Only implemented in SPIFFS. Performs a quick garbage collection operation on SPIFFS,
+possibly making writes perform faster/better in the future. On very full or very fragmented
+filesystems, using this call can avoid or reduce issues where SPIFFS reports free space
+but is unable to write additional data to a file. See `this discussion
+` for more info.
+
+check
+~~~~~
+
+.. code:: cpp
+
+ SPIFFS.begin();
+ SPIFFS.check();
+
+Only implemented in SPIFFS. Performs an in-depth check of the filesystem metadata and
+correct what is repairable. Not normally needed, and not guaranteed to actually fix
+anything should there be corruption.
+
info
~~~~
@@ -379,7 +425,7 @@ info
or LittleFS.info(fs_info);
Fills `FSInfo structure <#filesystem-information-structure>`__ with
-information about the file system. Returns ``true`` is successful,
+information about the file system. Returns ``true`` if successful,
``false`` otherwise.
Filesystem information structure
@@ -404,30 +450,45 @@ block size - ``pageSize`` — filesystem logical page size - ``maxOpenFiles``
``maxPathLength`` — max file name length (including one byte for zero
termination)
-gc
-~~
+info64
+~~~~~~
.. code:: cpp
- SPIFFS.gc()
+ FSInfo64 fsinfo;
+ SD.info(fsinfo);
+ or LittleFS(fsinfo);
-Only implemented in SPIFFS. Performs a quick garbage collection operation on SPIFFS,
-possibly making writes perform faster/better in the future. On very full or very fragmented
-filesystems, using this call can avoid or reduce issues where SPIFFS reports free space
-but is unable to write additional data to a file. See `this discussion
-` for more info.
+Performs the same operation as ``info`` but allows for reporting greater than
+4GB for filesystem size/used/etc. Should be used with the SD and SDFS
+filesystems since most SD cards today are greater than 4GB in size.
-check
-~~~~~
+setTimeCallback(time_t (*cb)(void))
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: cpp
- SPIFFS.begin();
- SPIFFS.check();
+ time_t myTimeCallback() {
+ return 1455451200; // UNIX timestamp
+ }
+ void setup () {
+ LittleFS.setTimeCallback(myTimeCallback);
+ ...
+ // Any files will now be made with Pris' incept date
+ }
-Only implemented in SPIFFS. Performs an in-depth check of the filesystem metadata and
-correct what is repairable. Not normally needed, and not guaranteed to actually fix
-anything should there be corruption.
+
+The SD, SDFS, and LittleFS filesystems support a file timestamp, updated when the file is
+opened for writing. By default, the ESP8266 will use the internal time returned from
+``time(NULL)`` (i.e. local time, not UTC, to conform to the existing FAT filesystem), but this
+can be overridden to GMT or any other standard you'd like by using ``setTimeCallback()``.
+If your app sets the system time using NTP before file operations, then
+you should not need to use this function. However, if you need to set a specific time
+for a file, or the system clock isn't correct and you need to read the time from an external
+RTC or use a fixed time, this call allows you do to so.
+
+In general use, with a functioning ``time()`` call, user applications should not need
+to use this function.
Directory object (Dir)
----------------------
@@ -468,6 +529,12 @@ fileSize
Returns the size of the current file pointed to
by the internal iterator.
+fileTime
+~~~~~~~~
+
+Returns the time_t write time of the current file pointed
+to by the internal iterator.
+
isFile
~~~~~~
@@ -491,6 +558,13 @@ rewind
Resets the internal pointer to the start of the directory.
+setTimeCallback(time_t (*cb)(void))
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sets the time callback for any files accessed from this Dir object via openNextFile.
+Note that the SD and SDFS filesystems only support a filesystem-wide callback and
+calls to ``Dir::setTimeCallback`` may produce unexpected behavior.
+
File object
-----------
@@ -562,6 +636,12 @@ fullName
Returns the full path file name as a ``const char*``.
+getLastWrite
+~~~~~~~~~~~~
+
+Returns the file last write time, and only valid for files opened in read-only
+mode. If a file is opened for writing, the returned time may be indeterminate.
+
isFile
~~~~~~
@@ -616,3 +696,10 @@ rewindDirectory (compatibiity method, not recommended for new code)
Resets the ``openNextFile`` pointer to the top of the directory. Only
valid when ``File.isDirectory() == true``.
+
+setTimeCallback(time_t (*cb)(void))
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sets the time callback for this specific file. Note that the SD and
+SDFS filesystems only support a filesystem-wide callback and calls to
+``Dir::setTimeCallback`` may produce unexpected behavior.
diff --git a/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino b/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino
new file mode 100644
index 0000000000..4c6b487aef
--- /dev/null
+++ b/libraries/LittleFS/examples/LittleFS_Timestamp/LittleFS_Timestamp.ino
@@ -0,0 +1,181 @@
+/* Example showing timestamp support in LittleFS */
+/* Released into the public domain. */
+/* Earle F. Philhower, III */
+
+#include
+#include
+#include
+#include
+
+#ifndef STASSID
+#define STASSID "your-ssid"
+#define STAPSK "your-password"
+#endif
+
+const char *ssid = STASSID;
+const char *pass = STAPSK;
+
+long timezone = 2;
+byte daysavetime = 1;
+
+
+bool getLocalTime(struct tm * info, uint32_t ms) {
+ uint32_t count = ms / 10;
+ time_t now;
+
+ time(&now);
+ localtime_r(&now, info);
+
+ if (info->tm_year > (2016 - 1900)) {
+ return true;
+ }
+
+ while (count--) {
+ delay(10);
+ time(&now);
+ localtime_r(&now, info);
+ if (info->tm_year > (2016 - 1900)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void listDir(const char * dirname) {
+ Serial.printf("Listing directory: %s\n", dirname);
+
+ Dir root = LittleFS.openDir(dirname);
+
+ while (root.next()) {
+ File file = root.openFile("r");
+ Serial.print(" FILE: ");
+ Serial.print(root.fileName());
+ Serial.print(" SIZE: ");
+ Serial.print(file.size());
+ time_t t = file.getLastWrite();
+ struct tm * tmstruct = localtime(&t);
+ file.close();
+ Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
+ }
+}
+
+
+void readFile(const char * path) {
+ Serial.printf("Reading file: %s\n", path);
+
+ File file = LittleFS.open(path, "r");
+ if (!file) {
+ Serial.println("Failed to open file for reading");
+ return;
+ }
+
+ Serial.print("Read from file: ");
+ while (file.available()) {
+ Serial.write(file.read());
+ }
+ file.close();
+}
+
+void writeFile(const char * path, const char * message) {
+ Serial.printf("Writing file: %s\n", path);
+
+ File file = LittleFS.open(path, "w");
+ if (!file) {
+ Serial.println("Failed to open file for writing");
+ return;
+ }
+ if (file.print(message)) {
+ Serial.println("File written");
+ } else {
+ Serial.println("Write failed");
+ }
+ file.close();
+}
+
+void appendFile(const char * path, const char * message) {
+ Serial.printf("Appending to file: %s\n", path);
+
+ File file = LittleFS.open(path, "a");
+ if (!file) {
+ Serial.println("Failed to open file for appending");
+ return;
+ }
+ if (file.print(message)) {
+ Serial.println("Message appended");
+ } else {
+ Serial.println("Append failed");
+ }
+ file.close();
+}
+
+void renameFile(const char * path1, const char * path2) {
+ Serial.printf("Renaming file %s to %s\n", path1, path2);
+ if (LittleFS.rename(path1, path2)) {
+ Serial.println("File renamed");
+ } else {
+ Serial.println("Rename failed");
+ }
+}
+
+void deleteFile(const char * path) {
+ Serial.printf("Deleting file: %s\n", path);
+ if (LittleFS.remove(path)) {
+ Serial.println("File deleted");
+ } else {
+ Serial.println("Delete failed");
+ }
+}
+
+void setup() {
+ Serial.begin(115200);
+ // We start by connecting to a WiFi network
+ Serial.println();
+ Serial.println();
+ Serial.print("Connecting to ");
+ Serial.println(ssid);
+
+ WiFi.begin(ssid, pass);
+
+ while (WiFi.status() != WL_CONNECTED) {
+ delay(500);
+ Serial.print(".");
+ }
+ Serial.println("WiFi connected");
+ Serial.println("IP address: ");
+ Serial.println(WiFi.localIP());
+ Serial.println("Contacting Time Server");
+ configTime(3600 * timezone, daysavetime * 3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
+ struct tm tmstruct ;
+ delay(2000);
+ tmstruct.tm_year = 0;
+ getLocalTime(&tmstruct, 5000);
+ Serial.printf("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
+ Serial.println("");
+
+ Serial.printf("Formatting LittleFS filesystem\n");
+ LittleFS.format();
+ listDir("/");
+ deleteFile("/hello.txt");
+ writeFile("/hello.txt", "Hello ");
+ appendFile("/hello.txt", "World!\n");
+ listDir("/");
+
+ Serial.printf("The timestamp should be valid above\n");
+
+ Serial.printf("Now unmount and remount and perform the same operation.\n");
+ Serial.printf("Timestamp should be valid, data should be good.\n");
+ LittleFS.end();
+ Serial.printf("Now mount it\n");
+ if (!LittleFS.begin()) {
+ Serial.println("LittleFS mount failed");
+ return;
+ }
+ readFile("/hello.txt");
+ listDir("/");
+
+
+}
+
+void loop() { }
+
diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h
index 467255ca31..2a35869eda 100644
--- a/libraries/LittleFS/src/LittleFS.h
+++ b/libraries/LittleFS/src/LittleFS.h
@@ -47,11 +47,8 @@ class LittleFSDirImpl;
class LittleFSConfig : public FSConfig
{
public:
- LittleFSConfig(bool autoFormat = true) {
- _type = LittleFSConfig::fsid::FSId;
- _autoFormat = autoFormat;
- }
- enum fsid { FSId = 0x4c495454 };
+ static constexpr uint32_t FSId = 0x4c495454;
+ LittleFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
};
class LittleFSImpl : public FSImpl
@@ -176,7 +173,7 @@ class LittleFSImpl : public FSImpl
}
bool setConfig(const FSConfig &cfg) override {
- if ((cfg._type != LittleFSConfig::fsid::FSId) || _mounted) {
+ if ((cfg._type != LittleFSConfig::FSId) || _mounted) {
return false;
}
_cfg = *static_cast(&cfg);
@@ -422,7 +419,25 @@ class LittleFSFileImpl : public FileImpl
lfs_file_close(_fs->getFS(), _getFD());
_opened = false;
DEBUGV("lfs_file_close: fd=%p\n", _getFD());
+ if (timeCallback) {
+ // Add metadata with last write time
+ time_t now = timeCallback();
+ int rc = lfs_setattr(_fs->getFS(), _name.get(), 't', (const void *)&now, sizeof(now));
+ if (rc < 0) {
+ DEBUGV("Unable to set time on '%s' to %d\n", _name.get(), now);
+ }
+ }
+ }
+ }
+
+ time_t getLastWrite() override {
+ time_t ftime = 0;
+ if (_opened && _fd) {
+ int rc = lfs_getattr(_fs->getFS(), _name.get(), 't', (void *)&ftime, sizeof(ftime));
+ if (rc != sizeof(ftime))
+ ftime = 0; // Error, so clear read value
}
+ return ftime;
}
const char* name() const override {
@@ -520,6 +535,27 @@ class LittleFSDirImpl : public DirImpl
return _dirent.size;
}
+ time_t fileTime() override {
+ if (!_valid) {
+ return 0;
+ }
+ int nameLen = 3; // Slashes, terminator
+ nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0;
+ nameLen += strlen(_dirent.name);
+ char *tmpName = (char*)malloc(nameLen);
+ if (!tmpName) {
+ return 0;
+ }
+ snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name);
+ time_t ftime = 0;
+ int rc = lfs_getattr(_fs->getFS(), tmpName, 't', (void *)&ftime, sizeof(ftime));
+ if (rc != sizeof(ftime))
+ ftime = 0; // Error, so clear read value
+ free(tmpName);
+ return ftime;
+ }
+
+
bool isFile() const override {
return _valid && (_dirent.type == LFS_TYPE_REG);
}
diff --git a/libraries/SD/examples/listfiles/listfiles.ino b/libraries/SD/examples/listfiles/listfiles.ino
index 2b5eaf1ab8..fd35d0c338 100644
--- a/libraries/SD/examples/listfiles/listfiles.ino
+++ b/libraries/SD/examples/listfiles/listfiles.ino
@@ -28,14 +28,14 @@ File root;
void setup() {
// Open serial communications and wait for port to open:
- Serial.begin(9600);
+ Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
Serial.print("Initializing SD card...");
- if (!SD.begin(4)) {
+ if (!SD.begin(SS)) {
Serial.println("initialization failed!");
return;
}
@@ -70,11 +70,13 @@ void printDirectory(File dir, int numTabs) {
} else {
// files have sizes, directories do not
Serial.print("\t\t");
- Serial.println(entry.size(), DEC);
+ Serial.print(entry.size(), DEC);
+ Serial.print("\t\t");
+ time_t ft = entry.getLastWrite();
+ struct tm *tm = localtime(&ft);
+ // US format. Feel free to convert to your own locale...
+ Serial.printf("%02d-%02d-%02d %02d:%02d:%02d\n", tm->tm_mon + 1, tm->tm_mday, tm->tm_year % 100, tm->tm_hour, tm->tm_min, tm->tm_sec);
}
entry.close();
}
}
-
-
-
diff --git a/libraries/SD/src/SD.cpp b/libraries/SD/src/SD.cpp
index 6b40b0e494..a3f0431dd8 100644
--- a/libraries/SD/src/SD.cpp
+++ b/libraries/SD/src/SD.cpp
@@ -3,3 +3,5 @@
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
SDClass SD;
#endif
+
+void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*) = nullptr;
diff --git a/libraries/SD/src/SD.h b/libraries/SD/src/SD.h
index bb1c696a58..d15d8f85b4 100644
--- a/libraries/SD/src/SD.h
+++ b/libraries/SD/src/SD.h
@@ -29,6 +29,7 @@
#undef FILE_WRITE
#define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT | sdfat::O_APPEND)
+
class SDClass {
public:
boolean begin(uint8_t csPin, SPISettings cfg = SPI_HALF_SPEED) {
@@ -137,6 +138,17 @@ class SDClass {
return ((uint64_t)clusterSize() * (uint64_t)totalClusters());
}
+ void setTimeCallback(time_t (*cb)(void)) {
+ SDFS.setTimeCallback(cb);
+ }
+
+ // Wrapper to allow obsolete datetimecallback use, silently convert to time_t in wrappertimecb
+ void dateTimeCallback(void (*cb)(uint16_t*, uint16_t*)) {
+ extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*);
+ __SD__userDateTimeCB = cb;
+ SDFS.setTimeCallback(wrapperTimeCB);
+ }
+
private:
const char *getMode(uint8_t mode) {
bool read = (mode & sdfat::O_READ) ? true : false;
@@ -150,8 +162,46 @@ class SDClass {
else { return "r"; }
}
+ static time_t wrapperTimeCB(void) {
+ extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*);
+ if (__SD__userDateTimeCB) {
+ uint16_t d, t;
+ __SD__userDateTimeCB(&d, &t);
+ return sdfs::SDFSImpl::FatToTimeT(d, t);
+ }
+ return time(nullptr);
+ }
+
};
+
+// Expose FatStructs.h helpers for MSDOS date/time for use with dateTimeCallback
+static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
+ return (year - 1980) << 9 | month << 5 | day;
+}
+static inline uint16_t FAT_YEAR(uint16_t fatDate) {
+ return 1980 + (fatDate >> 9);
+}
+static inline uint8_t FAT_MONTH(uint16_t fatDate) {
+ return (fatDate >> 5) & 0XF;
+}
+static inline uint8_t FAT_DAY(uint16_t fatDate) {
+ return fatDate & 0X1F;
+}
+static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
+ return hour << 11 | minute << 5 | second >> 1;
+}
+static inline uint8_t FAT_HOUR(uint16_t fatTime) {
+ return fatTime >> 11;
+}
+static inline uint8_t FAT_MINUTE(uint16_t fatTime) {
+ return (fatTime >> 5) & 0X3F;
+}
+static inline uint8_t FAT_SECOND(uint16_t fatTime) {
+ return 2*(fatTime & 0X1F);
+}
+
+
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
extern SDClass SD;
#endif
diff --git a/libraries/SDFS/src/SDFS.h b/libraries/SDFS/src/SDFS.h
index db55fe547a..c1c4f17ad8 100644
--- a/libraries/SDFS/src/SDFS.h
+++ b/libraries/SDFS/src/SDFS.h
@@ -45,22 +45,9 @@ class SDFSDirImpl;
class SDFSConfig : public FSConfig
{
public:
- SDFSConfig() {
- _type = SDFSConfig::fsid::FSId;
- _autoFormat = false;
- _csPin = 4;
- _spiSettings = SD_SCK_MHZ(10);
- _part = 0;
- }
- SDFSConfig(uint8_t csPin, SPISettings spi) {
- _type = SDFSConfig::fsid::FSId;
- _autoFormat = false;
- _csPin = csPin;
- _spiSettings = spi;
- _part = 0;
- }
+ static constexpr uint32_t FSId = 0x53444653;
- enum fsid { FSId = 0x53444653 };
+ SDFSConfig(uint8_t csPin = 4, SPISettings spi = SD_SCK_MHZ(10)) : FSConfig(FSId, false), _csPin(csPin), _part(0), _spiSettings(spi) { }
SDFSConfig setAutoFormat(bool val = true) {
_autoFormat = val;
@@ -152,7 +139,7 @@ class SDFSImpl : public FSImpl
bool setConfig(const FSConfig &cfg) override
{
- if ((cfg._type != SDFSConfig::fsid::FSId) || _mounted) {
+ if ((cfg._type != SDFSConfig::FSId) || _mounted) {
DEBUGV("SDFS::setConfig: invalid config or already mounted\n");
return false;
}
@@ -203,6 +190,20 @@ class SDFSImpl : public FSImpl
return (clusterSize() * totalClusters());
}
+ // Helper function, takes FAT and makes standard time_t
+ static time_t FatToTimeT(uint16_t d, uint16_t t) {
+ struct tm tiempo;
+ memset(&tiempo, 0, sizeof(tiempo));
+ tiempo.tm_sec = (((int)t) << 1) & 0x3e;
+ tiempo.tm_min = (((int)t) >> 5) & 0x3f;
+ tiempo.tm_hour = (((int)t) >> 11) & 0x1f;
+ tiempo.tm_mday = (int)(d & 0x1f);
+ tiempo.tm_mon = ((int)(d >> 5) & 0x0f) - 1;
+ tiempo.tm_year = ((int)(d >> 9) & 0x7f) + 80;
+ tiempo.tm_isdst = -1;
+ return mktime(&tiempo);
+ }
+
protected:
friend class SDFileImpl;
friend class SDFSDirImpl;
@@ -212,6 +213,7 @@ class SDFSImpl : public FSImpl
return &_fs;
}
+
static uint8_t _getFlags(OpenMode openMode, AccessMode accessMode) {
uint8_t mode = 0;
if (openMode & OM_CREATE) {
@@ -350,6 +352,17 @@ class SDFSFileImpl : public FileImpl
return _opened ? _fd->isDirectory() : false;
}
+ time_t getLastWrite() override {
+ time_t ftime = 0;
+ if (_opened && _fd) {
+ sdfat::dir_t tmp;
+ if (_fd.get()->dirEntry(&tmp)) {
+ ftime = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime);
+ }
+ }
+ return ftime;
+ }
+
protected:
SDFSImpl* _fs;
@@ -425,6 +438,12 @@ class SDFSDirImpl : public DirImpl
_size = file.fileSize();
_isFile = file.isFile();
_isDirectory = file.isDirectory();
+ sdfat::dir_t tmp;
+ if (file.dirEntry(&tmp)) {
+ _time = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime);
+ } else {
+ _time = 0;
+ }
file.getName(_lfn, sizeof(_lfn));
file.close();
} else {
@@ -447,6 +466,7 @@ class SDFSDirImpl : public DirImpl
std::shared_ptr _dir;
bool _valid;
char _lfn[64];
+ time_t _time;
std::shared_ptr _dirPath;
uint32_t _size;
bool _isFile;
diff --git a/package/package_esp8266com_index.template.json b/package/package_esp8266com_index.template.json
index 709238253c..66b03a92f4 100644
--- a/package/package_esp8266com_index.template.json
+++ b/package/package_esp8266com_index.template.json
@@ -121,7 +121,7 @@
},
{
"packager": "esp8266",
- "version": "2.5.0-4-b40a506",
+ "version": "2.5.0-4-69bd9e6",
"name": "mklittlefs"
},
{
@@ -302,61 +302,54 @@
]
},
{
- "version": "2.5.0-4-b40a506",
+ "version": "2.5.0-4-69bd9e6",
"name": "mklittlefs",
"systems": [
{
"host": "aarch64-linux-gnu",
- "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/aarch64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz",
- "archiveFileName": "aarch64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz",
- "checksum": "SHA-256:25c4dcf818d175e19c3cc22bc0388c61fa3d9bdf82a1fad388323cef34caa169",
- "size": "44059"
+ "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/aarch64-linux-gnu-mklittlefs-69bd9e6.tar.gz",
+ "archiveFileName": "aarch64-linux-gnu-mklittlefs-69bd9e6.tar.gz",
+ "checksum": "SHA-256:74d938f15a3fb8ac20aeb0f938ace2c6759f622451419c09446aa79866302e18",
+ "size": "44342"
},
{
"host": "arm-linux-gnueabihf",
- "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/arm-linux-gnueabihf.mklittlefs-7f77f2b.1563313032.tar.gz",
- "archiveFileName": "arm-linux-gnueabihf.mklittlefs-7f77f2b.1563313032.tar.gz",
- "checksum": "SHA-256:75a284f4e8c54d302b1880df46dd48e18857f69c21baa0977b1e6efc404caf18",
- "size": "36567"
- },
- {
- "host": "i686-pc-linux-gnu",
- "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz",
- "archiveFileName": "i686-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz",
- "checksum": "SHA-256:022c96df4d110f957d43f6d23e9c5e8b699a66d8ab041056dd5da7411a8ade42",
- "size": "47544"
+ "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/arm-linux-gnueabihf-mklittlefs-69bd9e6.tar.gz",
+ "archiveFileName": "arm-linux-gnueabihf-mklittlefs-69bd9e6.tar.gz",
+ "checksum": "SHA-256:926cca1c1f8f732a8ac79809ce0a52cabe283ab4137aa3237bca0fcca6bc2236",
+ "size": "36871"
},
{
"host": "i686-mingw32",
- "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip",
- "archiveFileName": "i686-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip",
- "checksum": "SHA-256:7778209e9df8c8c5f5da82660ff9a95b866defee3c9eb5c22371e0fd84b1addc",
- "size": "332057"
+ "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/i686-w64-mingw32-mklittlefs-69bd9e6.zip",
+ "archiveFileName": "i686-w64-mingw32-mklittlefs-69bd9e6.zip",
+ "checksum": "SHA-256:da916c66f70e162f4aec22dbcb4542dd8b8187d12c35c915d563e2262cfe6fbd",
+ "size": "332325"
},
{
"host": "x86_64-apple-darwin",
- "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-apple-darwin14.mklittlefs-7f77f2b.1563313032.tar.gz",
- "archiveFileName": "x86_64-apple-darwin14.mklittlefs-7f77f2b.1563313032.tar.gz",
- "checksum": "SHA-256:c465da766026c6c66d731442b741fb5a7f8b741e9473d181e6c5e588c541f588",
- "size": "362014"
+ "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-apple-darwin14-mklittlefs-69bd9e6.tar.gz",
+ "archiveFileName": "x86_64-apple-darwin14-mklittlefs-69bd9e6.tar.gz",
+ "checksum": "SHA-256:35610be5f725121eaa9baea83c686693f340742e61739af6789d00feff4e90ba",
+ "size": "362366"
},
{
"host": "x86_64-pc-linux-gnu",
- "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz",
- "archiveFileName": "x86_64-linux-gnu.mklittlefs-7f77f2b.1563313032.tar.gz",
- "checksum": "SHA-256:6a358716d4c780fa459b4c774723302431b3ad5e1ee3f7edae62be331541615c",
- "size": "46164"
+ "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-linux-gnu-mklittlefs-69bd9e6.tar.gz",
+ "archiveFileName": "x86_64-linux-gnu-mklittlefs-69bd9e6.tar.gz",
+ "checksum": "SHA-256:e4ce7cc80eceab6a9a2e620f2badfb1ef09ee88f7af529f290c65b4b72f19358",
+ "size": "46518"
},
{
"host": "x86_64-mingw32",
- "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip",
- "archiveFileName": "x86_64-w64-mingw32.mklittlefs-7f77f2b.1563313032.zip",
- "checksum": "SHA-256:d5d44b5f21681a831318a23b31957bc9368c50f0766964ead409c3d2fe4747d2",
- "size": "344578"
+ "url": "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-4/x86_64-w64-mingw32-mklittlefs-69bd9e6.zip",
+ "archiveFileName": "x86_64-w64-mingw32-mklittlefs-69bd9e6.zip",
+ "checksum": "SHA-256:c65ee1ee38f65ce67f664bb3118301ee6e93bec38a7a7efaf8e1d8455c6a4a18",
+ "size": "344780"
}
]
}
]
}
]
-}
\ No newline at end of file
+}