web interface

This commit is contained in:
Stefan Ostermann 2023-08-28 21:22:48 +02:00
parent 37f11de98a
commit 4fd7856736
6 changed files with 141 additions and 43 deletions

View File

@ -18,5 +18,6 @@ lib_deps =
me-no-dev/ESP Async WebServer@^1.2.3
alanswx/ESPAsyncWiFiManager@^0.31
miguelbalboa/MFRC522@^1.4.10
bblanchon/ArduinoJson@^6.21.3
monitor_speed = 115200
board_build.partitions = huge_app.csv

View File

@ -46,6 +46,14 @@ void DirectoryNode::addMP3File(const String &mp3File)
mp3Files.push_back(mp3File);
}
void DirectoryNode::setSecondsPlayed(const uint32_t seconds) {
secondsPlayed = seconds;
}
uint32_t DirectoryNode::getSecondsPlayed() {
return secondsPlayed;
}
void DirectoryNode::buildDirectoryTree(const char *currentPath)
{
File rootDir = SD.open(currentPath);

View File

@ -10,6 +10,7 @@ private:
std::vector<DirectoryNode*> subdirectories;
std::vector<String> mp3Files;
const String* currentPlaying;
uint16_t secondsPlayed = 0;
public:
DirectoryNode(const String& nodeName);
@ -22,6 +23,9 @@ public:
void setCurrentPlaying(const String* mp3File);
const String* getCurrentPlaying() const;
void setSecondsPlayed(const uint32_t seconds);
uint32_t getSecondsPlayed();
void addSubdirectory(DirectoryNode* subdirectory);
void addMP3File(const String& mp3File);
void buildDirectoryTree(const char* currentPath);

View File

@ -9,13 +9,27 @@ const char index_html[] PROGMEM = R"rawliteral(
<body>
<h1>HannaBox</h1>
<span id="state">%PLAYING%</span><br/><br/>
<span id="state"></span><br/><br/>
<button class="prev-button" onmouseup="toggleCheckbox('prev');" ontouchend="toggleCheckbox('prev');"></button>
<button class="play-button" onmouseup="toggleCheckbox('start');" ontouchend="toggleCheckbox('start');"></button>
<button class="next-button" onmouseup="toggleCheckbox('next');" ontouchend="toggleCheckbox('next');"></button><br/>
<div class="slidecontainer">
<label for="progress" id="progressLabel"></label>
<input name="progress" type="range" min="0" max="100" value="0" class="slider" id="progressSlider"
onmouseup="postValue('progress',document.getElementById('progressSlider').value);"
ontouchend="postValue('progress',document.getElementById('progressSlider').value);">
</div>
<div class="slidecontainer">
<label for="volume">Vol</label>
<input name="volume" type="range" min="0" max="21" value="12" class="slider" id="volumeSlider"
onmouseup="postValue('volume',document.getElementById('volumeSlider').value);"
ontouchend="postValue('volume',document.getElementById('volumeSlider').value);">
</div>
<button onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Stop</button>&nbsp;
<button class="prev-button" onmouseup="simpleGetCall('prev');" ontouchend="simpleGetCall('prev');"></button>
<button class="play-button" onmouseup="simpleGetCall('toggleplaypause');" ontouchend="simpleGetCall('toggleplaypause');"></button>
<button class="next-button" onmouseup="simpleGetCall('next');" ontouchend="simpleGetCall('next');"></button><br/>
<button onmouseup="simpleGetCall('stop');" ontouchend="simpleGetCall('stop');">Stop</button>&nbsp;
<form action='playnamed' method='post'>
<div>
<label for="title">Song title to play</label>
@ -46,9 +60,17 @@ const char index_html[] PROGMEM = R"rawliteral(
});
});
function toggleCheckbox(x) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/" + x, true);
function simpleGetCall(endpoint) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/" + endpoint, true);
xhr.send();
}
function postValue(endpoint,value) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "/" + endpoint, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.send("value="+encodeURIComponent(value));
xhr.send();
}
@ -57,7 +79,7 @@ const char index_html[] PROGMEM = R"rawliteral(
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
displayState(xhr.response);
displayState(JSON.parse(xhr.response));
}
}
@ -67,14 +89,23 @@ const char index_html[] PROGMEM = R"rawliteral(
}
function displayState(state) {
document.getElementById("state").innerHTML = state;
document.getElementById("state").innerHTML = state['title'];
document.getElementById("progressLabel").innerHTML = state['time'];
var elements = document.getElementsByClassName('play-button');
var btn = elements[0];
if (state=='-') {
btn.classList.remove('paused');
} else {
btn.classList.add('paused');
}
var btn = elements[0];
if (state['playing']) {
btn.classList.add('paused');
} else {
btn.classList.remove('paused');
}
var progress = document.getElementById('progressSlider');
progress.value = state['time'];
progress.max = state['length'];
var volume = document.getElementById('volumeSlider');
volume.value = state['volume'];
}
function playNamedSong(song) {

View File

@ -11,6 +11,10 @@ void loop2(void* parameter);
void named_song_action(AsyncWebServerRequest *request);
void progress_action(AsyncWebServerRequest *request);
void volume_action(AsyncWebServerRequest *request);
boolean buttonPressed(const uint8_t pin);
/**

View File

@ -6,6 +6,8 @@
#include <ESPAsyncWebServer.h> //Local WebServer used to serve the configuration portal
#include <ESPAsyncWiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
#include <ArduinoJson.h>
#include "Audio.h"
@ -131,6 +133,11 @@ void playNextMp3()
{
currentNode = rootNode.advanceToNextMP3(currentNode->getCurrentPlaying());
}
if (currentNode!=NULL) {
currentNode->setSecondsPlayed(0);
}
Serial.print("Now advancing to ");
String mp3File = currentNode->getCurrentPlayingFilePath();
Serial.println(mp3File.c_str());
@ -163,35 +170,22 @@ void unmute()
String getState()
{
String state = String();
DynamicJsonDocument jsonState(1024);
jsonState["playing"] = audio.isRunning();
if (currentNode!=NULL)
jsonState["title"] = *currentNode->getCurrentPlaying();
jsonState["time"] = audio.getAudioCurrentTime();
jsonState["volume"] = audio.getVolume();
jsonState["length"] = audio.getAudioFileDuration();
String output;
serializeJson(jsonState,output);
return output;
if (audio.isRunning())
{
state += "Playing ";
state += audio.getAudioCurrentTime();
state += " / ";
state += audio.getAudioFileDuration();
state += " ";
}
else
{
return "-";
}
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();
@ -203,8 +197,11 @@ void stop()
{
if (audio.isRunning())
{
Serial.println("stopping audio.");
Serial.println("stopping audio.");
audio.stopSong();
if (currentNode!=NULL) {
currentNode->setSecondsPlayed(0);
}
}
}
@ -219,6 +216,16 @@ void start()
playNextMp3();
}
void togglePlayPause()
{
if (currentNode != NULL)
{
audio.pauseResume();
} else {
playNextMp3();
}
}
void next()
{
playNextMp3();
@ -311,7 +318,7 @@ void setup()
server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request)
{
String state = getState();
request->send(200, "text/plain", state.c_str()); });
request->send(200, "application/json", state.c_str()); });
server.on("/start", HTTP_GET, [](AsyncWebServerRequest *request)
{
@ -319,6 +326,13 @@ void setup()
request->send(200, "text/plain", "start");
start(); });
server.on("/toggleplaypause", HTTP_GET, [](AsyncWebServerRequest *request)
{
request->send(200, "text/plain", "toggleplaypause");
togglePlayPause(); });
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request)
{
@ -333,6 +347,10 @@ void setup()
server.on("/playnamed", HTTP_POST, named_song_action);
server.on("/progress", HTTP_POST, progress_action);
server.on("/volume", HTTP_POST, volume_action);
server.begin();
xTaskCreatePinnedToCore(
@ -358,7 +376,35 @@ void named_song_action(AsyncWebServerRequest *request) {
playSongByName(p->value());
}
}
request->send_P(200, "text/html", index_html, processor);
request->send_P(200, "text/plain", "ok");
}
void progress_action(AsyncWebServerRequest *request) {
Serial.println("progress!");
int params = request->params();
for (int i = 0; i < params; i++) {
AsyncWebParameter* p = request->getParam(i);
Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
if (p->name()=="value") {
audio.setAudioPlayPosition(atoi(p->value().c_str()));
}
}
request->send_P(200, "text/plain", "ok");
}
void volume_action(AsyncWebServerRequest *request) {
Serial.println("volume!");
int params = request->params();
for (int i = 0; i < params; i++) {
AsyncWebParameter* p = request->getParam(i);
Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
if (p->name()=="value") {
audio.setVolume(atoi(p->value().c_str()));
}
}
request->send_P(200, "text/plain", "ok");
}
void loop()
@ -372,6 +418,10 @@ void loop()
deactivateRFID();
activateSD();
audio.loop();
if (currentNode!=NULL) {
currentNode->setSecondsPlayed(audio.getAudioCurrentTime());
}
deactivateSD();
activateRFID();
} else if (asyncStart) {