Last Feature Attempt (For Now) - promise!

OK, this is the last feature I would like to get working - manual deletion of all save files on the SD card, regardless of which folder they are in.

I’ve tried this, but although it looks like the system is happy to do it, deletion always fails.
Main code is here:

 void deleteSaveFile(const char* filename) {
    if (MountSDCard) {
      Serial.print("SD card mounted successfully: ");

      Serial.println(filename);
    } else {
      Serial.print("Failed to mount SD card: "); 
    }        
    
    bool result = LSFS::Remove(filename);
          Serial.print("Attempting to delete file...: ");
          
    if (result) {
//      PlaySound("restorey.wav");
      Serial.print("File deleted: ");
      Serial.println(filename);
    } else {
//      PlaySound("restoren.wav");
      Serial.print("Failed to delete file: ");
      Serial.println(filename);
    }
    }

Event Handler here. (I’ve scattered test files about to establish which path descriptions work - obviously these will be changed to preset.ini, curstate.ini etc. once everything is working):

case EVENTID(BUTTON_POWER, EVENT_FOURTH_HELD, MODE_OFF):
    deleteSaveFile("myfile1.txt");
    deleteSaveFile("Save1/myfile2.txt");
    deleteSaveFile("/myfile1.txt");
    deleteSaveFile("PROFFIE/myfile1.txt");
    deleteSaveFile("/myfile5.txt");
    deleteSaveFile("/myfile6.txt");
      return true;

Serial monitor is showing the button press, saying the SD card has successfully mounted, and printing out “Attempting to delete file”, but the deletion itself is failing.

10:58:28.853 -> EVENT: Power-Held#4 millis=46180
10:58:28.853 -> SD card mounted successfully: myfile1.txt
10:58:28.853 -> Attempting to delete file: Failed to delete file: myfile1.txt
10:58:28.853 -> SD card mounted successfully: Save1/myfile2.txt
10:58:28.853 -> Attempting to delete file: Failed to delete file: Save1/myfile2.txt
10:58:28.853 -> SD card mounted successfully: /myfile1.txt
10:58:28.853 -> Attempting to delete file: Failed to delete file: /myfile1.txt
10:58:28.853 -> SD card mounted successfully: PROFFIE/myfile1.txt
10:58:28.853 -> Attempting to delete file: Failed to delete file: PROFFIE/myfile1.txt
10:58:28.853 -> SD card mounted successfully: /myfile5.txt
10:58:28.853 -> Attempting to delete file: Failed to delete file: /myfile5.txt
10:58:28.853 -> SD card mounted successfully: /myfile6.txt
10:58:28.853 -> Attempting to delete file: Failed to delete file: /myfile6.txt
10:58:30.341 -> EVENT: Power-Released#4 millis=47664
10:58:30.341 -> EVENT: Power-Released millis=47664

So I guess the heart of it is this line - bool result = LSFS::Remove(filename); - as the rest is pretty much debug and serial comments, but it doesn’t seem to be doing anything.

Am I at least in the right ballpark by trying to use LSFS, or am I barking up completely the wrong tree?

  1. MountSDCard is a function, you’re using it like a bool. Maybe what you want is something like:
MountSDCard();
if (!LSFS::IsMounted()) {
   Serial.println("Failed to mount SD card.");
   return;
}
  1. You need to lock the sd card before using any of these functions. Otherwise the sound system might try to use the SD card at the same time, which causes major problems. (Crash, corruption, etc.)

  2. After deleting the files, you need to reset everything in memory. Probably the easy thing to do is to just reboot proffieOS.

It’s the right tree, you just have to make sure not to sit on the branch you’re cutting…

1 Like

LOL! I hear ya! Though I’m getting used to falling on my backside with this stuff! :laughing:

Surely if I lock it (once I figure out how to do that), that would prevent the system from altering (deleting) any files on it wouldn’t it? Are there gradations of lock that allow deleting but not reading? Or are we talking about something that effectively suspends all other SD processes so that the system can get in and delete the files unhindered?

The idea behind “locking” is that you basically tell everything else that it’s being used. You can do whatever you need to during that time, and then you “unlock” it to let everything else know you’re done and that things can continue.

Of course everything else doesn’t need to know “who” locked it, just that it’s in use at the moment and shouldn’t try to do anything with it. Other things that need it will wait until they can lock it themselves in order to do something.

Think of it like checking out a book from a library. Your little function here needs to check out the book to have access to it. Certainly you could go look over someone else’s shoulder while they have it, but you’ll interfere with them and probably make them angry, so instead you (or if you have it something else) wait for the book to be returned, then check it out yourself.

In the realm of software, it’s not locking in the sense of stopping things from happening outright as much as it is a form of notification, if you will.

2 Likes

Got it! Thanks @ryryog25 - that does make sense. :slight_smile:
Just got to figure out how to do it now! LOL! Though I see quite a few references to it in lsfs.h, so I’m guessing the answer is in there somewhere. Will do some experimenting this evening. :slight_smile:
Thanks again. :slight_smile:

1 Like

I love this idea, can’t wait to see what you come up with.

OMG! I’ve only gone and managed to do it!!! :open_mouth: :smiley:

So I’ve tucked it away on a button press that’s unilkely to be fired by accident, and you have to define it to enable it. But when you run it, it deletes all save files from both the root level of the SD card and the first level of directories from the root. Once the process has run, it plays a reset.wav file which I’ve made which says, “Restoring factory defaults… completed. Please reboot your saber.” If a reset sound file isn’t available, it plays the font ident instead. (I felt it needed to play something every time, so people knew when to release the button after the last hold). Since a reboot is probably quite important after this process, there might be a case for adding a talkie comment from the system saying, “Reboot now”, (assuming that’s possible - I’m guessing it is?) so that it wouldn’t rely on the presence of a wav file to tell people they need to do it. But that’s a stretch too far for my abilities, so I’m pretty content to stick with it as it is.

If anyone’s interested, here’s the business end of it:

#ifdef SABERSENSE_ENABLE_RESET
    //  To restore system to 'factory defaults' by deleting save files.
    //  Deletes files in root and all first level directories. 
    //  Requires reboot after deletions.
    case EVENTID(BUTTON_POWER, EVENT_FOURTH_HELD, MODE_OFF): {
      // Lock the SD card to prevent other operations during deletion.
      if (LSFS::Begin()) {
        const char* filesToDelete[] = {
          "curstate.ini",
          "curstate.tmp",
          "preset.ini",
          "preset.tmp",
          "global.ini",
          "global.tmp"
        };
        // Delete files from the root directory
        for (const char* targetFile : filesToDelete) {
          if (LSFS::Exists(targetFile)) {
            LSFS::Remove(targetFile);
              Serial.print("Deleted from root: ");
              Serial.println(targetFile);
              }
           }
        // Find all immediate subdirectories of the root and delete files
        LSFS::Iterator dirIterator("/");  // Iterator for the root directory
          while (dirIterator) {
            const char* subdirName = dirIterator.name();
            if (dirIterator.isdir()) {
              // Construct the full path to the subdirectory
              PathHelper subdirPath("/");
              subdirPath.Append(subdirName);
                // Iterate over the files to delete in this subdirectory
                for (const char* targetFile : filesToDelete) {
                  PathHelper filePath(subdirPath); 
                  filePath.Append(targetFile); 
                    // If the file exists in this subdirectory, delete it
                    if (LSFS::Exists(filePath)) {
                        LSFS::Remove(filePath);
                        Serial.print("Deleted from ");
                        Serial.print(subdirPath);
                        Serial.print(": ");
                        Serial.println(targetFile);
                    }
                  }
                }
              ++dirIterator;  // Move to the next entry in the root directory
            }
        // Unlock the SD card after deletion is complete
        LSFS::End();
          // Sound file to instruct user to reboot hilt after deletions.
          if (SFX_reset) {
            hybrid_font.PlayCommon(&SFX_reset); 
          } else {
            // Plays font ident if RESET file not available.
            SaberBase::DoNewFont();  
          }  
      } else {
        Serial.println("Failed to lock SD card for deletion.");
      }
    break;
  }
#endif

That’s pretty much every feature I want now working. :smiley: The only tiny detail I’d like to add is the option to play the font file after the array file on array switches (enabled by define) but as I mentioned on the other thread, that proved trickier than I first envisaged. Will give myself a day off and may come back to that one (or not - we’ll see). But for now, this is one happy beginner coder! :smiley:

1 Like

Curious if this will lead to file corruption or issues with ini and tmp files over time. Especially if done multiple times without proper reformatting of the SD. I recall having SD cards corrupt just from deleting sounds from fonts and re-adding without properly reformatting the SD. I’d think this is not dissimilar.

I don’t know talkies, but you could use sos in morse code (I think most people would recognise it):

void sos() { // 600 is the typical aviation morse code frequency & 800 is the frequency usualy used in movies for morse code.
    beeper.Beep(0.2, 800); //dot (.)
    beeper.Silence(0.15);  //pause between (.) or (-)
    beeper.Beep(0.2, 800);
    beeper.Silence(0.15);
    beeper.Beep(0.2, 800);
    beeper.Silence(0.5);   //pause between characters
    beeper.Beep(0.6, 800); //dash (-)
    beeper.Silence(0.15);
    beeper.Beep(0.6, 800);
    beeper.Silence(0.15);
    beeper.Beep(0.6, 800);
    beeper.Silence(0.5);
    beeper.Beep(0.2, 800);
    beeper.Silence(0.15);
    beeper.Beep(0.2, 800);
    beeper.Silence(0.15);
    beeper.Beep(0.2, 800);
    }

Or for a more memory efficient function (and less repetitive, more elegant, but harder to read):

void sos() {
    constexpr float dotDuration = 0.2; // Duration of a dot in seconds
    constexpr float dashDuration = 0.6; // Duration of a dash in seconds
    constexpr float pauseBetweenBeats = 0.15; // Pause between dots or dashes
    constexpr float pauseBetweenChars = 0.5; // Pause between characters
    constexpr float frequency = 800; // Beep frequency in Hz

    // Play the SOS pattern: ... --- ...
    for (int i = 0; i < 3; ++i) { // Three dots
        beeper.Beep(dotDuration, frequency);
        if (i < 2) beeper.Silence(pauseBetweenBeats); // No pause after the last dot
    }

    beeper.Silence(pauseBetweenChars); // Pause between S and O

    for (int i = 0; i < 3; ++i) { // Three dashes
        beeper.Beep(dashDuration, frequency);
        if (i < 2) beeper.Silence(pauseBetweenBeats); // No pause after the last dash
    }

    beeper.Silence(pauseBetweenChars); // Pause between O and S

    for (int i = 0; i < 3; ++i) { // Three dots
        beeper.Beep(dotDuration, frequency);
        if (i < 2) beeper.Silence(pauseBetweenBeats); // No pause after the last dot
    }
}
1 Like

You’d just insert the option to call DoNewFont() inside the define guard after array plays . For this function (assuming this is what you’re talking about):


  void TriggerBladeID() {
    FindBladeAgain();
    if (SFX_array) {
      SFX_array.Select(current_config - blades);
      hybrid_font.PlayPolyphonic(&SFX_array);
    } else {
      SaberBase::DoNewFont();
    }
  }

you’d do something like this (although I think the playback would overlap)


  void TriggerBladeID() {
    FindBladeAgain();
    if (SFX_array) {
      SFX_array.Select(current_config - blades);
      hybrid_font.PlayPolyphonic(&SFX_array);
#ifdef PLAY_FONT_AFTER_ARRAY_SOUND
      SaberBase::DoNewFont();
#endif
    } else {
      SaberBase::DoNewFont();
    }
  }

To avoid overlap, you could look into a call from Loop() and a mechanism that will wait until array.wav finishes before playing font.wav.
I’ve done something like this in my prop called DelayTimer.

The manual switch one could be like:

#ifdef SAVE_PRESET
    savestate_.ReadINIFromSaveDir("curstate");
    if (SFX_array) {
      SetPreset(savestate_.preset, false);
      SFX_array.Select(current_config - blades);
      hybrid_font.PlayPolyphonic(&SFX_array);
#ifdef PLAY_FONT_AFTER_ARRAY_SOUND
      SaberBase::DoNewFont();
#endif
   } else {
      SetPreset(savestate_.preset, true);
    }
#else

    if (SFX_array) {
      SetPreset(0, false);
      SFX_array.Select(current_config - blades);
      hybrid_font.PlayPolyphonic(&SFX_array);
#ifdef PLAY_FONT_AFTER_ARRAY_SOUND
      SaberBase::DoNewFont();
#endif
  } else {
      SetPreset(0, true);
    }
#endif // SAVE_PRESET

It’s early days, admittedly, but I don’t see why it would as it completely deletes all instances of the six named files (so long as they’re not buried too deep in the file structure). Whenever I upload a config I always delete all save files manually beforehand and it hasn’t caused corruption (other things have, but not that). That said, I think the reboot immediately afterwards is quite important, hence a talkie announce, if it’s possible, might be justified. But ultimately time will tell. Like, you, I’ll be interested to see if other people have any issues with it.

Yeh I tried that, but both effects did indeed play at the same time.
And I did look into the loop thing, but it got quite complicated quite quickly. Does your last piece of code above incorporate it? I can’t see much difference, but if it’s easy to do, I’ll have another crack at it when I get home today, as I would like to get it working.

This is not how you lock the SD card.

This is how you look the SD card:

Why not actually reboot it? Here is how you do it:

Although you might need to do some polling if you want to wait for “reset.wav” to finish playing first. Although in this particular case using delay() might be warranted since that will also prevent the saber from trying to create a new save file while the sound is playing.

1 Like

Ah OK. I’ll change that.
So what does LSFS::Begin() do, given that it does seem to work?
And when I want to unlock the SD at the end, it must be LOCK_SD(false); ?

I did wonder about whether there was a way to build the reboot into the routine. So would this actually perform a full reboot including playing the boot sound effect? If so, that probably negates the need for a confirmation reset.wav (although it would be nice to have the saber confirm what’s happening, but as mentioned that’s file-dependent so might not work well if people use other reset.wavs).

Thanks for the notes though Prof - will take a proper look later today. :slight_smile:

LSFS::Begin() calls SD.begin() which initializes the SD library.
I’m surprised that it works.

Yes

3 Likes