Compare commits

..

No commits in common. "f173272f8b4be3387b17054a07b5fa0b41f9bf67" and "2b9796732621560c5ed36ece8954b7df65209321" have entirely different histories.

6 changed files with 95 additions and 478 deletions

View File

@ -1,17 +1,12 @@
# HannaBox
Manual install of wifi manager download zip in project dir using vs console:
## Microcontroller
pio lib install ~/Downloads/WiFiManager-2.0.16-rc.2.zip
D1 Mini ESP32
Due to mp3 playback it needs a multicore esp 32:
This library only works on multi-core ESP32 chips like the ESP32-S3. It does not work on the ESP32-S2 or the ESP32-C3 warning
## Pins
```
Amplifier:
25 -> DIN
RX -> DIN
D8 -> BCLK
D4 -> LRC
5V -> Vin
@ -24,8 +19,6 @@ CS -> D1
MOSI -> D7
CLK -> D5
MISO -> D6
```
## Links

View File

@ -1,235 +0,0 @@
#include "DirectoryNode.h"
DirectoryNode::DirectoryNode(const String &nodeName)
: name(nodeName), currentPlaying(nullptr) {}
DirectoryNode::~DirectoryNode()
{
for (DirectoryNode *childNode : subdirectories)
{
delete childNode;
}
}
const String &DirectoryNode::getName() const
{
return name;
}
const std::vector<DirectoryNode *> &DirectoryNode::getSubdirectories() const
{
return subdirectories;
}
const std::vector<String> &DirectoryNode::getMP3Files() const
{
return mp3Files;
}
void DirectoryNode::setCurrentPlaying(const String *mp3File)
{
currentPlaying = mp3File;
}
const String *DirectoryNode::getCurrentPlaying() const
{
return currentPlaying;
}
void DirectoryNode::addSubdirectory(DirectoryNode *subdirectory)
{
subdirectories.push_back(subdirectory);
}
void DirectoryNode::addMP3File(const String &mp3File)
{
mp3Files.push_back(mp3File);
}
void DirectoryNode::buildDirectoryTree(const char *currentPath)
{
File rootDir = SD.open(currentPath);
while (true)
{
File entry = rootDir.openNextFile();
if (!entry)
{
break;
}
if (entry.isDirectory())
{
DirectoryNode *newNode = new DirectoryNode(entry.name());
subdirectories.push_back(newNode);
newNode->buildDirectoryTree((String(currentPath) + entry.name()).c_str());
}
else if (String(entry.name()).endsWith(".mp3"))
{
mp3Files.push_back(entry.name());
}
entry.close();
}
rootDir.close();
}
void DirectoryNode::printDirectoryTree(int level) const
{
for (int i = 0; i < level; i++)
{
Serial.print(" ");
}
Serial.println(name);
for (const String &mp3File : mp3Files)
{
for (int i = 0; i <= level; i++)
{
Serial.print(" ");
}
Serial.println(mp3File);
}
for (DirectoryNode *childNode : subdirectories)
{
childNode->printDirectoryTree(level + 1);
}
}
DirectoryNode *DirectoryNode::findFirstDirectoryWithMP3s()
{
if (!mp3Files.empty())
{
// Found a directory with MP3 files
return this;
}
for (DirectoryNode *subdirectory : subdirectories)
{
DirectoryNode *result = subdirectory->findFirstDirectoryWithMP3s();
if (result != nullptr)
{
// Found a directory with MP3 files in the subdirectories
return result;
}
}
// No directory with MP3 files found
return nullptr;
}
void DirectoryNode::advanceToFirstMP3InThisNode() {
if (mp3Files.size()>0) {
currentPlaying = &mp3Files[0];
}
}
DirectoryNode* DirectoryNode::advanceToNextMP3(const String* currentGlobal)
{
bool useFirst = false;
Serial.println(currentGlobal->c_str());
if (currentGlobal != nullptr)
{
for (size_t i = 0; i < mp3Files.size(); i++)
{
if (*currentGlobal == mp3Files[i])
{
// Found the current playing MP3 file
if (i < mp3Files.size() - 1)
{
// Advance to the next MP3 file in the same directory
currentPlaying = &mp3Files[i + 1];
return this;
}
useFirst = true;
// Reached the end of the MP3 files in the directory
break;
}
}
}
// We are either not playing, or we've exhausted all the MP3 files in this directory.
// Therefore, we need to recursively look in our subdirectories.
for (auto subdir : subdirectories)
{
Serial.println("searching next node");
if (useFirst && subdir->mp3Files.size()>0) {
subdir->currentPlaying = &subdir->mp3Files[0];
return subdir;
}
// Have each subdirectory advance its song
for (size_t i = 0; i < subdir->mp3Files.size(); i++)
{
if (*currentGlobal == subdir->mp3Files[i])
{
// Found the current playing MP3 file
if (i < subdir->mp3Files.size() - 1)
{
// Advance to the next MP3 file in the same directory
subdir->currentPlaying = &subdir->mp3Files[i + 1];
return subdir;
} else {
useFirst = true;
}
// Reached the end of the MP3 files in the directory
break;
}
}
}
// If we get here, there were no MP3 files or subdirectories left to check
currentPlaying = nullptr;
Serial.println("no more nodes found");
return this;
}
String DirectoryNode::getDirectoryStructureHTML() const
{
String html;
html.reserve(1024); // Reserve memory for better performance
appendIndentation(html, 0);
html += "<ul>\n";
appendIndentation(html, 1);
html += "<li>" + name + "</li>\n";
for (const String &mp3File : mp3Files)
{
appendIndentation(html, 2);
html += "<li>" + mp3File + "</li>\n";
}
for (DirectoryNode *childNode : subdirectories)
{
html += childNode->getDirectoryStructureHTML();
}
appendIndentation(html, 0);
html += "</ul>\n";
return html;
}
void DirectoryNode::appendIndentation(String &html, int level) const
{
for (int i = 0; i < level; i++)
{
html += " ";
}
}
String DirectoryNode::getCurrentPlayingFilePath() const
{
if (currentPlaying != nullptr)
{
String filePath = "/" + name;
if (!filePath.endsWith("/"))
{
filePath += "/";
}
filePath += *currentPlaying;
return filePath;
}
return "";
}

View File

@ -1,37 +0,0 @@
#ifndef DIRECTORYNODE_H_
#define DIRECTORYNODE_H_
#include <SD.h>
#include <vector>
class DirectoryNode {
private:
String name;
std::vector<DirectoryNode*> subdirectories;
std::vector<String> mp3Files;
const String* currentPlaying;
public:
DirectoryNode(const String& nodeName);
~DirectoryNode();
const String& getName() const;
const std::vector<DirectoryNode*>& getSubdirectories() const;
const std::vector<String>& getMP3Files() const;
void setCurrentPlaying(const String* mp3File);
const String* getCurrentPlaying() const;
void addSubdirectory(DirectoryNode* subdirectory);
void addMP3File(const String& mp3File);
void buildDirectoryTree(const char* currentPath);
void printDirectoryTree(int level = 0) const;
DirectoryNode* advanceToNextMP3(const String* currentGlobal);
void advanceToFirstMP3InThisNode();
String getDirectoryStructureHTML() const;
void appendIndentation(String& html, int level) const;
DirectoryNode* findFirstDirectoryWithMP3s();
String getCurrentPlayingFilePath() const;
};
#endif /* DIRECTORYNODE_H_ */

View File

@ -5,10 +5,11 @@ const char index_html[] PROGMEM = R"rawliteral(
<title>HannaBox</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial; margin:5px auto; padding: 30px;}
body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;}
.button {
padding: 10px 20px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #2f4468;
@ -35,15 +36,9 @@ const char index_html[] PROGMEM = R"rawliteral(
<body>
<h1>HannaBox</h1>
<span id="state">%PLAYING%</span><br/><br/>
<button class="button" onmouseup="toggleCheckbox('start');" ontouchend="toggleCheckbox('start');">Start</button><br/><br/>
<button class="button" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Stop</button><br/><br/>
<button class="button" onmouseup="toggleCheckbox('next');" ontouchend="toggleCheckbox('next');">Next</button><br/><br/>
<p>
<h2>Content of SD Card:</h2>
%DIRECTORY%
</p>
Playing %PLAYING%<br/><br/>
<button class="button" onmouseup="toggleCheckbox('start');" ontouchend="toggleCheckbox('start');">On!</button><br/><br/>
<button class="button" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Off!</button><br/><br/>
<script>
setInterval(getState, 500);
function toggleCheckbox(x) {

View File

@ -1,92 +0,0 @@
#ifndef HELPER_H_
#define HELPER_H_
#include <SD.h>
struct DirectoryNode {
String name;
std::vector<DirectoryNode*> subdirectories;
std::vector<String> mp3Files;
String* currentPlaying;
};
void buildDirectoryTree(DirectoryNode* currentNode, const char* currentPath) {
File root = SD.open(currentPath);
while (true) {
File entry = root.openNextFile();
if (!entry) {
break;
}
if (entry.isDirectory()) {
DirectoryNode* newNode = new DirectoryNode;
newNode->name = String(currentPath) + entry.name();
currentNode->subdirectories.push_back(newNode);
buildDirectoryTree(newNode, (String(currentPath) + entry.name()).c_str());
} else if (String(entry.name()).endsWith(".mp3")) {
currentNode->mp3Files.push_back(entry.name());
}
entry.close();
}
root.close();
}
void printDirectoryTree(DirectoryNode* currentNode, int level = 0) {
for (int i = 0; i < level; i++) {
Serial.print(" ");
}
Serial.println(currentNode->name);
for (const String& mp3File : currentNode->mp3Files) {
for (int i = 0; i <= level; i++) {
Serial.print(" ");
}
Serial.println(mp3File);
}
for (DirectoryNode* childNode : currentNode->subdirectories) {
printDirectoryTree(childNode, level + 1);
}
}
void advanceToNextMP3(DirectoryNode* currentNode) {
if (currentNode->currentPlaying != nullptr) {
for (size_t i = 0; i < currentNode->mp3Files.size(); i++) {
if (*currentNode->currentPlaying == currentNode->mp3Files[i]) {
// Found the current playing MP3 file
if (i < currentNode->mp3Files.size() - 1) {
// Advance to the next MP3 file in the same directory
currentNode->currentPlaying = &currentNode->mp3Files[i + 1];
return;
}
// Reached the end of the MP3 files in the directory
break;
}
}
}
// If not playing or reached the end, set the first MP3 file as the current playing
if (!currentNode->mp3Files.empty()) {
currentNode->currentPlaying = &currentNode->mp3Files[0];
}
}
DirectoryNode* findFirstMP3Node(DirectoryNode* currentNode) {
if (!currentNode->mp3Files.empty()) {
return currentNode;
}
for (DirectoryNode* subdirectory : currentNode->subdirectories) {
DirectoryNode* tempNode = findFirstMP3Node(subdirectory);
if (!tempNode->mp3Files.empty()) {
return tempNode;
}
}
return NULL;
}
#endif /* HELPER_H_ */

View File

@ -17,47 +17,50 @@
#include <SD.h>
#include "WebContent.h"
#include "DirectoryNode.h"
File root;
File mp3File;
void printDirectory(File dir, int numTabs);
unsigned long lastStart = 0;
const int startDelay = 250;
Audio audio;
// Set web server port number to 80
AsyncWebServer server(80);
AsyncWebServer server2(81);
DNSServer dns;
File next;
DirectoryNode rootNode("/");
DirectoryNode *currentNode = NULL;
void stop();
void start();
// Variable to store the HTTP request
String header;
void playNextMp3()
{
stop();
if (currentNode == NULL)
{
currentNode = rootNode.findFirstDirectoryWithMP3s();
if (currentNode)
{
currentNode->advanceToFirstMP3InThisNode();
}
}
else
{
currentNode = rootNode.advanceToNextMP3(currentNode->getCurrentPlaying());
}
Serial.print("Now advancing to ");
String mp3File = currentNode->getCurrentPlayingFilePath();
Serial.println(mp3File.c_str());
next = root.openNextFile();
audio.connecttoSD(mp3File.c_str());
if (!next)
{
root = SD.open("/");
}
while (!String(next.name()).endsWith(".mp3"))
{
next = root.openNextFile();
if (!next)
{
Serial.println("no more files found.");
return;
}
}
Serial.print("initialized");
Serial.print(next.name());
Serial.print("\n");
audio.stopSong();
audio.connecttoSD(next.name());
}
void audio_info(const char *info)
@ -65,57 +68,25 @@ void audio_info(const char *info)
// Serial.print("info "); Serial.println(info);
}
String getState()
{
String state = String();
if (audio.isRunning())
{
state += "Playing ";
}
else
{
state += "Stopped ";
}
if (currentNode)
{
state += currentNode->getName();
state += " ";
if (currentNode->getCurrentPlaying())
state += *currentNode->getCurrentPlaying();
}
return state;
}
String processor(const String& var)
{
if (var == "PLAYING" && currentNode)
return getState();
if (var == "DIRECTORY")
{
return rootNode.getDirectoryStructureHTML();
}
if(var == "PLAYING")
return F(next.name());
return String();
}
void stop()
{
Serial.println("stopping audio.");
if (audio.isRunning())
{
void stop() {
if (audio.isRunning()) {
audio.stopSong();
}
}
void start()
{
currentNode->setCurrentPlaying(NULL);
currentNode = NULL;
void start() {
if (!next) {
playNextMp3();
} else {
audio.connecttoSD(next.name());
}
void next()
{
playNextMp3();
}
void setup()
@ -140,10 +111,7 @@ void setup()
}
Serial.println("SD initialization done.");
rootNode.buildDirectoryTree("/");
rootNode.printDirectoryTree();
// printDirectoryTree(&rootNode, 0);
root = SD.open("/");
// printDirectory(root, 0);
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(12); // 0...21
@ -151,42 +119,67 @@ void setup()
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send_P(200, "text/html", index_html,processor); });
server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request)
{
String state = getState();
request->send(200, "text/plain", state.c_str()); });
server.on("/start", HTTP_GET, [](AsyncWebServerRequest *request)
{
server.on("/start", HTTP_GET, [] (AsyncWebServerRequest *request) {
start();
request->send(200, "text/plain", "start");
start(); });
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request)
{
});
server.on("/stop", HTTP_GET, [] (AsyncWebServerRequest *request) {
stop();
request->send(200, "text/plain", "stop");
stop(); });
});
server.on("/next", HTTP_GET, [](AsyncWebServerRequest *request)
{
request->send(200, "text/plain", "next");
next(); });
server.begin();
}
void printDirectory(File dir, int numTabs)
{
while (true)
{
File entry = dir.openNextFile();
if (!entry)
{
// no more files
break;
}
for (uint8_t i = 0; i < numTabs; i++)
{
Serial.print('\t');
}
Serial.print(entry.name());
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();
}
}
void loop()
{
audio.loop();
// Serial.print(digitalRead(D2));
if (digitalRead(D3) == LOW)
{
unsigned long now = millis();
if (now - lastStart > startDelay)
{
playNextMp3();
Serial.println("mp3 started.");
lastStart = now;
}
}