SD file creation date

photon
Tags: #<Tag:0x00007f1c9ee82d00>

#1

So I am currently using the VS1053 board and photon to upload/download voice recordings from FTP server, and I have no problem receiving date information from server when creating new download files (with callback function) but I am having issues with accessing the creation (or modification date/time) for the files on my SD card for the purpose of a weekly cleanup function (If downloaded file is older than week…delete). Do I need to create another function within SD class that has access to some of the internal root items? Any help would be great.
@ScruffR
@peekay123

**I have used peekay123’s code for handling FTP communication and will post my full code at bottom.

Topic is similar to issue in: Sd card percent full function


#include "Adafruit_VS1053_Photon.h"
#include "sd-card-library-photon-compat.h"
#include <sd-fat.h>

#define BREAKOUT_SCK     A3
#define BREAKOUT_MISO    A4
#define BREAKOUT_MOSI    A5
 // These are the pins used for the breakout example
 #define BREAKOUT_RESET  D6//9      // VS1053 reset pin (output)
 #define BREAKOUT_CS     A2//10     // VS1053 chip select pin (output)
 #define BREAKOUT_DCS    D5//8      // VS1053 Data/command select pin (output)
 // These are the pins used for the music maker shield

 // These are common pins between breakout and shield
 #define CARDCS D3//4     // Card chip select pin
 // DREQ should be an Int pin, see http://arduino.cc/en/Reference/attachInterrupt
 #define DREQ WKP//3       // VS1053 Data request, ideally an Interrupt pin

 Adafruit_VS1053_FilePlayer musicPlayer =
   // create breakout-example object!
   Adafruit_VS1053_FilePlayer(BREAKOUT_MOSI, BREAKOUT_MISO, BREAKOUT_SCK, BREAKOUT_RESET, BREAKOUT_CS, BREAKOUT_DCS, DREQ, CARDCS);


// comment out next line to write to SD from FTP server
// #define FTPWRITE

// Need to replace with host name instead of ip for drivehq
IPAddress server( 66, 220, 9, 50 );

TCPClient client;
TCPClient dclient;

char outBuf[128];
char outCount;

//SYSTEM_THREAD(ENABLED);

byte doFTP();
byte eRcv();
void efail();
void readSD();

// change fileName to your file (8.3 format!)
// Receiving device, date (day, month, year), time(hour, minute, 24hr format), A-Z 1-26, a-z 27-53,
char fileName[13];

// Declare constants for directory time

time_t tval;

File root;

String fileList[50];
String sdList[50];

String sdCreationTime[50];
String sdCreationDate[50];

SdVolume volume;
SdFile roots;

void setup()
{
  Time.zone(-7);
  delay(2000);
  Serial.begin(9600);
//  while(!Serial.available()) Particle.process();
  Serial.println("Initialization");
  if(!SD.begin(BREAKOUT_MOSI,BREAKOUT_MISO,BREAKOUT_SCK,CARDCS))
  {
    Serial.println("SD init fail");
  }

  Serial.println("Ready. Press f/r/d/l/q - ver 1");
delay(2000);
}

void loop()
{
  byte inChar;

  inChar = Serial.read();

  if(inChar == 'f')
  {
    if(doFTP(fileName)) Serial.println("FTP OK");
    else Serial.println("FTP FAIL");
  }

  if(inChar == 'r')
  {
    readSD();
  }
  if(inChar == 'd')
  {
    if(directoryDate(fileList[0])) Serial.println("FTP OK");
    else Serial.println("FTP FAIL");
  }
  if(inChar == 'l')
  {
    root = SD.open("/");
    printDirectory(root, 0);
  }
  if(inChar == 'q')
  {
    if(directoryList()) Serial.println("List");
    else Serial.println("FTP FAIL");
    delay(5000);
    if(directoryDate(fileList[0])) Serial.println("Date");
    else Serial.println("FTP FAIL");
    delay(5000);
    (fileList[0]).toCharArray(fileName, 13);
    if(doFTP(fileName)) Serial.println("Retrieve");
    else Serial.println("FTP FAIL");


  }

}

File fh;

byte doFTP(char fileFTP[13])
{
#ifdef FTPWRITE
  fh = SD.open(fileFTP,FILE_READ);
#else
  SD.remove(fileFTP);
  SdFile::dateTimeCallback(DateTime);
  fh = SD.open(fileFTP,FILE_WRITE);
#endif

  if(!fh)
  {
    Serial.println("SD open fail");
    return 0;
  }

#ifndef FTPWRITE
  if(!fh.seek(0))
  {
    Serial.println("Rewind fail");
    fh.close();
    return 0;
  }
#endif

  Serial.println("SD opened");

//  if (client.connect(server,21)) { // port number is 21 for driveHQ
    if (client.connect(server, 21)){
    Serial.println("Command connected");
  }
  else {
    fh.close();
    Serial.println("Command connection failed");
    return 0;
  }


  // For remote FTP server replace particle with username and photon with password, ftp.drivehq.com/drivehqshare/clabadmin/dev1-2
  // unsure about the format for the rest but PASV is likely passive TCP
  if(!eRcv()) return 0;
  client.println("USER clab-dev1");
  if(!eRcv()) return 0;
  client.println("PASS connectionslab");
  if(!eRcv()) return 0;
  client.println("CWD /drivehqshare/clabadmin/dev1-2");
  if(!eRcv()) return 0;
  client.println("SYST");
  if(!eRcv()) return 0;
  client.println("Type I");
  if(!eRcv()) return 0;
  client.println("PASV");
  if(!eRcv()) return 0;

  char *tStr = strtok(outBuf,"(,");
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL,"(,");
    array_pasv[i] = atoi(tStr);
    if(tStr == NULL)
    {
      Serial.println("Bad PASV Answer");
    }
  }

  unsigned int hiPort,loPort;

  hiPort = array_pasv[4] << 8;
  loPort = array_pasv[5] & 255;

  Serial.print("Data port: ");
  hiPort = hiPort | loPort;
  Serial.println(hiPort);

  if (dclient.connect(server,hiPort)) {
    Serial.println("Data connected");
  }
  else {
    Serial.println("Data connection failed");
    client.stop();
    fh.close();
    return 0;
  }

#ifdef FTPWRITE
  client.print("STOR ");
  client.println(fileFTP);
#else
  client.print("RETR ");
  client.println(fileFTP);
#endif

  if(!eRcv())
  {
    dclient.stop();
    return 0;
  }

#ifdef FTPWRITE
  Serial.println("Writing");

  byte clientBuf[64];
  int clientCount = 0;

  while(fh.available())
  {
    clientBuf[clientCount] = fh.read();
    clientCount++;

    if(clientCount > 63)
    {
      dclient.write(clientBuf,64);
      clientCount = 0;
      delay(2);
    }
  }

  if(clientCount > 0) dclient.write(clientBuf,clientCount);

#else
  while(dclient.connected())
  {
    while(dclient.available())
    {
      char c = dclient.read();
      fh.write(c);
    //  Serial.write(c);
    }
  }
#endif

  dclient.stop();
  Serial.println("Data disconnected");
  client.stop();
  Serial.println("Command disconnected");

  fh.close();
  Serial.println("SD closed");
  return 1;
}

byte eRcv()
{
  byte respCode;
  byte thisByte;

  while(!client.available()) Spark.process();
  respCode = client.peek();
  outCount = 0;

  while(client.available())
  {
    thisByte = client.read();
    Serial.write(thisByte);

    if(outCount < 127)
    {
      outBuf[outCount] = thisByte;
      outCount++;
      outBuf[outCount] = 0;
    }
  }

  if(respCode >= '4')
  {
    efail();
    return 0;
  }

  return 1;
}


void efail()
{
  byte thisByte = 0;

  client.println("QUIT");

  while(!client.available()) Spark.process();
  while(client.available())
  {
    thisByte = client.read();
    Serial.write(thisByte);
  }

  client.stop();
  Serial.println("Command disconnected");
  fh.close();
  Serial.println("SD closed");
}

void readSD()
{
  fh = SD.open(fileName,FILE_READ);

  if(!fh)
  {
    Serial.println("SD open fail");
    return;
  }

  while(fh.available())
  {
    Serial.write(fh.read());
  }

  fh.close();
}

byte directoryDate(String fileInput)
{

//  if (client.connect(server,21)) { // port number is 21 for driveHQ
    if (client.connect(server, 21)){
    Serial.println("Command connected");
  }
  else {
    Serial.println("Command connection failed");
    return 0;
  }


  if(!eRcv()) return 0;
  client.println("USER clab-dev1");
  if(!eRcv()) return 0;
  client.println("PASS connectionslab");
  if(!eRcv()) return 0;
  client.println("CWD /drivehqshare/clabadmin/dev1-2");
  if(!eRcv()) return 0;
  client.println("SYST");
  if(!eRcv()) return 0;
  client.println("Type ascii");
  if(!eRcv()) return 0;
  client.println("PASV");
  if(!eRcv()) return 0;

  char *tStr = strtok(outBuf,"(,");
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL,"(,");
    array_pasv[i] = atoi(tStr);
    if(tStr == NULL)
    {
      Serial.println("Bad PASV Answer");
    }
  }

  unsigned int hiPort,loPort;

  hiPort = array_pasv[4] << 8;
  loPort = array_pasv[5] & 255;

  Serial.print("Data port: ");
  hiPort = hiPort | loPort;
  Serial.println(hiPort);

  if (dclient.connect(server,hiPort)) {
    Serial.println("Data connected");
  }
  else {
    Serial.println("Data connection failed");
    client.stop();
    return 0;
  }

  // Sending list command to server for date of file
  byte testBuffer[65];
  byte newBuffer[13];
  client.println("LIST " + fileInput);

  // Reading buffer values
  if(!eRcv()) return 0;
  dclient.read(testBuffer, 65);

  // String val = String((char*)testBuffer);
  // Serial.println("List Command Has been Issued");
  // Serial.println(val);

  // Formatting date
   int i = 0;
   for (i=39; i<51; i++) {
     newBuffer[i-39] = testBuffer[i];
   }
   String val = String((char*)newBuffer);
   val = val.remove(12);
   //char date[12];
   //val.toCharArray(date, 12);
   String monthInput = val.substring(0, 3);
   int Day = (val.substring(4, 6)).toInt();
   int Hour = (val.substring(7, 9)).toInt();
   int Minute = (val.substring(10, 12)).toInt();
   int Month;

   // Switch statement for month values
   if (monthInput == "Jan") {
          Month = 1; }
   else if (monthInput == "Feb") {
          Month = 2; }
   else if (monthInput == "Mar") {
          Month = 3; }
   else if (monthInput == "Apr") {
          Month = 4; }
   else if (monthInput == "May") {
          Month = 5; }
   else if (monthInput == "Jun") {
          Month = 6; }
   else if (monthInput == "Jul") {
          Month = 7; }
   else if (monthInput == "Aug") {
          Month = 8; }
   else if (monthInput == "Sep") {
          Month = 9; }
   else if (monthInput == "Oct") {
          Month = 10; }
   else if (monthInput == "Nov") {
          Month = 11; }
   else if (monthInput == "Dec") {
          Month = 12; }

   tval = tmConvert_t(2019, Month, Day, Hour, Minute, 00);

   Serial.println("UNIX OFFSET");
   Serial.println(Time.month(tval));
   Serial.println(Time.day(tval));
   Serial.println(Time.hour(tval));
   Serial.println(Time.minute(tval));


  if(!eRcv())
  {
    dclient.stop();
    return 0;
  }

  dclient.stop();
  Serial.println("Data disconnected");
  client.stop();
  Serial.println("Command disconnected");


  return 1;
}

byte directoryList()
{

//  if (client.connect(server,21)) { // port number is 21 for driveHQ
    if (client.connect(server, 21)){
    Serial.println("Command connected");
  }
  else {
    Serial.println("Command connection failed");
    return 0;
  }


  // For remote FTP server replace particle with username and photon with password, ftp.drivehq.com/drivehqshare/clabadmin/dev1-2
  // unsure about the format for the rest but PASV is likely passive TCP
  if(!eRcv()) return 0;
  client.println("USER clab-dev1");
  if(!eRcv()) return 0;
  client.println("PASS connectionslab");
  if(!eRcv()) return 0;
  client.println("CWD /drivehqshare/clabadmin/dev1-2");
  if(!eRcv()) return 0;
  client.println("SYST");
  if(!eRcv()) return 0;
  client.println("Type ascii");
  if(!eRcv()) return 0;
  client.println("PASV");
  if(!eRcv()) return 0;

  char *tStr = strtok(outBuf,"(,");
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL,"(,");
    array_pasv[i] = atoi(tStr);
    if(tStr == NULL)
    {
      Serial.println("Bad PASV Answer");
    }
  }

  unsigned int hiPort,loPort;

  hiPort = array_pasv[4] << 8;
  loPort = array_pasv[5] & 255;

  Serial.print("Data port: ");
  hiPort = hiPort | loPort;
  Serial.println(hiPort);

  if (dclient.connect(server,hiPort)) {
    Serial.println("Data connected");
  }
  else {
    Serial.println("Data connection failed");
    client.stop();
    return 0;
  }


  Serial.println("Testing NLST Command");
  byte testBuffer2[500];

  client.println("NLST");
  if(!eRcv()) return 0;

  dclient.read(testBuffer2, 500);
  String val = String((char*)testBuffer2);
  val = val.trim();

  int i = 0;
  for (i=0; i < (val.length()/13); i++) {
    // file array limit
    if (i == 50) {
      break;
    }
    if (i == (val.length()/13 -1)) {
    fileList[i] = (val.substring(i*13, (i*13) + 14)).trim();
    }
    else {
      fileList[i] = (val.substring(i*13, (i*13) + 13)).trim();
    }
  }
  // String test = (val.substring(0, 13)).trim();
  Serial.println("List Command Has been Issued");

  Serial.println(fileList[0]);

  if(!eRcv())
  {
    dclient.stop();
    return 0;
  }


  dclient.stop();
  Serial.println("Data disconnected");
  client.stop();
  Serial.println("Command disconnected");

  return 1;
}

time_t tmConvert_t(int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss)  // inlined for speed
{
  struct tm t;
  t.tm_year = YYYY-1900;
  t.tm_mon = MM - 1;
  t.tm_mday = DD;
  t.tm_hour = hh;
  t.tm_min = mm;
  t.tm_sec = ss;
  t.tm_isdst = 0;  // not used
  time_t t_of_day = mktime(&t);
  return t_of_day;
}

 void DateTime(uint16_t* date, uint16_t* timeVal) {

   // return date using FAT_DATE macro to format fields
   *date = FAT_DATE(2019, Time.month(tval), Time.day(tval));

   // return time using FAT_TIME macro to format fields
   *timeVal = FAT_TIME(Time.hour(tval), Time.minute(tval), 00);
 }

 void printDirectory(File dir, int numTabs) {

   int i = 0;
   while (true) {

     File entry =  dir.openNextFile();
     if (! entry) {
       // no more files
       break;
     }
    //  for (uint8_t i = 0; i < numTabs; i++) {
    //    Serial.print('\t');
    //  }
    if (entry.isDirectory()) {

    }
    else if (String(entry.name()).startsWith("RE")) {
     sdList[i] = String(entry.name());
     i++;
    }
    //  if (entry.isDirectory()) {
    //    Serial.println("/");
    //    printDirectory(entry, numTabs + 1);
    //  } else {
    //    // files have sizes, directories do not
    //    Serial.print("\t\t");
    //    Serial.println(entry.size(), DEC);
    //  }
     entry.close();
   }

    Serial.println(sdList[0]); // Testing

 }

// Testing sample code
 void printTimestamps(SdFile& f) {
  dir_t d;
  if (!f.dirEntry(&d)) Serial.println("Error");
  f.printFatDate(d.creationDate);
  f.printFatTime(d.creationTime);
}

#2

You can have a look at this SdFat library example how to access the FAT timestamps.
https://build.particle.io/libs/SdFat/1.0.16/tab/example/Timestamp.ino


#3

I tried compiling the timestamp code and it complained about SdFat (even with library included) not being a type, going to just poke around the library to see what’s going wrong.


#4

Yup, the example won’t compile as is, since it’s using some functions not present on this platform (e.g. cout).
But you should be able to get how it’s done from looking at the code :wink:

I’ll have a shot at getting a simple PoC done.


#5

I think I get the basic idea with using the SdFile and & for addressing, I was just having some issues with accessing things in main script. I’m trying to write functions within sd-photon-compat.h right now and having some better luck. So far I got printFatDate and printFatTime to work but was trying to get it to just pass out the information instead of printing.

sd-card-library-photon-compat.cpp ->

boolean SDClass::fileDate(String sdCreation, String fileItem) {
  SdFile entryFileTest;
	char input[13];
	fileItem.toCharArray(input, 13);
	entryFileTest.open(SD.root, input, O_READ);
	SdFile & entryFile = entryFileTest;
	dir_t d;
  if (!entryFile.dirEntry(&d)) {
    Serial.println("Error Date");
  }

	entryFile.FatOutput(d.creationDate, d.creationTime, sdCreation);
        // entryFile.printFatDate(d.creationDate);
       // entryFile.printFatTime(d.creationTime);
	entryFileTest.close();
	return true;
}

sd-fat.h ->

static void FatOutput(uint16_t fatDate, uint16_t fatTime, String dateOutput);

sd-file.cpp ->

void SdFile::FatOutput(uint16_t fatDate, uint16_t fatTime, String dateOutput)
{
  // uint8_t v;
  // v = FAT_YEAR(fatDate);
  // char str[3];
  // str[0] = '0' + v/10;
  // str[1] = '0' + v % 10;
  // str[2] = 0;
  // dateOutput = String(str);
  // dateOutput = dateOutput + ":";
  // 
  // v = FAT_MONTH(fatDate);
  // str[0] = '0' + v/10;
  // str[1] = '0' + v % 10;
  // str[2] = 0;
  // dateOutput = String(str);
  // dateOutput = dateOutput + ":";
  // 
  // v = FAT_DAY(fatDate);
  // str[0] = '0' + v/10;
  // str[1] = '0' + v % 10;
  // str[2] = 0;
  // dateOutput = String(str);
  // dateOutput = dateOutput + ":";
  // 
  // v = FAT_HOUR(fatTime);
  // str[0] = '0' + v/10;
  // str[1] = '0' + v % 10;
  // str[2] = 0;
  // dateOutput = String(str);
  // dateOutput = dateOutput + ":";
  // 
  // v = FAT_MINUTE(fatTime);
  // str[0] = '0' + v/10;
  // str[1] = '0' + v % 10;
  // str[2] = 0;
  // dateOutput = String(str);
  // dateOutput = dateOutput + ":";
  // 
  // v = FAT_SECOND(fatTime);
  // str[0] = '0' + v/10;
  // str[1] = '0' + v % 10;
  // str[2] = 0;
  // dateOutput = String(str);
}

#6

Also I noticed that ls function can be used by creating a simple function in the sd-card-library-photon-compat library for spitting out root directory file date and names. Apologies for my messy coding!

boolean SDClass::dateList(void) {
	root.ls(LS_DATE);
	return true;
}

#7

For anyone else interested in receiving specific creation dates for SD files apart from the above simple function of printing out all file information in root directory. There is probably a much easier way to do it (see above timestamp example), but I didn’t have access to certain items in main code and I was currently using the SD class so didn’t want conflicts.

[1] Created struct in sd-fat.h so I could pass through information via pointers back to main code (otherwise it doesn’t return values).

struct sdFatDate{
  int year;
  int month;
  int day;
  int hour;
  int minute;
  int second;
};

[2] Created function within sd-fat.h within same class as printFatDate/printFatTime

static void FatOutput(uint16_t fatDate, uint16_t fatTime, sdFatDate * storedPointer);

[3] Function is defined within sd-file.cpp

void SdFile::FatOutput(uint16_t fatDate, uint16_t fatTime, sdFatDate * storedPointer)
{
  uint8_t v;
  v =  FAT_HOUR(fatTime);
  char str[3];
  str[0] = '0' + v/10;
  str[1] = '0' + v % 10;
  str[2] = 0;
  int val = String(str).toInt();
  (*storedPointer).hour = val;
  
  v = FAT_MINUTE(fatTime);
  str[0] = '0' + v/10;
  str[1] = '0' + v % 10;
  str[2] = 0;
  val = String(str).toInt();
  (*storedPointer).minute = val;

  v = FAT_SECOND(fatTime);
  str[0] = '0' + v/10;
  str[1] = '0' + v % 10;
  str[2] = 0;
  val = String(str).toInt();
  (*storedPointer).second = val;

  int yearVal = FAT_YEAR(fatDate);
  (*storedPointer).year = yearVal;

  v = FAT_MONTH(fatDate);
  str[0] = '0' + v/10;
  str[1] = '0' + v % 10;
  str[2] = 0;
  val = String(str).toInt();
  (*storedPointer).month = val;

  v = FAT_DAY(fatDate);
  str[0] = '0' + v/10;
  str[1] = '0' + v % 10;
  str[2] = 0;
  val = String(str).toInt();
  (*storedPointer).day = val;
}

[4] Initialized function within sd-card-library-photon-compat.h

boolean fileDate(sdFatDate * holdPointer, String fileItem);

[5] Declare function within sd-card-library-photon-compat.cpp

boolean SDClass::fileDate(sdFatDate * holdPointer, String fileItem) {
  SdFile entryFileTest;
	char input[13];
	fileItem.toCharArray(input, 13);
	entryFileTest.open(SD.root, input, O_READ);
	SdFile & entryFile = entryFileTest;
	dir_t d;
  if (!entryFile.dirEntry(&d)) {
    Serial.println("Error Date");
  }

	entryFile.FatOutput(d.creationDate, d.creationTime, holdPointer);

	entryFileTest.close();
	Serial.println(holdPointer->hour); // Can ignore this, was just debugging
	return true;
}

[6] Main code, very brief idea.

sdFatDate dateStorage; // Declare global access to struct

// Bunch more code .....

    SD.fileDate(&dateStorage, sdList[0]); // Calling function
    Serial.println(dateStorage.hour); // Test printing out creation date from requested file