Arduino random file on SD: rewindDirectory - file

I am building a fancy alarm clock with an Arduino, Mp3 player and an LED matrix to display the time and to serve as a soft wake up light. I intend to make this project accessible and open source when it's done. I want the Arduino to chose randomly an mp3 file located on the SD card and play it, until the end of the song and then chose another one until you push the stop button. But I have trouble with the random file selection:
To have something that doesn't take too much memory I thought of a way to do the selection:
The mp3 files present on the root folder of the SD are counted during initialisation using File::openNextFile() until it return the null object. ->int n_files
Select a random number between 1 and n_files. -> int rand_song
Open the rand_song file using a loop of File::openNextFile() until the wanted file is reached (might not be so efficient but it doesn't matter if it takes a couple of seconds). -->File chosen_rand
Give the name of chosen_rand.name() to musicPlayer.startPlayingFile() so the song plays.
Go back to 1) if end of song reached
So first question, do you think it makes sense to do it this way?
Then, I did a working implementation of the above algorithm missing 4th point (see Code1). But when I add the startPlaying(), a problem occurs: between two call of the function selecting the rand_song file, the position is conserved even with calling File::rewindDirectory() (see Code2).
This lead to my second question, what is the righteous way to use File, SD and File::rewindDirectory together?
I already tried a couple a thing like closing the files (because apparently only one at the time must be open) (see Code3). But always the same, it is working for the first file, but as the rewind doesn't work, it started from the middle of the folder and then hit the end of the folder.
I hope I am precise enough and that I am not missing the essential to make it work.
void setup()
{ ... intializations done before
// Count the number of files
File folder = SD.open(music_folder);
n_files = countMP3File(folder);
folder.close();
Serial.print("Number of MP3 files: "); Serial.println(n_files);
}
int countMP3File(File dir)
{
int counter = 0;
while(true)
{
File entry = dir.openNextFile();
if (! entry)
{
dir.rewindDirectory();
break;
}
Serial.println(entry.name());
if(strstr(entry.name(), extansion) != NULL)
counter++;
entry.close();
}
Serial.println("----------------");
return counter;
}
File selectFileN(int number, File dir)
{
int counter = 0;
File return_entry;
while(true)
{
File entry = dir.openNextFile();
if (! entry)
{
Serial.println("Last file reached");
dir.rewindDirectory();
break;
}
Serial.println(entry.name());
if(strstr(entry.name(), extansion) != NULL)
counter++;
if(counter==number)
{
return_entry = entry;
dir.rewindDirectory();
break;
}
entry.close();
}
return return_entry;
}
Code1 (working)
void loop()
{
int i, rand_song;
File folder = SD.open(music_folder);
File chosen_rand;
rand_song = random(0, n_files)+1;
Serial.print("Random number: "); Serial.println(rand_song);
chosen_rand = selectFileN(rand_song, folder);
if(chosen_rand)
Serial.print("Random file name: "); Serial.println(chosen_rand.name());
folder.close();
chosen_rand.close();
Serial.print("playingMusic= "); Serial.println(musicPlayer.playingMusic);
Serial.println();
Serial.println();
delay(200);
}
Code2 (not working)
void loop()
{
int i, rand_song;
File folder = SD.open(music_folder);
File chosen_rand;
rand_song = random(0, n_files)+1;
Serial.print("Random number: "); Serial.println(rand_song);
chosen_rand = selectFileN(rand_song, folder);
if(chosen_rand)
Serial.print("Random file name: "); Serial.println(chosen_rand.name());
//Added code here
if(!musicPlayer.startPlayingFile(chosen_rand.name()))
{
Serial.println("Could not open mp3 file");
}
else Serial.println("Stated playing!");
//End added code
folder.close();
chosen_rand.close();
Serial.print("playingMusic= "); Serial.println(musicPlayer.playingMusic);
Serial.println();
Serial.println();
//Added code here
delay(2000);
musicPlayer.stopPlaying();
//End added code
delay(200);
}
Code3 (not working)
void loop()
{
int i, rand_song;
File folder = SD.open(music_folder);
File chosen_rand;
rand_song = random(0, n_files)+1;
Serial.print("Random number: "); Serial.println(rand_song);
chosen_rand = selectFileN(rand_song, folder);
if(chosen_rand)
Serial.print("Random file name: "); Serial.println(chosen_rand.name());
//Added code to close file before playing it
char * namefile = (char*)malloc(15);
strcpy(namefile, chosen_rand.name());
folder.close();
chosen_rand.close();
//End added code
if(!musicPlayer.startPlayingFile(chosen_rand.name()))
{
Serial.println("Could not open mp3 file");
}
else Serial.println("Stated playing!");
free(namefile);
Serial.print("playingMusic= "); Serial.println(musicPlayer.playingMusic);
Serial.println();
Serial.println();
delay(2000);
musicPlayer.stopPlaying();
delay(200);
}

Alright, I found the problem. As only one File of the SD card should be open at a time, when the file is open by the musicPlayer, the rewindDirectory fails (I think, correct me if I am wrong, but my tests make me believe that). So the fix is quiet easy and logic, just put the rewindDirectory after the musicPlayer closed the file, so a good place to do that is just before my function that select the random file.
So here's the working loop:
void loop()
{
int i, rand_song;
File folder = SD.open(music_folder);
File random_file;
rand_song = random(0, n_files)+1;
Serial.print("Random number: "); Serial.println(rand_song);
folder.rewindDirectory();
random_file = selectFileN(rand_song, folder);
folder.close();
if(!musicPlayer.startPlayingFile(random_file.name()))
{
Serial.println("Could not open mp3 file");
}
else Serial.println("Stated playing!");
random_file.close();
Serial.print("playingMusic= "); Serial.println(musicPlayer.playingMusic);
Serial.println();
Serial.println();
delay(2000);
musicPlayer.stopPlaying();
delay(200);
}

Related

How do I read properly from a binary file?

I already cut my code to minimum and I think that is everything I need for my problem.
I want the user to be able to read this structure:
typedef struct {
char movieTitle[30];
unsigned int yearPublished;
char director[50];
char genre[50];
float profit;
} movie_t;
from a binary file that has already 4 movies in it, but should also be able to store max 100 movie structures like defined here:
//global var.
// ================================================================
#define MAX_STORAGE 100
movie_t movieData[MAX_STORAGE];
movie_t movieRecord;
unsigned int movieCount = 0;
With my code that I already wrote below, I can't somehow read all movies out of the file, because my "count checker" tells me every-time that just one movie was read. Also just one movie is showing in my console output code below my "readFile" function.
// main functions --- read from file
// ================================================================
int
readFile(const char* fileName)
{
//open file
FILE* read = fopen(fileName, "rb");
if (!read)
{
printf("ERROR: Could not open the file!\n");
return -1;
}
else
{
printf("%s were opened successfully.\n\n", fileName);
}
//read from file
int count = 0;
while (!feof(read))
{
size_t count = fread(&movieRecord, sizeof(movieRecord), 1, read);
if (count == 0)
{
printf("ERROR: Read process was unsuccessful or incomplete!\n");
}
if (count == 1)
{
printf("Successfully read %i movie.\n", count);
}
else
{
printf("Successfully read %i movies.\n", count);
}
return count;
}
fclose(read);
return 0;
}
This is the console output where ALL movies from the file should be showing up. Also they should be showing with a number (Num.) from 1 - 100.
// main functions --- console Output
// ================================================================
void
consoleOutput(movie_t movieData2, unsigned index)
{
printf("MOVIE DATA Num. %u\n\n", index);
printf("Movie title : %s\n", movieData2.movieTitle);
printf("Publishing year : %u\n", movieData2.yearPublished);
printf("Director : %s\n", movieData2.director);
printf("Genre : %s\n", movieData2.genre);
printf("Profit : %.2f\n", movieData2.profit);
}
This is how my main function looks like if you would like to see it:
// MAIN
// ================================================================
int main(void)
{
// defaultname for file
char fileName[50] = "movies.dat";
// variable for loop
int stopLoop = 0;
// loop
do {
// Output menu
printf("\nMENU:\n");
printf("(a) Show all movies\n");
printf("(o) Read moviefile\n");
printf("(q) Exit programm\n");
printf("\nYour choice: ");
// User input
char ch = _getch();
printf("%c\n\n", ch);
// Switch case for user input
switch (ch)
{
case 'q': // Exit programm
stopLoop = 1;
break;
case 'a': // Show all movies
consoleOutput(movieRecord, movieCount = readFile(fileName));
break;
case 'o': // Read moviefile
readFile(fileName);
break;
default:
printf("==> Invalid input!\n");
}
} while (!stopLoop);
return 0;
}
Side-note: because the user is able to input new movies, they should be able to save that data to the same file from where they read it. But that's something I want to test myself first, before I ask around for help.
You are returning from your loop body after reading only one entry. If you really wish to read all the entries in the file, you'll need to change your reading loop to reading into your array, which you can in fact get rid of all together thanks to how fread works:
// main functions --- read from file
// ================================================================
int
readFile(const char* fileName)
{
//open file
FILE* read = fopen(fileName, "rb");
if (!read)
{
printf("ERROR: Could not open the file!\n");
return 0; // note that since you'll probably assign the output to movieCount, it might be better to return 0 on failure
}
else
{
printf("%s were opened successfully.\n\n", fileName);
}
// Here's where the problem, you only ever read out one movie entry into movieRecord and returned
// You can use fread to read out the entire file into the array instead
// It would also be better not to use a global variable for this, instead taking in a pointer to an movie_t array to readFile instead but I left it using the global as per the original program
size_t count = fread(movieData, sizeof(movie_t), MAX_STORAGE, read);
if (ferror(read))
{
printf("ERROR: Read process was unsuccessful or incomplete!\n");
fclose(read);
return 0;
}
if (count == 1)
{
printf("Successfully read %i movie.\n", count);
}
else
{
printf("Successfully read %i movies.\n", count);
}
fclose(read);
return count;
}
From here, you'll have movieData populated with the data from your file and should adjust the rest of your program accordingly.

need help My Library project has member problem

seems something is wrong. I've made a library project but some functions seem to be malfunctioning
for example : When i choose my member list (Shown below) it exists the code.
in c version.
void addmember()
{
system("cls");
// creating a FILE variable
FILE *fptr;
// creating a character variable
char ch;
// open the file in write mode
fptr = fopen("username.txt", "w");
// take user input
printf("Enter your name: ");
ch = getchar();
printf("Save any more?(Y / N):");
if(getch()=='n')
mainmenu();
else
system("cls");
addmember();
}
void memberlist(){
char ch;
FILE *fptr;
// open the file in read mode
fptr=fopen("username.txt", "r");
// display the content of the file
printf("\nFile content:\n");
while( (ch = getc(fptr)) != EOF ) {
printf("%c", ch);
}
printf("\nEnd of file\n");
// close file
fclose(fptr);
return 0;
}
In memberlist do you check if the fopen works well ? Probably fptr is NULL
Several remarks in addmember() :
addmember call itself at the end of its body, unconditionally, it is an infinite recursion
for each recursive call you fopen("username.txt", "w"); and that for nothing, but you never close the file
only one character is read for the name, it's suspicious

How do I preserve the xcode working directory when compiling into an exec?

I coded a relatively simple C program in XCode that I can input unformatted text files with vocabulary lists into and it returns me an html file that has them in a nice table. It works completely fine when run in Xcode, it pulls the files from the Working Directory I set and puts the html files back in there.
Still, I don't want to have to open XCode every time I want to run this program. I tried compiling it through the command line but the finished exec doesn't find the files it should be opening, whether they're in the Working Directory set in Xcode or the same one as the exec. I've tried entering the whole file path but that doesn't help it find them either. Here's the code for the project:
#include <stdio.h>
#include <stdlib.h>
int main() {
char name1[50], name2[50], Eng[15][256], Kanji[15][256], Roma[15][256]; //declare file names and string arrays
FILE *fptr; //declare file pointer
printf("Input: ");
scanf("%s",name2); //input input file, has to be .txt
printf("\nOutput: ");
scanf("%s",name1); //input output file, has to be .html
int i, m;
printf("\nNumber: ");
scanf("%d",&m); //input number of kanji in list
if ((fptr = fopen(name2,"r")) == NULL){
printf("Error! opening file");
// error in case input file doesn't exist
exit(1);
}
for (i=0;i<m;i++) {
fscanf(fptr,"%s%s%s", Eng[i], Kanji[i], Roma[i]); //reads the list into the arrays
}
fptr = fopen(name1,"w");
fprintf(fptr,"<!DOCTYPE html>\n<head>\n<meta charset='utf-8'>\n<script src='/Volumes/PRIVAT/Hobbies/Code/ressources/jquery-3.3.1.min.js'></script>\n\n<link href='https://fonts.googleapis.com/css?family=Kosugi+Maru' rel='stylesheet'>\n\n<link rel='stylesheet' href='style.css'>\n<script src='script.js'></script>\n\n</head>\n<body>\n<table>\n<colgroup> <col id='col1' span='1'> </colgroup>\n<colgroup> <col id='col2' span='2'> </colgroup>\n\n<caption>WEEK 1</caption>\n<tr>\n<th>English</th>\n<th>Kanji</th>\n<th>Reading</th>\n</tr>"); //writes constant part of html file
for (i=0;i<m;i++) {
fprintf(fptr,"\n<tr>\n<td>%s</td>\n<td>%s</td>\n<td>%s</td>\n</tr>",Eng[i],Kanji[i],Roma[i]); // writes variable part of html file
}
fprintf(fptr,"\n</body>"); //closing html
fclose(fptr); //closes output
return 0;
}
The html is fine, so don't bother trying to read that here. Really my only question is whether there's a way to compile this project to run in terminal that preserves the working directory. Thanks for any help!
Your code works for me. The working directory of your executable is where you run it from, not where the executable is located.
You can add a call to getcwd to see what the current working directory for your program is set to (from this Stack Overflow answer). It is also helpful to add a call to perror if fopen returns NULL so that you can see the reason why it failed.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
int main() {
char name1[50], name2[50], Eng[15][256], Kanji[15][256], Roma[15][256]; //declare file names and string arrays
FILE *fptr; //declare file pointer
// print current working directory
char cwd[PATH_MAX];
if (getcwd(cwd, sizeof(cwd)) != NULL) {
printf("Current working dir: %s\n", cwd);
} else {
perror("getcwd() error");
return 1;
}
printf("Input: ");
scanf("%s",name2); //input input file, has to be .txt
printf("\nOutput: ");
scanf("%s",name1); //input output file, has to be .html
int i, m;
printf("\nNumber: ");
scanf("%d",&m); //input number of kanji in list
if ((fptr = fopen(name2,"r")) == NULL){
perror("Error! opening file");
// error in case input file doesn't exist
exit(1);
}
for (i=0;i<m;i++) {
fscanf(fptr,"%s%s%s", Eng[i], Kanji[i], Roma[i]); //reads the list into the arrays
}
fptr = fopen(name1,"w");
fprintf(fptr,"<!DOCTYPE html>\n<head>\n<meta charset='utf-8'>\n<script src='/Volumes/PRIVAT/Hobbies/Code/ressources/jquery-3.3.1.min.js'></script>\n\n<link href='https://fonts.googleapis.com/css?family=Kosugi+Maru' rel='stylesheet'>\n\n<link rel='stylesheet' href='style.css'>\n<script src='script.js'></script>\n\n</head>\n<body>\n<table>\n<colgroup> <col id='col1' span='1'> </colgroup>\n<colgroup> <col id='col2' span='2'> </colgroup>\n\n<caption>WEEK 1</caption>\n<tr>\n<th>English</th>\n<th>Kanji</th>\n<th>Reading</th>\n</tr>"); //writes constant part of html file
for (i=0;i<m;i++) {
fprintf(fptr,"\n<tr>\n<td>%s</td>\n<td>%s</td>\n<td>%s</td>\n</tr>",Eng[i],Kanji[i],Roma[i]); // writes variable part of html file
}
fprintf(fptr,"\n</body>"); //closing html
fclose(fptr); //closes output
return 0;
}

Is it possible to copy a searched string from one file and print into another?

The following function is only used to display certain areas of a file. In this case I am trying to copy every line that has the word "EMPTY" from the main file to another file. Other than by opening a file to write to I use for char arrays to look for the proper string needed. At this point, I try to use the data from that file(In which the string was searched for) and try to copy it into another file. At this point some of the new file prints to the screen which is the first part at the top under where I created the file but when I try to add the string from the other file they do no print. Can you print certain string fro one file to another?
void emptySeats(void)
{
int position = 0;
int AvalabileOne = 0;
int countGone = 0;
int confirm = 0;
char testOne[] = "EMPTY";
char StrOne[10], StrTwo[10], StrThree[10], StrFour[10];
FILE *fEmptyseatArrangement;
fEmptyseatArrangement = fopen("airlineEmptySeatingArrangment.txt", "w+");
fprintf(fseatArrangement, "\t\t\tEcono-Airlines Passangers System\n\n\t Seat Number\t Seatavalability Last Name First Name\n");
do
{
fseatArrangement = fopen("airlineSeatingArrangment.txt", "r");
fseek(fseatArrangement, 102, SEEK_SET);
while (AvalabileOne < FULL)
{
fscanf(fseatArrangement, "%s%s%s%s\n", &StrOne, &StrTwo, &StrThree, &StrFour);
if (strcmp(StrThree, testOne))
{
fprintf(fEmptyseatArrangement, "\t\t%s\t\t%s\t\t%s\t\t%s\n", StrOne, StrTwo, StrThree, StrFour);
countGone = countGone + 1;
}
AvalabileOne = AvalabileOne + 1;
}
fclose(fEmptyseatArrangement);
fclose(fseatArrangement);
fEmptyseatArrangement = fopen("airlineEmptySeatingArrangment.txt", "r");
while (fgets(econoAirEmptySeatArrangement, 1000, fEmptyseatArrangement) != NULL)
printf("%s", econoAirEmptySeatArrangement);
fclose(fEmptyseatArrangement);
printf("There are %d seats vacant at the moment\n", FULL - countGone);
printf("Enter Zero(0) Key to return to menu at anytime.");
scanf(" %d", &position);
if (position == 0)
{
system("cls");
menu();
}
else
{
system("cls");
printf("INVALID INPUT! Please try again.\n");
}
} while (AvalabileOne != 0);
system("pause");
return;
}

File reading in c with fscanf

Im taking a class in c programming and I have this project where they give us a half-made project, and we need to finish it and fix some of the functions.
This project is about sort of a social network.
In this project you can send messages to other users (on the same computer for now) by writing the target user and then you enter the message. Afterwards the message is saved in a file called "messages.txt" in the same folder in this Format:
"[At]25/08/2013 [From]user1 [To]user2 [Message]hello whats up?"
"[At]Date [From]user [To]user2 [Message]any user input"
now after writing this, i go to the second user and try to read from the file with this function:
void showUnreadMessages(char* userName) // the function gets the name of the current
user that wishes to read his/hers messages
{
char msg[MAX_MESSAGE];
char toU[MAX_USER_NAME];
char fromU[MAX_USER_NAME];
char at[15];
int count = 0, flag = 0, count1 = 0;
FILE *file = fopen(MESSAGE_FILENAME, "rt"); //open the messages file
FILE *temp;
clearScreen(); //system("CLS")
if (file == NULL) //if the file didn't exict open one
{
printf("No messages\n");
flag = 1;
_flushall();
file = fopen(MESSAGE_FILENAME, "wt");
_flushall();
}
while (!feof(file) && flag == 0) //if the file did exict
{
if (count1 == 0)
{
temp = file;
}
_flushall();
fscanf(file, "[At]%s [From]%s [To]%s [Message]%s\n", at, fromU, toU, msg); //scan one line at a time
_flushall();
if (strcmp(userName, toU) == 0) //if the userNames match than its a message for the current user
{
count++;
}
count1++;
}
fclose(file);
if (count > 0 && flag == 0) //if there are messages to user
{
printf("You have %d new Messages\n", count);
_flushall();
while (!feof(temp))
{
_flushall();
fscanf(temp, "[At]%s [From]%s [To]%s [Message]%s\n", at, fromU, toU, msg); //scan one line at a time to print it for the user
_flushall();
if (strcmp(userName, toU) == 0)
{
printf("New message at %s from: %s\nStart of message: %s\n-----------------------------------------\n", at, fromU, msg);
}
}
fclose(temp);
}
else if (count == 0 && flag == 0)
{
printf("You have no Messages\n");
}
if (!file)
{
remove(MESSAGE_FILENAME);
}
PAUSE; // system("PAUSE")
}
Now when i try to read with this function, it only shows that the message is the first word in the message section on the first line...
For example For "[At]25/08/2013 [From]user1 [To]user2 [Message]hello whats up?"
the message will be "hello"
and it will be printed twice.. i dont know what to do, for some reason when i open the file and do fscanf for one time it also shows that the pointer file starts "up?[At]... (what appears on the second line)"
Please help me if you understand what i did wrong (which i know is a lot)
Thanks in advance
This part of fscanf :
"..etc. [Message]%s\n"
will only read ONE word of "Hello what's up" because %s parses for contiguous characters.
nr_fields = fscanf(file, "[At]%s [From]%s [To]%s [Message]%80c\n"
would read up to 80 characters regardless of spaces etc. in the text message. Also, the destination for %80c must be 80 characters or more!
Also, always test for number of fields found by fscanf.
Finally, fscanf works when used as directed, but it does have some subtle aspects.
One issue is that temp is pointing to a handle that is no longer valid after you call fclose(file) after the first loop. You could use fgets() to read a line and strtok() and strncpy() to split the read string.
I think it would be a good idea to encapsulate the reading in an extra function to reduce code duplication.

Resources