[ai] new option on how to continue for mapping
This commit is contained in:
parent
ce863f4d02
commit
02cd5da886
234
src/main.cpp
234
src/main.cpp
|
|
@ -58,6 +58,15 @@ Audio audio;
|
|||
|
||||
uint volume = 7;
|
||||
|
||||
// Folder-play tracking: flattened list of files inside a mapped folder and current index
|
||||
// Used when a mapping targets a folder (play folder once or loop folder)
|
||||
#include <vector>
|
||||
static std::vector<std::pair<DirectoryNode*, int>> folderFlatList;
|
||||
static int folderFlatIndex = -1;
|
||||
static String folderRootPath = "";
|
||||
// Pointer to the root DirectoryNode for active folder-mode playback
|
||||
DirectoryNode* folderRootNode = nullptr;
|
||||
|
||||
AsyncWebServer server(80);
|
||||
DNSServer dns;
|
||||
|
||||
|
|
@ -441,21 +450,103 @@ void playSongByRFID(String id)
|
|||
return;
|
||||
}
|
||||
|
||||
auto songit = rfid_map.find(id);
|
||||
if (songit == rfid_map.end())
|
||||
auto it = rfid_map.find(id);
|
||||
if (it == rfid_map.end())
|
||||
{
|
||||
Serial.println("Song for UID not found: " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (songit->second.length() == 0)
|
||||
MappingEntry entry = it->second;
|
||||
if (entry.target.length() == 0)
|
||||
{
|
||||
Serial.println("Empty song name mapped to: " + id);
|
||||
Serial.println("Empty mapping target for UID: " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("Searching for song: " + songit->second);
|
||||
playSongByName(songit->second);
|
||||
Serial.print("RFID mapping found. Target: ");
|
||||
Serial.print(entry.target);
|
||||
Serial.print(" Mode: ");
|
||||
Serial.println(entry.mode);
|
||||
|
||||
// Reset folder tracking
|
||||
folderFlatList.clear();
|
||||
folderFlatIndex = -1;
|
||||
folderRootPath = "";
|
||||
folderModeActive = false;
|
||||
|
||||
// Set continuous mode based on mapping ('c' => continuous, otherwise not)
|
||||
continuousMode = (entry.mode == 'c');
|
||||
|
||||
// Try to locate the target in the directory tree
|
||||
currentNode = rootNode.advanceToMP3(&entry.target);
|
||||
if (currentNode == nullptr || currentNode->getCurrentPlaying() == nullptr)
|
||||
{
|
||||
Serial.println("No node/file found for mapping target: " + entry.target);
|
||||
return;
|
||||
}
|
||||
|
||||
String mp3File = currentNode->getCurrentPlayingFilePath();
|
||||
if (mp3File.length() == 0)
|
||||
{
|
||||
Serial.println("Empty file path for mapping target: " + entry.target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect whether the mapping targeted a folder (matching a subdirectory name).
|
||||
// advanceToMP3 returns the directory node if a subdirectory name was matched.
|
||||
bool targetIsFolder = false;
|
||||
if (!entry.target.startsWith("/") && entry.target == currentNode->getName())
|
||||
{
|
||||
targetIsFolder = true;
|
||||
}
|
||||
|
||||
// If the mapping targets a folder (or explicitly 'f' mode), activate folder tracking
|
||||
if (targetIsFolder || entry.mode == 'f')
|
||||
{
|
||||
folderModeActive = true;
|
||||
folderRootNode = currentNode;
|
||||
// Build flat list of files inside this folder for sequential/looped playback
|
||||
folderFlatList.clear();
|
||||
folderRootNode->buildFlatMP3List(folderFlatList);
|
||||
|
||||
// Find index of current playing file within the folder list
|
||||
for (size_t i = 0; i < folderFlatList.size(); i++)
|
||||
{
|
||||
DirectoryNode *node = folderFlatList[i].first;
|
||||
int fileIdx = folderFlatList[i].second;
|
||||
if (node->getCurrentPlayingFilePath() == mp3File)
|
||||
{
|
||||
folderFlatIndex = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute root path for safety checks (path up to last '/')
|
||||
int lastSlash = mp3File.lastIndexOf('/');
|
||||
if (lastSlash >= 0)
|
||||
{
|
||||
folderRootPath = mp3File.substring(0, lastSlash + 1); // include trailing slash
|
||||
}
|
||||
}
|
||||
|
||||
Serial.print("Playing mapped target: ");
|
||||
Serial.println(mp3File);
|
||||
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
|
||||
if (!playFile(mp3File.c_str()))
|
||||
{
|
||||
Serial.println("Failed to play mapped file: " + mp3File);
|
||||
currentNode = nullptr;
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
return;
|
||||
}
|
||||
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -480,7 +571,7 @@ bool playFile(const char *filename, uint32_t resumeFilePos)
|
|||
void playNextMp3()
|
||||
{
|
||||
stop();
|
||||
continuousMode = true;
|
||||
// Do not force continuous mode here; respect current global state.
|
||||
if (currentNode == nullptr)
|
||||
{
|
||||
currentNode = rootNode.findFirstDirectoryWithMP3s();
|
||||
|
|
@ -661,9 +752,12 @@ void saveMappingToFile(const String filename)
|
|||
{
|
||||
for (const auto &pair : rfid_map)
|
||||
{
|
||||
// Format: UID=target|mode
|
||||
file.print(pair.first);
|
||||
file.print("="); // Using F() macro
|
||||
file.println(pair.second);
|
||||
file.print("=");
|
||||
file.print(pair.second.target);
|
||||
file.print("|");
|
||||
file.println(pair.second.mode);
|
||||
}
|
||||
file.close();
|
||||
Serial.println("Mapping saved to file.");
|
||||
|
|
@ -683,7 +777,16 @@ void editMapping(AsyncWebServerRequest *request)
|
|||
String song = request->getParam("song", true)->value();
|
||||
rfid.trim();
|
||||
song.trim();
|
||||
rfid_map[rfid] = song;
|
||||
|
||||
char mode = 's';
|
||||
if (request->hasParam("mode", true))
|
||||
{
|
||||
String mStr = request->getParam("mode", true)->value();
|
||||
if (mStr.length() > 0)
|
||||
mode = mStr.charAt(0);
|
||||
}
|
||||
|
||||
rfid_map[rfid] = MappingEntry(song, mode);
|
||||
saveMappingToFile(getSysDir(mapping_file));
|
||||
request->send(200, "text/plain", "Mapping updated");
|
||||
}
|
||||
|
|
@ -693,7 +796,7 @@ void editMapping(AsyncWebServerRequest *request)
|
|||
}
|
||||
}
|
||||
|
||||
std::map<String, String> readDataFromFile(String filename)
|
||||
void readDataFromFile(String filename)
|
||||
{
|
||||
|
||||
File file = SD.open(filename);
|
||||
|
|
@ -702,19 +805,33 @@ std::map<String, String> readDataFromFile(String filename)
|
|||
{
|
||||
while (file.available())
|
||||
{
|
||||
// Read key and value from the file
|
||||
// Read key and raw value from the file
|
||||
String line = file.readStringUntil('\n');
|
||||
int separatorIndex = line.indexOf('=');
|
||||
if (separatorIndex != -1)
|
||||
{
|
||||
// Extract key and value
|
||||
String key = line.substring(0, separatorIndex).c_str();
|
||||
String value = line.substring(separatorIndex + 1).c_str();
|
||||
// Extract key and raw value
|
||||
String key = line.substring(0, separatorIndex);
|
||||
String raw = line.substring(separatorIndex + 1);
|
||||
key.trim();
|
||||
value.trim();
|
||||
Serial.println("found rfid mapping for " + value);
|
||||
raw.trim();
|
||||
|
||||
// Support optional mode delimited by '|' => target|mode
|
||||
String target = raw;
|
||||
char mode = 's';
|
||||
int delim = raw.indexOf('|');
|
||||
if (delim != -1)
|
||||
{
|
||||
target = raw.substring(0, delim);
|
||||
String mstr = raw.substring(delim + 1);
|
||||
mstr.trim();
|
||||
if (mstr.length() > 0)
|
||||
mode = mstr.charAt(0);
|
||||
}
|
||||
|
||||
Serial.println("found rfid mapping for " + target + " mode " + String(mode));
|
||||
// Add key-value pair to the map
|
||||
rfid_map[key] = value;
|
||||
rfid_map[key] = MappingEntry(target, mode);
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
|
|
@ -724,8 +841,6 @@ std::map<String, String> readDataFromFile(String filename)
|
|||
Serial.print("Error opening file ");
|
||||
Serial.println(filename);
|
||||
}
|
||||
|
||||
return rfid_map;
|
||||
}
|
||||
|
||||
String processor(const String &var)
|
||||
|
|
@ -744,7 +859,7 @@ String processor(const String &var)
|
|||
{
|
||||
char c = s[i];
|
||||
if (c == '&')
|
||||
out += "&";
|
||||
out += "&";
|
||||
else if (c == '<')
|
||||
out += "";
|
||||
else if (c == '>')
|
||||
|
|
@ -766,7 +881,11 @@ String processor(const String &var)
|
|||
html.concat(F("<tr><td style='border:1px solid #ccc;padding:4px;'>"));
|
||||
html.concat(htmlEscape(pair.first));
|
||||
html.concat(F("</td><td style='border:1px solid #ccc;padding:4px;'>"));
|
||||
html.concat(htmlEscape(pair.second));
|
||||
// Show target and mode (e.g. "mysong.mp3|s")
|
||||
String mappingVal = pair.second.target;
|
||||
mappingVal += "|";
|
||||
mappingVal += pair.second.mode;
|
||||
html.concat(htmlEscape(mappingVal));
|
||||
html.concat("</td></tr>");
|
||||
}
|
||||
html.concat("</table>");
|
||||
|
|
@ -915,6 +1034,77 @@ void previous()
|
|||
void audio_eof_mp3(const char *info)
|
||||
{
|
||||
Serial.println("audio file ended.");
|
||||
if (prepareSleepMode)
|
||||
return;
|
||||
|
||||
// If folder-mode is active, advance only inside that folder.
|
||||
if (folderModeActive && folderRootNode != nullptr)
|
||||
{
|
||||
// Ensure flat list is built
|
||||
if (folderFlatList.empty())
|
||||
folderRootNode->buildFlatMP3List(folderFlatList);
|
||||
|
||||
// Try to find current index if not set
|
||||
if (folderFlatIndex < 0)
|
||||
{
|
||||
String cur = currentNode ? currentNode->getCurrentPlayingFilePath() : String();
|
||||
for (size_t i = 0; i < folderFlatList.size(); i++)
|
||||
{
|
||||
if (folderFlatList[i].first->getCurrentPlayingFilePath() == cur)
|
||||
{
|
||||
folderFlatIndex = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (folderFlatIndex >= 0 && folderFlatIndex < (int)folderFlatList.size() - 1)
|
||||
{
|
||||
// Advance to next file in the folder
|
||||
folderFlatIndex++;
|
||||
DirectoryNode *nextNode = folderFlatList[folderFlatIndex].first;
|
||||
int fileIdx = folderFlatList[folderFlatIndex].second;
|
||||
nextNode->setCurrentPlaying(&nextNode->getMP3Files()[fileIdx]);
|
||||
currentNode = nextNode;
|
||||
currentNode->setSecondsPlayed(0);
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reached end of folder list
|
||||
if (continuousMode && !folderFlatList.empty())
|
||||
{
|
||||
// Loop back to first in folder
|
||||
folderFlatIndex = 0;
|
||||
DirectoryNode *nextNode = folderFlatList[folderFlatIndex].first;
|
||||
int fileIdx = folderFlatList[folderFlatIndex].second;
|
||||
nextNode->setCurrentPlaying(&nextNode->getMP3Files()[fileIdx]);
|
||||
currentNode = nextNode;
|
||||
currentNode->setSecondsPlayed(0);
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop playback and clear folder mode
|
||||
folderModeActive = false;
|
||||
folderRootNode = nullptr;
|
||||
folderFlatList.clear();
|
||||
folderFlatIndex = -1;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Default behavior: if continuous mode is enabled, go to next globally
|
||||
if (continuousMode && !prepareSleepMode)
|
||||
playNextMp3();
|
||||
}
|
||||
|
|
|
|||
19
src/main.h
19
src/main.h
|
|
@ -80,6 +80,23 @@ boolean continuePlaying = false;
|
|||
|
||||
boolean prepareSleepMode = false;
|
||||
|
||||
std::map<String, String> rfid_map;
|
||||
class DirectoryNode;
|
||||
|
||||
// Mapping entry that stores target (file or folder) and playback mode:
|
||||
// 's' = single (default) - play only the selected song (or single file in folder)
|
||||
// 'f' = folder - play files inside the selected folder, then stop
|
||||
// 'c' = continuous - continuously play (like previous continuousMode)
|
||||
struct MappingEntry {
|
||||
String target;
|
||||
char mode;
|
||||
MappingEntry() : target(""), mode('s') {}
|
||||
MappingEntry(const String& t, char m) : target(t), mode(m) {}
|
||||
};
|
||||
|
||||
std::map<String, MappingEntry> rfid_map;
|
||||
|
||||
// Folder-play helper: when a mapping requests "folder only" playback we keep
|
||||
// track of the folder root node so EOF handling can advance only inside that folder.
|
||||
bool folderModeActive = false;
|
||||
|
||||
#endif
|
||||
|
|
@ -117,6 +117,14 @@
|
|||
<label for="song">Song:</label>
|
||||
<input type="text" id="song" name="song" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="mode">Mode:</label>
|
||||
<select id="mode" name="mode">
|
||||
<option value="s">Single (play selected song / file)</option>
|
||||
<option value="f">Folder (play selected folder, then stop)</option>
|
||||
<option value="c">Continuous (continuous playback / loop folder)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="action-btn" style="grid-column: 1 / -1;" onclick="editMapping()">Update Mapping</button>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -164,13 +164,17 @@ function playNamedSong(song) {
|
|||
function editMapping() {
|
||||
var rfid = document.getElementById('rfid').value;
|
||||
var song = document.getElementById('song').value;
|
||||
var modeEl = document.getElementById('mode');
|
||||
var mode = modeEl ? modeEl.value : 's';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/edit_mapping", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
xhr.send("rfid=" + encodeURIComponent(rfid) + "&song=" + encodeURIComponent(song));
|
||||
xhr.send("rfid=" + encodeURIComponent(rfid) + "&song=" + encodeURIComponent(song) + "&mode=" + encodeURIComponent(mode));
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
alert("Mapping updated successfully!");
|
||||
} else if (xhr.readyState === 4) {
|
||||
alert("Failed to update mapping: " + (xhr.responseText || xhr.status));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue