Arianna autonomous DAQ firmware

Dependencies:   mbed SDFileSystemFilinfo AriSnProtocol NetServicesMin AriSnComm MODSERIAL PowerControlClkPatch DS1820OW

SnSDUtils.cpp

Committer:
uci1
Date:
2014-11-28
Revision:
65:2cb3e99ce466
Parent:
64:6c7a316eafad
Child:
66:685f9d0a48ae

File content as of revision 65:2cb3e99ce466:

#include "SnSDUtils.h"

#include "mbed.h"
#include "SDFileSystem.h"
#include "FATDirHandle.h"
#include <string.h>
#include <string>

#include "SnConfigFrame.h"
#include "SnEventFrame.h"
#include "SnHeartbeatFrame.h"
#include "SnClockSetFrame.h"

#include "Watchdog.h"

//#define DEBUG

#define SUBDIRSEQ 100  // make a new subdir every X sequences

const char* const SnSDUtils::kSDdir     = "/sd";
const char* const SnSDUtils::kSDsubDir  = "/sd/data";
const char* const SnSDUtils::kRunSeqListFilenm = "/sd/RunSeqLC.txt";
const uint16_t    SnSDUtils::kMaxSeqNum = 64000; // max "normal" seq
const uint16_t    SnSDUtils::kBadSeqNum = 65000; // should be larger than kMaxSeqNum to separate "normal" and "bad" seqs
char              SnSDUtils::fgCurFileName[kFNBufSize]={0};
FILE*             SnSDUtils::fgCurFile  = 0;
uint16_t          SnSDUtils::fgCurSeq   = 0;
bool              SnSDUtils::fgNeedToInit = true;
const uint8_t     SnSDUtils::kIOvers    = 4;
const uint32_t    SnSDUtils::kMaxSizeOfFileHdr = 
    sizeof(uint8_t)+sizeof(uint64_t)+sizeof(uint32_t)+sizeof(uint16_t)
    +(sizeof(uint8_t)+(2u*sizeof(uint16_t))); // power frame v1

SnSDUtils::InitSDFcn SnSDUtils::fgDoInit = 0;
bool                 SnSDUtils::fgInitOk = false;

static const uint16_t __kMaxUShort = ~0;

bool SnSDUtils::InitSDCard(const bool force) {
    Watchdog::kick(); // don't reset
    if ((fgNeedToInit || force) && (fgDoInit!=0)) {
        fgInitOk = (*fgDoInit)() == 0;
        if (IsInitOk()) {
            fgNeedToInit = false;
        }
    }
    return fgInitOk;
}

const char* SnSDUtils::GetSubDirFor(const uint32_t run, const uint16_t seq,
                                    uint32_t& slen, const bool useSeq) {
    // returns a STATIC string! (so make a copy of it before use)
    // sets slen to the length of this string (same as strlen)
    Watchdog::kick(); // don't reset
    static const uint16_t tmplen = strlen(kSDsubDir)+50; 
    static char* tmpsd = new char[tmplen];
    slen = snprintf(tmpsd, tmplen, "%s/r%05ld", kSDsubDir, run);
    if (useSeq) {
        slen += snprintf(tmpsd+slen, tmplen-slen, "/s%05d", (seq/SUBDIRSEQ)*SUBDIRSEQ);
    }
    if (slen > tmplen) {
        slen = tmplen;
    }
    return tmpsd;
}

const char* SnSDUtils::GetOutFileName(const uint64_t macadr,
                                      const uint32_t run,
                                      const uint16_t seq) {
    // returns the formatted file name, or NULL if the directory is too long
    // and the full name cannot fit in the buffer
    // NOTE: this fcn uses a static buffer, and so should not be called
    // multiple times in the same line (that includes the calling of functions
    // that call this function!)
    //
    // filename = SnEvtsM[6-byte hex mac adr]r[6-digit run num]s[5-digit seq num].dat
    //  35 chars      7     +    12         +1+         5     +1+     5         + 4
    Watchdog::kick(); // don't reset
    uint32_t sdlen(0);
    std::string subdirs( GetSubDirFor(run, seq, sdlen, true) );
    const char* subdir = subdirs.c_str();
    if (sdlen<(kFNBufSize-37)) {
        static char tbuf[kFNBufSize];
        memset(tbuf, 0, sizeof(char)*kFNBufSize);
        // if file name format changes, GetSeqNum must be changed too
        sprintf(tbuf, "%s/SnEvtsM%012llXr%05lds%05d.dat",
            subdir,
            macadr>>16, // 64 -> 48 bits
            run, seq);
        return tbuf;
    } else {
        return NULL;
    }
}

bool SnSDUtils::GetRunSeqFromFilename(const char* fn,
                                      uint32_t& run,
                                      uint16_t& seq) {
    Watchdog::kick(); // don't reset
    bool ret = false;
    const int32_t ncomp = strrchr(fn, 'r') - fn;
#ifdef DEBUG
    printf("fn=%s, ncomp=%d\r\n",fn,ncomp);
#endif
    if ((ncomp<strlen(fn)) && (ncomp>0)) {
        if (sscanf(fn+ncomp,"r%lus%hu.dat",&run,&seq)==2) {
#ifdef DEBUG
            printf("run=%u, seq=%hu\r\n",run,seq);
#endif
            ret = true;
        }
    }
    return ret;
}
/*
uint16_t SnSDUtils::GetSeqNumFromFileName(const char* fn) {
    uint16_t seq=0;
    const uint32_t ncomp = strrchr(fn, 's') - fn;
    if (ncomp<strlen(fn)) {
        sscanf(fn+ncomp,"s%hu.dat",&seq);
    }
    return seq;
}
*/

DIR* SnSDUtils::OpenOrMakeAllDirs(const char* dirname) {
#ifdef DEBUG
    printf("OpenOrMakeAllDirs: [%s]\r\n",dirname);
#endif
    Watchdog::kick(); // don't reset
    // try making the subdir
    DIR* sd(0);
    std::string dn(dirname);
    std::size_t slash(0);
    std::string sdr;
    bool ok=true;
    while (ok) {
        slash=dn.find_first_of('/',slash+1);
        if (slash==std::string::npos) {
            sdr = dn; // no more slashes
            ok=false;
        } else {
            sdr = dn.substr(0, slash);
        }
#ifdef DEBUG
        printf("slash=%d, sdr=[%s] (%d), ok=%s\r\n",
            (int)slash, sdr.c_str(), (int)sdr.size(),
            ok ? "true" : "false");
#endif        
        if (sd!=0) {
            // close the one from last time
            closedir(sd);
        }
        // skip the /sd, as it's not a real directory
        // should be skipped anyway, but..
        if (sdr.compare(kSDdir)!=0) {
#ifdef DEBUG
            printf("calling OpenOrMakeDir [%s]\r\n",sdr.c_str());
#endif
            sd = OpenOrMakeDir(sdr.c_str());
        }
    }
    return sd;
}

DIR* SnSDUtils::OpenOrMakeDir(const char* dirname) {
#ifdef DEBUG
    printf("open dir %s\r\n",dirname);
#endif
    Watchdog::kick(); // don't reset
    if (InitSDCard()) {

        DIR* rd( opendir(dirname) );
        if (rd==NULL) {
            // try making the directory
#ifdef DEBUG
            printf("making dir %s\r\n",dirname);
#endif
            mkdir(dirname, 0777);
#ifdef DEBUG
            printf("opening dir %s\r\n",dirname);
#endif
            rd = opendir(dirname);
        }
#ifdef DEBUG
            printf("returning rd=%p\r\n",(void*)rd);
#endif
        return rd;
    } else {
        return 0;
    }
}

uint16_t SnSDUtils::GetSeqNum(const uint64_t macadr,
                              const uint32_t run) {
    // count the files having expected filename format

    Watchdog::kick(); // don't reset

    uint16_t maxs(kBadSeqNum);
    
    if (InitSDCard()) {
        
        maxs = 0; // change back from kBadSeqNum!
        
        // get the run dir
        uint32_t rdlen(0);
        std::string rdnms ( GetSubDirFor(run, 0, rdlen, false) );
        const char* rdnm = rdnms.c_str();
        // open/make the run directory
        FATDirHandle* rd(static_cast<FATDirHandle*>( OpenOrMakeAllDirs(rdnm) ));
        struct dirent* rdent;
        uint16_t dseq(0);
#ifdef DEBUG
            printf("starting readdir loop over %p\r\n",(void*)rd);
#endif    
        while ( (rdent = readdir(rd))!=NULL ) {
#ifdef DEBUG
            printf("rdent = %p\r\n",(void*)rdent);
#endif    
            if ((rd->filinfo()->fattrib & AM_DIR)!=0) {
#ifdef DEBUG
                printf("is a dir\r\n");
#endif    
                // is a directory
                const int ncm = sscanf(rdent->d_name, "s%hu", &dseq);
                if (ncm==1) {
#ifdef DEBUG
                    printf("dseq=%hu, maxs=%hu\r\n",dseq,maxs);
#endif                    
                    if ( (dseq>maxs) && (dseq<kMaxSeqNum) ) {
                        maxs = dseq;
                    }
                }
            }
#ifdef DEBUG
             else {
                printf("not a dir\r\n");
            }
#endif    
        }
#ifdef DEBUG
        printf("closing directory %p\r\n",(void*)rd);
#endif    
        closedir(rd);
#ifdef DEBUG
        printf("Found max seq dir num %hu for run %u\r\n",maxs,run);
#endif

        Watchdog::kick(); // don't reset
        
        // open up the seq dir
        rdnms = GetSubDirFor(run, maxs, rdlen, true);
        rdnm = rdnms.c_str();
        // runXseq0 filename (fn points to a static buffer)
        const char* fn = GetOutFileName(macadr, run, maxs)
            + rdlen + 1; // take out dir and '/'s
        // don't compare seq#. don't use num of chars in case seq is >999
        const int32_t ncomp = strrchr(fn, 's') - fn;
        // open (or make) the run/seq dir
        rd = static_cast<FATDirHandle*>( OpenOrMakeAllDirs(rdnm) );
        // get the new sequence number (ok if it overflows this seq "bin")
        maxs=0;
        while ( (rdent = readdir(rd))!=NULL ) {
            Watchdog::kick(); // don't reset
            if ((rd->filinfo()->fattrib & AM_DIR)==0) {
                // is a file.
                // don't just count files, in case one seq was
                // transferred and erased in the middle of a run
                if (strncmp(rdent->d_name, fn, ncomp)==0) {
                    // allow for deleted files to make gaps.
                    // search for highest seq number and increase that
                    if (sscanf((rdent->d_name)+ncomp,"s%hu.dat",&dseq)==1) {
#ifdef DEBUG
                        printf("dn=%s, seq=%hu, __kMaxUShort=%hu\r\n",
                            rdent->d_name, dseq, __kMaxUShort);
#endif
                        if (dseq==__kMaxUShort) {
                            maxs = dseq;
                            break;
                        }
#ifdef DEBUG
                        printf("dseq=%hu, maxs=%hu\r\n",dseq,maxs);
#endif                    
                        if ( (dseq>=maxs) && (dseq<kMaxSeqNum)) {
                            maxs=dseq+1;
                        }
                        if (maxs==__kMaxUShort) {
                            break;
                        }
                    }
                }
            }
        }
        closedir(rd);
    } else {
        // no SD card
        
    }
#ifdef DEBUG
    printf("return maxs=%hu\r\n",maxs);
#endif
    return maxs;
}

FILE* SnSDUtils::OpenExistingFile(const char* name, const bool setcurrent,
                                  const bool redoDir) {
    Watchdog::kick(); // don't reset
    FILE* f = 0;
    if (InitSDCard()) {
        //if ((name!=NULL) && ((*name)!=0) ) { // simple check if filename not set
        if (name!=NULL) { // simple check if filename not set
#ifdef DEBUG
            printf("opening SD file. name=[%s]\r\n",name);
#endif
            f = OpenSDFile(name, "rb", redoDir);
            /*
            if (setcurrent) {
                fgCurFile = f;
                strncpy(fgCurFileName, name, kFNBufSize-1);
                fgCurSeq = GetSeqNumFromFileName(fgCurFileName);
            }
            */
        }
    }
    return f;
}

bool SnSDUtils::GetFullFilename(const char* name, std::string& ffn) {
#ifdef DEBUG
    printf("GetFullFilename (%s)\r\n",name);
#endif
    Watchdog::kick(); // don't reset
    bool ret = false;
    uint32_t run(0);
    uint16_t seq(0);
    const char* fn = strrchr(name, '/');
#ifdef DEBUG
    printf("w/o / : %s\r\n",fn);
#endif
    if (fn!=NULL) {
        ++fn; // remove the /
    } else {
        fn = name;
    }
    ffn = "";
    if (GetRunSeqFromFilename(fn, run, seq)) {
#ifdef DEBUG
        printf("got run=%d, seq=%hu\r\n",run,seq);
#endif
        uint32_t sdlen(0);
        std::string subds( GetSubDirFor(run,seq,sdlen, true) );
        const char* subd = subds.c_str();
#ifdef DEBUG
        printf("subd=%s\r\n",subd);
#endif
        ffn = subd;
        ffn += "/";
        ret = true;
    }
    ffn += fn;
#ifdef DEBUG
    printf("ffn=%s, ret=%d\r\n",ffn.c_str(),(int)ret);
#endif
    return ret;
}

FILE* SnSDUtils::OpenSDFile(const char* name, const char* mode,
                            const bool redoDir) {
    // TODO: check if we have memory?
#ifdef DEBUG
    printf("OpenSDFile: Trying to open %s.\r\n",name);
#endif
    Watchdog::kick(); // don't reset
    FILE* f = 0;
    if (InitSDCard()) {

        std::string ffn;
        bool ok = true;
#ifdef DEBUG
        printf("redoDir=%d\r\n",(int)redoDir);
#endif
        if (redoDir) {
#ifdef DEBUG
            printf("calling GetFullFilename\r\n");
#endif
            ok = GetFullFilename(name, ffn);
#ifdef DEBUG
            printf("ffn=%s\r\n",ffn.c_str());
#endif
        } else {
#ifdef DEBUG
            printf("looking for /\r\n");
#endif
            // make sure the directory exists
            const char* ld = strrchr(name, '/');
#ifdef DEBUG
            printf("ld=%p, ld-name = %d\r\n",ld,(int)(ld-name));
#endif
            if ((ld!=0) && (ld>name)) {
                std::string dn(name, ld-name);
                DIR* d = OpenOrMakeAllDirs(dn.c_str());
#ifdef DEBUG
            printf("d=%p\r\n",d);
#endif
                if (d!=NULL) {
                    closedir(d);
                }
            }
            // now just copy the (already-) full name
            ffn = name;
        }
        if ( ok && ffn.size()>0 ) {
#ifdef DEBUG
            printf("OpenSDFile: %s, mode %s\r\n",ffn.c_str(),mode);
#endif
            f = fopen(ffn.c_str(), mode);
            //setvbuf(f, 0, _IONBF, 0); // no buffering
#ifdef DEBUG
            printf("OpenSDFile: f=%p\r\n",(void*)f);
#endif
        }
#ifdef DEBUG
        printf("ffn=%s\r\n",ffn.c_str());
#endif
    }
    return f;
}

FILE* SnSDUtils::OpenNewOutputFile(const uint64_t macadr,
                                   const uint32_t run,
                                   const uint16_t minseq) {
    // opens a new file in the specified directory and writes this
    // this mbed's mac address as the first sizeof(uint64_t) bytes (i.e. 4 bytes)
    //
#ifdef DEBUG
    printf("getting seq num for run %u, minseq %hu\r\n",
        run, minseq);
#endif
    Watchdog::kick(); // don't reset
    fgCurFile = 0;
    fgCurSeq = GetSeqNum(macadr, run);
#ifdef DEBUG
    printf("fgCurSeq=%hu\r\n",fgCurSeq);
#endif
    if (InitSDCard()) {
        if (fgCurSeq<minseq) {
            fgCurSeq=minseq;
        }
#ifdef DEBUG
        printf("getting output file name\r\n");
#endif
        memset(fgCurFileName, 0, sizeof(char)*kFNBufSize);
        strncpy(fgCurFileName,GetOutFileName(macadr, run, fgCurSeq),kFNBufSize-1);
        //fprintf(stderr,"cur file = %s (%hu)\n\r",fgCurFileName,fgCurSeq);
#ifdef DEBUG
        printf("fgCurFileName=%s\r\n",fgCurFileName);
#endif
        fgCurFile = 0;
        if (fgCurFileName!=NULL) {
#ifdef DEBUG
            printf("opening SD file\r\n");
#endif
            fgCurFile = OpenSDFile(fgCurFileName, "wb", false);
            if (fgCurFile!=NULL && ferror(fgCurFile)==0) {
#ifdef DEBUG
                printf("Writing file header\r\n");
#endif
                WriteFileHeader(fgCurFile, macadr, run, fgCurSeq);
                
                AddToRunSeqList(run, fgCurSeq);
            }
        }
#ifdef DEBUG
        printf("fgCurFile=%p\r\n",(void*)fgCurFile);
#endif
    }
    return fgCurFile;
}

void SnSDUtils::PrintFilesInDirs(const char* dirname) {
    Watchdog::kick(); // don't reset
    if (InitSDCard()) {

        DIR* d;
        struct dirent* dent;
        Watchdog::kick(); // don't reset
        if ( (d = opendir( dirname ))!=NULL ) {
            FATDirHandle* dir = static_cast<FATDirHandle*>(d);
            while ( (dent = readdir(d))!=NULL ) {
                printf("dn=%s. datr=%02x. dir=%d\r\n",
                    dent->d_name,
                    dir->filinfo()->fattrib,
                    dir->filinfo()->fattrib & AM_DIR);
                if ( (dir->filinfo()->fattrib & AM_DIR)!=0 ) {
                    std::string dnm(dirname);
                    dnm += "/";
                    dnm += dent->d_name;
                    PrintFilesInDirs(dnm.c_str());
                }
            }
            closedir(d);
        }
    }
}

float SnSDUtils::GetFreeBytes() {
    Watchdog::kick(); // don't reset
    float frs(0);
    if (InitSDCard()) {

        FATFS* fs;
        DWORD fre_clust;
        f_getfree("0:",&fre_clust,&fs);
        frs = static_cast<float>(fs->csize)
            *static_cast<float>(fs->free_clust)
#if _MAX_SS != 512
            *(fs->ssize);
#else
            *512;
#endif
#ifdef DEBUG
        printf("free space = %g b (%g GB, %g MB, %g KB)\r\n",
            frs, frs/1073741824.0, frs/1048576.0, frs/1024.0);
#endif
    }
    return frs;
}

void SnSDUtils::GetDirProps(const char* dirname,
                            uint32_t& nfiles,
                            float& totbytes) {
    Watchdog::kick(); // don't reset
    nfiles = 0;
    totbytes = 0;
    if (InitSDCard()) {

        struct dirent* dent;
        FATDirHandle* d = static_cast<FATDirHandle*>( opendir(dirname) );
        if (d!=0) {
            while ( (dent = readdir(d))!=NULL ) {
                Watchdog::kick(); // don't reset
                if ( (d->filinfo()->fattrib & AM_DIR)!=0 ) {
                    // a subdirectory
                    std::string dnm(dirname);
                    dnm += "/";
                    dnm += dent->d_name;
                    uint32_t sdnf;
                    float    sdtb;
                    GetDirProps(dnm.c_str(), sdnf, sdtb);
                    nfiles += sdnf;
                    totbytes += sdtb;
                 } else {
                    // a file
                    ++nfiles;
                    totbytes += d->filinfo()->fsize;
                 }
            }
            closedir(d);
        }
    }
#ifdef DEBUG
    printf("GetDirProps: %s :: nf=%u, tb=%g\r\n",
        dirname, nfiles, totbytes);
#endif
}

bool SnSDUtils::WriteHeartbeatTo(FILE* file,
                                 const uint32_t time,
                                 const uint32_t num) {
    Watchdog::kick(); // don't reset
    if (file!=0) {
        if (InitSDCard()) {
        
            const bool r1 =
                SnHeaderFrame::WriteTo(file, SnHeaderFrame::kHeartbeatCode,
                             SnHeartbeatFrame::SizeOf(SnHeartbeatFrame::kIOVers));
            const bool r2 = 
                SnHeartbeatFrame::WriteTo(file, time, num);
            return (r1 && r2);
        }
    }
    return false;
}

bool SnSDUtils::WriteTrigWaitWinTime(FILE* file,
                                     SnClockSetFrame& clkset,
                                     const bool isStart) {
    Watchdog::kick(); // don't reset
    if (file!=0) {
        if (InitSDCard()) {
        
            bool ok = SnHeaderFrame::WriteTo(file,
                    (isStart) ? (SnHeaderFrame::kFileTrgStrtCode)
                              : (SnHeaderFrame::kFileTrgStopCode),
                    clkset.SizeOf());
            ok &= (SnCommWin::kOkMsgSent == clkset.WriteTo(file));
            return ok;
        }
    }
    return false;
}

bool SnSDUtils::WriteEventTo(FILE* efile, char* const evtBuf,
                             const SnEventFrame& evt,
                             const SnConfigFrame& conf) {
    Watchdog::kick(); // don't reset
    // write event to SD card
    bool ret = false;
    if (efile!=0) {
        if (InitSDCard()) {
            
            uint8_t sLoseLSB=0, sLoseMSB=0;
            uint16_t sWvBase=0;
            conf.GetPackParsFor(SnConfigFrame::kSDcard, sLoseLSB, sLoseMSB, sWvBase);
            SnHeaderFrame::WriteTo(efile, SnHeaderFrame::kEventCode, 
                                   evt.SizeOf(SnEventFrame::kIOVers, sLoseLSB, sLoseMSB));
            ret = evt.WriteTo(efile, evtBuf, sLoseLSB, sLoseMSB, sWvBase);
            fflush(efile);
        }
    }
    return ret;
}

bool SnSDUtils::WriteConfig(FILE* efile,
                            const SnConfigFrame& conf) {
    Watchdog::kick(); // don't reset
    if (efile!=0) {
        if (InitSDCard()) {

            SnHeaderFrame::WriteTo(efile, SnHeaderFrame::kConfigCode, 
                               conf.SizeOf(SnConfigFrame::kIOVers));
            conf.WriteTo(efile);
            return true;
        }
    }
    return false;
}

void SnSDUtils::DeleteFile(FILE*& f, const char* fname) {
#ifdef DEBUG
    printf("try to delete %s at %p\r\n",fname,f);
#endif
    Watchdog::kick(); // don't reset
    if (InitSDCard()) {

        if (f!=0) {
            fclose(f);
            f=0;
        }
        std::string fn("");
        if (GetFullFilename(fname, fn)) {
#ifdef DEBUG
            printf("calling remove [%s]\r\n",fn.c_str());
#endif
            remove(fn.c_str());
        }
    }
}

void SnSDUtils::DeleteFilesOfRun(const uint32_t run) {
#ifdef DEBUG
    printf("deleteing files of run %lu\r\n",run);
#endif
    Watchdog::kick(); // don't reset
    uint32_t rdlen(0);
    std::string rdnm( GetSubDirFor(run, 0, rdlen, false) );
    DeleteAllFiles(rdnm.c_str());
}

void SnSDUtils::DeleteAllFiles(const char* dirname) {
#ifdef DEBUG
    printf("deleting ALL files in %s\r\n",dirname);
#endif
    Watchdog::kick(); // don't reset
    if (InitSDCard()) {

        DIR* d;
        struct dirent* dent;
        if ( (d = opendir( dirname ))!=NULL ) {
            FATDirHandle* dir = static_cast<FATDirHandle*>(d);
            while ( (dent = readdir(d))!=NULL ) {
                Watchdog::kick(); // don't reset
                if ( (dir->filinfo()->fattrib & AM_DIR)!=0 ) {
                    // a subdirectory
                    std::string dnm(dirname);
                    dnm += "/";
                    dnm += dent->d_name;
                    // delete all the files in this new subdir
#ifdef DEBUG
                    printf("call DeleteAllFiles(%s)\r\n",dnm.c_str());
#endif
                    DeleteAllFiles(dnm.c_str());
                } else if (strncmp(dent->d_name, "SnEvts", 6)==0) {
                    // a data file (delete it)
                    const bool isCurFile = 
                        (strcmp(dent->d_name, GetCurFileName())==0);
                    if (isCurFile==false) { // don't delete the current file
                        FILE* f(0); // dummy
                        DeleteFile(f, dent->d_name);
                    }
                }
            } // loop over stuff in this dir
            closedir(d);
            DeleteDirIfEmpty(dirname);
        }
    }
}

bool SnSDUtils::DeleteDirIfEmpty(const char* dirname) {
#ifdef DEBUG
    printf("DeleteDirIfEmpty(%s)\r\n",dirname);
#endif
    Watchdog::kick(); // don't reset
    bool doDel = false;
    if (InitSDCard()) {

        DIR* d;
        struct dirent* dent;
        if ( (d = opendir(dirname))!=NULL ) {
            dent = readdir(d);
            if ( dent==NULL ) {
                // then this directory is empty
                static const size_t subdsz = strlen(kSDsubDir);
                // just double check that this directory is
                // a subdirectory of the data dir
                if (strlen(dirname)>subdsz) {
                    doDel = true;
                }
            } // else this dir isn't empty
            closedir(d);
            if (doDel) {
#ifdef DEBUG
                printf("removing directory [%s]\r\n",dirname);
#endif
                remove(dirname);
            }
        }
    }
    return doDel;
}

SnCommWin::ECommWinResult
SnSDUtils::SendOneFile(const char* dfn,
                       SnCommWin* comm,
                       const uint32_t timeout,
                       char* const buf,
                       const uint32_t bsize,
                       const SnConfigFrame& curConf,
                       SnEventFrame& evt,
                       SnPowerFrame& pow) {

#ifdef DEBUG
    printf("SendOneFile (%s)\r\n",dfn);
#endif
    Watchdog::kick(); // don't reset
    SnCommWin::ECommWinResult res = SnCommWin::kOkMsgSent;
    
    // open the file
    const bool isCurFile = (strcmp(dfn, GetCurFileName())==0);
    FILE* f(0);
    if (isCurFile) {
        // file must already be written out!
        f = GetCurFile();
    } else {
        f = OpenExistingFile(dfn, false, false);
    }

#ifdef DEBUG
    printf("SendOneFile: isCurFile=%d, f=%p, fn=%s\r\n",
        (int)isCurFile, (void*)f, dfn);
#endif

    uint8_t hndres = SnHeaderFrame::kHnShFailNonCode;
    if (f!=0) {
#ifdef DEBUG
        printf("SendOneFile: calling SendDataFromFile\r\n");
#endif
        res = comm->SendDataFromFile(f, dfn,
                             curConf, evt, pow, buf, bsize,
                             0, timeout,
                             &hndres);

        if (isCurFile) {
            // move (back) to the end of the file
            // altho hopefully no writing will happen after this
            fseek(fgCurFile, 0, SEEK_END);
        }
    }                
#ifdef DEBUG
    printf("isCurFile=%d, res=%d, deleting=%d, hndres=%02x\r\n",
        (int)isCurFile, (int)res, (int)(curConf.IsDeletingFiles()),
        hndres);
#endif
    
    return res;
}

bool SnSDUtils::ClearRunSeqList() {
    Watchdog::kick(); // don't reset
    if (InitSDCard()) {
        FILE* rslistf = fopen(kRunSeqListFilenm,"w");
        const bool ok = rslistf!=0;
        fclose(rslistf);
#ifdef DEBUG
        printf("ClearRunSeqList. ok=%s\r\n",(ok?"true":"false"));
#endif
        return ok;
    }
    return false;
}

bool SnSDUtils::AddToRunSeqList(const uint32_t run,
                                const uint16_t seq) {
    Watchdog::kick(); // don't reset
    if (InitSDCard()) {
        FILE* rslistf = fopen(kRunSeqListFilenm,"a");
        bool ok = false;
        if (rslistf!=0) {
            ok  = fprintf(rslistf, "%u %hu\n", run, seq) > 0;
            ok &= 0==ferror(rslistf);
#ifdef DEBUG
            printf("AddToRunSeqList: run=%u, seq=%hu, ok=%s\r\n",
                run, seq, (ok?"true":"false"));
#endif
        }
        fclose(rslistf);
        return ok;    
    }
    return false;
}

SnCommWin::ECommWinResult
SnSDUtils::SendFileWithRunSeq(SnCommWin* comm,
                              const uint32_t timeout,
                              char* const buf,
                              const uint32_t bsize,
                              const SnConfigFrame& curConf,
                              SnEventFrame& evt,
                              SnPowerFrame& pow,
                              const uint32_t run,
                              const uint16_t seq) {

#ifdef DEBUG
    printf("SendFileWithRunSeq\r\n");
#endif
    Watchdog::kick(); // don't reset

    // get the file name
    std::string dfn = 
        GetOutFileName(SnConfigFrame::GetMacAddress(), run, seq);
    
    // send it
    const SnCommWin::ECommWinResult res = 
        SendOneFile(dfn.c_str(), comm, timeout, buf, bsize,
                    curConf, evt, pow);
    
    // see if we need to remove this directory now
    if (curConf.IsDeletingFiles()) {
        // get run/seq directory name
        uint32_t rdlen(0);
        std::string rdnm( GetSubDirFor(run, seq, rdlen, true) );
#ifdef DEBUG
        printf("Checking removal of dir [%s]\r\n",rdnm.c_str());
#endif
        if ( DeleteDirIfEmpty(rdnm.c_str()) ) {
            // removed the seq dir. do we need to remove
            // the run dir too?
            rdnm = GetSubDirFor(run, seq, rdlen, false);
#ifdef DEBUG
            printf("Checking removal of dir [%s]\r\n",rdnm.c_str());
#endif
            DeleteDirIfEmpty(rdnm.c_str());
        }
    }
    
    return res;
}

SnCommWin::ECommWinResult
SnSDUtils::SendFilesInRunSeqList(SnCommWin* comm,
                                 const uint32_t timeout,
                                 char* const buf,
                                 const uint32_t bsize,
                                 const SnConfigFrame& curConf,
                                 SnEventFrame& evt,
                                 SnPowerFrame& pow) {
#ifdef DEBUG
    printf("SendFilesInRunSeqList\r\n");
#endif

    SnCommWin::ECommWinResult rs = SnCommWin::kOkMsgSent;

    Watchdog::kick(); // don't reset

    if (InitSDCard()) {

        // open up the run/seq list file and send each corresponding file
        FILE* rslistf = fopen(kRunSeqListFilenm,"r");
#ifdef DEBUG
        printf("fslistf=%p\r\n",(void*)rslistf);
#endif
        if (rslistf!=0) {
            uint32_t run(0);
            uint16_t seq(0);
            while (    (feof(rslistf)==0)
                    && (ferror(rslistf)==0) ) {
#ifdef DEBUG
                printf("feof=%d, ferror=%d\r\n",
                    (int)(feof(rslistf)), (int)(ferror(rslistf)));
#endif            
                Watchdog::kick(); // don't reset

                const int nfilled = fscanf(rslistf, "%u %hu\n", &run ,&seq);
    
#ifdef DEBUG
                printf("nfilled=%d\r\n", nfilled);
#endif            
    
                if ( 2==nfilled ) {
    
#ifdef DEBUG
                    printf("run=%u, seq=%hu\r\n",run,seq);
#endif            
                    
                    SnCommWin::ECommWinResult res =
                        SendFileWithRunSeq(comm, timeout, buf, bsize,
                                           curConf, evt, pow,
                                           run, seq);
                        
                    if ((res<rs) || (res==SnCommWin::kOkStopComm)) {
                        rs = res;
                    }
                    // don't necessarily stop if rs is bad. don't want one bad file to
                    // prevent sending the others
                    if (rs<=SnCommWin::kFailTimeout) {
                        break;
                    } else if (rs==SnCommWin::kOkStopComm) {
                        break;
                    }
                    
                }
                
            }
            
            // do we need to clear the list?
            if (curConf.IsRunSeqListOneCommWinOnly()==false) {
                if (rs >= SnCommWin::kOkMsgSent) {
                    // all sent ok
                    ClearRunSeqList();
                }
            }
    
        }
        fclose(rslistf);
    }
    return rs;
}

SnCommWin::ECommWinResult
SnSDUtils::SendPartOfRun(SnCommWin* comm,
                         const uint32_t timeout,
                         char* const buf,
                         const uint32_t bsize,
                         const SnConfigFrame& curConf,
                         SnEventFrame& evt,
                         SnPowerFrame& pow,
                         const uint32_t run,
                         const uint16_t minseq,
                         const uint16_t maxseq) {
    // send files with run number 'run'
    // and seq number in [minseq,maxseq] (min/max inclusive)
    
#ifdef DEBUG
    printf("SendPartOfRun\r\n");
#endif

    Watchdog::kick(); // don't reset
    
    SnCommWin::ECommWinResult rs = SnCommWin::kOkMsgSent;

    for (uint16_t seq=minseq; seq<=maxseq; ++seq) {
        
        Watchdog::kick(); // don't reset
        
        SnCommWin::ECommWinResult res =
            SendFileWithRunSeq(comm,
                               timeout, buf, bsize,
                               curConf, evt, pow,
                               run, seq);
            
        if ((res<rs) || (res==SnCommWin::kOkStopComm)) {
            rs = res;
        }
        // don't necessarily stop if rs is bad. don't want one bad file to
        // prevent sending the others
        if (rs<=SnCommWin::kFailTimeout) {
            break;
        } else if (rs==SnCommWin::kOkStopComm) {
            break;
        }
        
    }
    
    return rs;
}


SnCommWin::ECommWinResult SnSDUtils::SendAllOfRun(SnCommWin* comm,
                                                  const uint32_t timeout,
                                                  char* const buf,
                                                  const uint32_t bsize,
                                                  const SnConfigFrame& curConf,
                                                  SnEventFrame& evt,
                                                  SnPowerFrame& pow,
                                                  const uint32_t runnum) {
    // send all files in a run
    
    Watchdog::kick(); // don't reset
    
    // get the run dir
    uint32_t rdlen(0);
    std::string rdnms ( GetSubDirFor(runnum, 0, rdlen, false) );
    return SendAllFiles(comm, timeout, buf, bsize, curConf, evt, pow,
                        rdnms.c_str());
}

SnCommWin::ECommWinResult SnSDUtils::SendAllFiles(SnCommWin* comm,
                                                  const uint32_t timeout,
                                                  char* const buf,
                                                  const uint32_t bsize,
                                                  const SnConfigFrame& curConf,
                                                  SnEventFrame& evt,
                                                  SnPowerFrame& pow,
                                                  const char* dirname) {
    // send all files in the specified directory
    
    Watchdog::kick(); // don't reset
    
    SnCommWin::ECommWinResult rs  = SnCommWin::kUndefFail;
    
    if (InitSDCard()) {

        rs  = SnCommWin::kOkMsgSent;
    
        DIR* d;
        struct dirent* dent;
        if ( (d = opendir( dirname ))!=NULL ) {
            FATDirHandle* dir = static_cast<FATDirHandle*>(d);
            while ( (dent = readdir(d))!=NULL ) {
                Watchdog::kick(); // don't reset
                SnCommWin::ECommWinResult res = rs;
                if ( (dir->filinfo()->fattrib & AM_DIR)!=0 ) {
                    // a subdirectory
                    std::string dnm(dirname);
                    dnm += "/";
                    dnm += dent->d_name;
                    // send all the files in this new subdir
                    res = SendAllFiles(comm, timeout, buf, bsize, 
                                 curConf, evt, pow,
                                 dnm.c_str());
                } else if (strncmp(dent->d_name, "SnEvts", 6)==0) {
                    // a data file (send it)
                    res = SendOneFile(dent->d_name, comm, timeout, buf, bsize,
                                      curConf, evt, pow);
                }
                if ((res<rs) || (res==SnCommWin::kOkStopComm)) {
                    rs = res;
                }
                // don't necessarily stop if rs is bad. don't want one bad file to
                // prevent sending the others
                if (rs<=SnCommWin::kFailTimeout) {
                    break;
                } else if (rs==SnCommWin::kOkStopComm) {
                    break;
                }
            } // loop over stuff in this directory
            closedir(d);
            // see if we need to remove this directory now
            if (curConf.IsDeletingFiles()) {
                DeleteDirIfEmpty(dirname);
            }
        }
    }
    return rs;
}