From 022c96ea13b6f0c4ac2df49a53fcb79cbe3ecca5 Mon Sep 17 00:00:00 2001 From: BlueFox Date: Thu, 7 Dec 2023 20:24:46 +0100 Subject: [PATCH] Added volume up, down, mute, get options --- src/NetSpeaker/NetSpeaker.ino | 39 ++++---- src/NetSpeaker/audio.ino | 5 +- src/NetSpeaker/fs.ino | 3 +- src/NetSpeaker/webserver.ino | 166 +++++++++++++++++++++++++--------- src/NetSpeaker/wifi.ino | 3 +- 5 files changed, 147 insertions(+), 69 deletions(-) diff --git a/src/NetSpeaker/NetSpeaker.ino b/src/NetSpeaker/NetSpeaker.ino index 20ade57..11f5b5f 100755 --- a/src/NetSpeaker/NetSpeaker.ino +++ b/src/NetSpeaker/NetSpeaker.ino @@ -11,12 +11,13 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI For more information, please refer to */ -#include "WiFi.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFi/src -#include "Audio.h" // https://github.com/schreibfaul1/ESP32-audioI2S -#include "SPI.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/SPI/src -#include "SD.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/src -#include "FS.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/FS/src -#include // https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer +#include "WiFi.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFi/src +#include "Audio.h" // https://github.com/schreibfaul1/ESP32-audioI2S +#include "SPI.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/SPI/src +#include "SD.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/src +#include "FS.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/FS/src +#include // https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer +#include // https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer // define all constants @@ -47,9 +48,9 @@ const String apSSID = version; // ssid of access point open const String apPSK = "aA16161Aa"; // pre-shared-key of access point opened when not able to connect to wifi // create all needed variables -int currentVolume = 21; // variable where current volume (0...21) is stored +int currentVolume = 20; // variable where current volume (0...20) is stored String currentSongPath; // path to currently playing song -bool audioPlaying = true; // play song or not? +bool audioPlaying = true; // play song or not? Audio audio; // Audio object (for playing audio, decoding mp3, ...) int currentPlaylistPosition = 0; // the current position in the current playlist String currentPlaylist = "/audio/" + directoryPlaylistName + playlistExtension; // path to current playlist @@ -69,7 +70,7 @@ void setup() { delay(retrySDMountTreshold); } - if(operation_mode == 0) { // things only need to be done if in interconnected mode + if (operation_mode == 0) { // things only need to be done if in interconnected mode setupWiFi(); setupConfigWeb(); setupApiWeb(); @@ -82,25 +83,25 @@ void setup() { pinMode(readyPin, OUTPUT); } else { Serial.println("[FATAL] PLEASE CHOOSE A OPERATION MODE! VALID OPTIONS: 0; 1. SLEEPING FOREVER."); - while(true) delay(100); + while (true) delay(100); } - setupAudio(); // setup audio library - createPlaylistFromDirectory("/audio"); // create playlist from default dir ("/audio") + setupAudio(); // setup audio library + createPlaylistFromDirectory("/audio"); // create playlist from default dir ("/audio") audio.connecttoFS(SD, getSongFromPlaylist(currentPlaylist, currentPlaylistPosition).c_str()); // play first element of the playlist digitalWrite(readyPin, HIGH); // show that startup is done and everything works fine } void loop() { - if(operation_mode == 0) { // things only need to be done if in interconnected mode - setupWiFi(); // if connection was lost - if(audioPlaying) audio.loop(); // play audio if not paused - if(esp_timer_get_time()%100 == 0) { // REALLY NEEDED; audio playing won't work else! - loopServer(); // listen on http ports all 100 ms + if (operation_mode == 0) { // things only need to be done if in interconnected mode + setupWiFi(); // if connection was lost + if (audioPlaying) audio.loop(); // play audio if not paused + if (esp_timer_get_time() % 50 == 0) { // REALLY NEEDED; audio playing won't work else! + loopServer(); // listen on http ports all 50 ms } } - if(operation_mode == 1) { // things only need to be done if in standalone mode - if(audioPlaying) audio.loop(); // play audio if not paused + if (operation_mode == 1) { // things only need to be done if in standalone mode + if (audioPlaying) audio.loop(); // play audio if not paused loopBtnListeners(); diff --git a/src/NetSpeaker/audio.ino b/src/NetSpeaker/audio.ino index 88b18ae..71832f1 100755 --- a/src/NetSpeaker/audio.ino +++ b/src/NetSpeaker/audio.ino @@ -13,6 +13,7 @@ For more information, please refer to void setupAudio() { audio.setPinout(I2S_BLCK, I2S_LRC, I2S_DOUT); // tell the audio library what output pins to use + audio.setVolumeSteps(20); Serial.printf("[SETUP] Set up audio card successfully (Pins: BLCK %d | LRC %d | DOUT %d)\n", I2S_BLCK, I2S_LRC, I2S_DOUT); } @@ -73,11 +74,11 @@ void backwardButtonHandler() { } void setAudioVolume() { - int newCurrentVolume = analogRead(audioVolumePin) / 195; // read voltage from audioVolumePin and divide by 195 (min. volume is 0, max. 21; 21 fits 195 times into 4095 (maximum input)) + int newCurrentVolume = analogRead(audioVolumePin) / 204.75; // read voltage from audioVolumePin and divide by 204.75 (min. volume is 0, max. 20; 20 fits 204.75 times into 4095 (maximum input)) if(currentVolume != newCurrentVolume) { // just do it if the volume changed currentVolume = newCurrentVolume; audio.setVolume(currentVolume); // set volume - Serial.printf("[INFO] Set volume to %d/21!\n", currentVolume); + Serial.printf("[INFO] Set volume to %d/20!\n", currentVolume); } } diff --git a/src/NetSpeaker/fs.ino b/src/NetSpeaker/fs.ino index 39296f6..5cafac6 100755 --- a/src/NetSpeaker/fs.ino +++ b/src/NetSpeaker/fs.ino @@ -65,6 +65,7 @@ String getWiFiPSK() { return getWiFiPSK(wifiConfigPath); } + bool createPlaylistFromDirectory(String folderpath) { // create a .m3u playlist from all directory contents (the directory 'folderpath' is used) File folder; File playlist; @@ -111,7 +112,7 @@ String getSongFromPlaylist(String path, int position) { song = (char)playlist.read(); // read character... while (playlist.available()) { currentChar = (char)playlist.read(); - if (currentChar != '\n') { + if (currentChar != '\n' && currentChar != '\r') { song += currentChar; // ... except if and as long as a newline hits } else { break; // exit while loop diff --git a/src/NetSpeaker/webserver.ino b/src/NetSpeaker/webserver.ino index 9f2c1ce..47190d6 100755 --- a/src/NetSpeaker/webserver.ino +++ b/src/NetSpeaker/webserver.ino @@ -11,37 +11,59 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI For more information, please refer to */ +String generate_api_json(bool success, String content, bool plain) { + String currentResourcePlaying = getSongFromPlaylist(currentPlaylist, currentPlaylistPosition); + + String json = "{\"success\": "; // success + json += success ? "true" : "false"; + json += ", "; + json += "\"state\": \""; // playing or paused? + json += audioPlaying ? "playing" : "paused"; + json += "\", "; + json += "\"currentResource\": \"" + currentResourcePlaying + "\""; // current playing resource + if (!plain) { + json += ", "; + json += content; + } + json += "}"; + + return json; +} +String generate_api_json(bool success) { // if you just want the basic json frame with the basic info + return generate_api_json(success, "", true); +} +String generate_api_json(bool success, String content) { // when info is to be embedded + return generate_api_json(success, content, false); +} + + void configRoot() { Serial.println("[HTTP] [Config] 200 - GET '/'"); - String html = ""; + String html = "<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Configuration | "; html += version; - html += "configuration


Configuration of your

NetSpeaker




Work in progress!

"; - + html += "


Configuration of your

NetSpeaker




Work in progress!

"; + conf_server.send(200, "text/html", html); } void apiRoot() { Serial.println("[HTTP] [API] 200 - GET '/'"); - String html = ""; + String html = "<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>API | "; html += version; - html += "configuration


API of your


NetSpeaker




Work in progress!

"; - + html += "


API of your


NetSpeaker




Work in progress!

"; + api_server.send(200, "text/html", html); } void api_v1_playback_toggle() { - Serial.println("[HTTP] [API] 200 - GET '/_api/v1/playback/toggle'"); + Serial.println("[HTTP] [API] 200 - GET '/api/v1/playback/toggle'"); audioPlaying = !audioPlaying; Serial.printf("[INFO] Playback has switched: %s\n", audioPlaying ? "Playing" : "Paused"); - String json = "{'success': true, 'state': '"; - json += audioPlaying ? "playing" : "paused"; - json += "'}"; - - api_server.send(200, "application/json", json); + api_server.send(200, "application/json", generate_api_json(true)); } void api_v1_playback_play() { @@ -50,51 +72,104 @@ void api_v1_playback_play() { audioPlaying = true; Serial.printf("[INFO] Playback has switched: Playing\n"); - String json = "{'success': true, 'state': 'playing'}"; - api_server.send(200, "application/json", json); + api_server.send(200, "application/json", generate_api_json(true)); } void api_v1_playback_pause() { - Serial.println("[HTTP] [API] 200 - GET '/_api/v1/playback/pause'"); + Serial.println("[HTTP] [API] 200 - GET '/api/v1/playback/pause'"); audioPlaying = false; Serial.printf("[INFO] Playback has switched: Paused\n"); - String json = "{'success': true, 'state': 'paused'}"; - api_server.send(200, "application/json", json); + api_server.send(200, "application/json", generate_api_json(true)); } void api_v1_playback_next() { - Serial.println("[HTTP] [API] 200 - GET '/_api/v1/playback/next'"); + Serial.println("[HTTP] [API] 200 - GET '/api/v1/playback/next'"); - String resourcePath = nextAudio(); + nextAudio(); - String json = "{'success': true, 'state': '"; - json += audioPlaying ? "playing" : "paused"; - json += "', 'currentResource': '"; - json += resourcePath; - json += "'}"; - - api_server.send(200, "application/json", json); + api_server.send(200, "application/json", generate_api_json(true)); } void api_v1_playback_previous() { - Serial.println("[HTTP] [API] 200 - GET '/_api/v1/playback/previous'"); + Serial.println("[HTTP] [API] 200 - GET '/api/v1/playback/previous'"); - String resourcePath = previousAudio(); + previousAudio(); - String json = "{'success': true, 'state': '"; - json += audioPlaying ? "playing" : "paused"; - json += "', 'currentResource': '"; - json += resourcePath; - json += "'}"; - - api_server.send(200, "application/json", json); + api_server.send(200, "application/json", generate_api_json(true)); +} + +void api_v1_playback_volume() { + String option = api_server.pathArg(0); + bool success = true; + Serial.printf("[HTTP] [API] 200 - GET '/api/v1/volume/%s'\n", option); + + if (option == "up") { + currentVolume++; + if (currentVolume > 20) currentVolume = 20; + } else if (option == "down") { + currentVolume--; + if (currentVolume < 0) currentVolume = 0; + } else if (option == "mute") { + currentVolume = 0; // TODO: remember the previous volume and just mute it; unmute has to be implemented then + } else if (option == "get") { + // just here that no 'success: false' is sent + } else if (option == "0") { + currentVolume = 0; + } else if (option == "1") { + currentVolume = 1; + } else if (option == "2") { + currentVolume = 2; + } else if (option == "3") { + currentVolume = 3; + } else if (option == "4") { + currentVolume = 4; + } else if (option == "5") { + currentVolume = 5; + } else if (option == "6") { + currentVolume = 6; + } else if (option == "7") { + currentVolume = 7; + } else if (option == "8") { + currentVolume = 8; + } else if (option == "9") { + currentVolume = 9; + } else if (option == "10") { + currentVolume = 10; + } else if (option == "11") { + currentVolume = 11; + } else if (option == "12") { + currentVolume = 12; + } else if (option == "13") { + currentVolume = 13; + } else if (option == "14") { + currentVolume = 14; + } else if (option == "15") { + currentVolume = 15; + } else if (option == "16") { + currentVolume = 16; + } else if (option == "17") { + currentVolume = 17; + } else if (option == "18") { + currentVolume = 18; + } else if (option == "19") { + currentVolume = 19; + } else if (option == "20") { + currentVolume = 20; + } else { + success = false; + } + + audio.setVolume(currentVolume); // set volume + String content = "\"volume\": "; // prepare the http response + content += String(currentVolume); + api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it } void setupConfigWeb() { - if(runConfServer) { + if (runConfServer) { conf_server.onNotFound([]() { Serial.println("[HTTP] [Config] 404: Not Found"); conf_server.send(404, "text/html", "Not Found

Not Found

This resource does not exist on this server.

"); @@ -109,15 +184,16 @@ void setupConfigWeb() { void setupApiWeb() { api_server.onNotFound([]() { - Serial.println("[HTTP] [API] 404: Not Found"); - api_server.send(404, "text/html", "Not Found

Not Found

This resource does not exist on this server.

"); - }); + Serial.println("[HTTP] [API] 404: Not Found"); + api_server.send(404, "text/html", "Not Found

Not Found

This resource does not exist on this server.

"); + }); api_server.on("/", apiRoot); - api_server.on("/_api/v1/playback/toggle", api_v1_playback_toggle); - api_server.on("/_api/v1/playback/play", api_v1_playback_play); - api_server.on("/_api/v1/playback/pause", api_v1_playback_pause); - api_server.on("/_api/v1/playback/next", api_v1_playback_next); - api_server.on("/_api/v1/playback/previous", api_v1_playback_previous); + api_server.on("/api/v1/playback/toggle", api_v1_playback_toggle); + api_server.on("/api/v1/playback/play", api_v1_playback_play); + api_server.on("/api/v1/playback/pause", api_v1_playback_pause); + api_server.on("/api/v1/playback/next", api_v1_playback_next); + api_server.on("/api/v1/playback/previous", api_v1_playback_previous); + api_server.on(UriBraces("/api/v1/volume/{}"), api_v1_playback_volume); Serial.println("[HTTP] [API] Starting API server (http) on port " + String(webport_api)); api_server.begin(); @@ -126,5 +202,5 @@ void setupApiWeb() { void loopServer() { api_server.handleClient(); - if(runConfServer) conf_server.handleClient(); + if (runConfServer) conf_server.handleClient(); } \ No newline at end of file diff --git a/src/NetSpeaker/wifi.ino b/src/NetSpeaker/wifi.ino index d4bc193..587427d 100755 --- a/src/NetSpeaker/wifi.ino +++ b/src/NetSpeaker/wifi.ino @@ -36,7 +36,7 @@ void setupWiFi() { WiFi.mode(WIFI_AP_STA); WiFi.begin(WiFiSSID, WiFiPSK); // wait for 10 seconds for wifi to connect - int start_timer = millis(); while(WiFi.status() != WL_CONNECTED) { if((millis()-start_timer) > 10000) break; } + int start_timer = millis(); while(WiFi.status() != WL_CONNECTED) { if((millis()-start_timer) > 20000) break; } if(WiFi.status() != WL_CONNECTED) { currentWiFiMode = 1; Serial.printf("[WiFi] Unable to connect to %s\n", WiFiSSID); @@ -58,5 +58,4 @@ void setupWiFi() { } break; } - } \ No newline at end of file