14 Commits

3 changed files with 344 additions and 86 deletions

View File

@@ -61,18 +61,16 @@ Features, already implemented, or still in progress for the v1.0.0 release!
- [X] /api/v1/system/restart/ - [X] /api/v1/system/restart/
- [X] /api/v1/system/name - [X] /api/v1/system/name
- [X] /api/v1/system/name/change - [X] /api/v1/system/name/change
- [ ] /api/v1/system/save_state - [X] /api/v1/system/restore_state/{on,off,get}
- [ ] /api/v1/system/save_playing - [X] /api/v1/system/restore_playing/{on,off,get}
- [ ] /api/v1/system/version - [X] /api/v1/system/version
- [X] /api/v1/system/wifi/change - [X] /api/v1/system/wifi/change
- [X] /api/v1/system/wifi/get_ssid - [X] /api/v1/system/wifi/get_ssid
- [ ] /api/v1/files/get - [ ] /api/v1/files/get
- [ ] /api/v1/files/upload - [ ] /api/v1/files/upload
- [X] Automatic WiFi connection - [X] Automatic WiFi connection
- [X] Access Point opened when no WiFi connection could be established - [X] Access Point opened when no WiFi connection could be established
- [ ] Implement a configuration server running on port 8080 - [ ] Implement a simple web interface running on location /
- [ ] Edit wifi connection
- [ ] Edit friendly name of the NetSpeaker
- [X] Improve the volume endpoint handler (currently pretty undynamic - not anymore :) - [X] Improve the volume endpoint handler (currently pretty undynamic - not anymore :)
- [ ] Add better encoding as umlauts are not displayed correctly **sometimes** - [ ] Add better encoding as umlauts are not displayed correctly **sometimes**
@@ -84,6 +82,7 @@ Thanks to...
- the makers of Arduino IDE - the makers of Arduino IDE
- schreibfaul1 (github) for creating the audio library used in this project - schreibfaul1 (github) for creating the audio library used in this project
- espressif for making the best microprocessor I've ever seen - espressif for making the best microprocessor I've ever seen
- the authors of Bootstrap which is being used for the simple web UI
External librarys used: External librarys used:

View File

@@ -22,7 +22,7 @@ For more information, please refer to <http://unlicense.org/>
// define all constants // define all constants
const String version = "NetSpeaker v0.2.5-dev"; // version string used e.g. for access point ssid when wifi couldn't connect const String version = "NetSpeaker v0.3.3-dev"; // version string used e.g. for access point ssid when wifi couldn't connect
const int operation_mode = 0; // 0: interconnected (no buttons, but api and wifi); 1: standalone (no wifi; no api) const int operation_mode = 0; // 0: interconnected (no buttons, but api and wifi); 1: standalone (no wifi; no api)
const int SD_CS = 5; // BOARD SPECIFIC const int SD_CS = 5; // BOARD SPECIFIC
const int SPI_MISO = 19; // BOARD SPECIFIC const int SPI_MISO = 19; // BOARD SPECIFIC
@@ -39,11 +39,9 @@ const int backwardButtonPin = 27; // pin for ba
const int waitOnSDCardEject = 5000; // defines how long to wait (in ms) after the button for SD card eject was pressed (on pin 'sdCardEjectPin'!) const int waitOnSDCardEject = 5000; // defines how long to wait (in ms) after the button for SD card eject was pressed (on pin 'sdCardEjectPin'!)
const int retrySDMountTreshold = 1000; // defines how long to wait (in ms) to the next try of mounting the sd card const int retrySDMountTreshold = 1000; // defines how long to wait (in ms) to the next try of mounting the sd card
const int readyPin = 32; // for an LED that shows if everything started up correctly (SD card mounted, wifi connected, ...) const int readyPin = 32; // for an LED that shows if everything started up correctly (SD card mounted, wifi connected, ...)
const int webport_config = 8080; // port for the configuration interface
const int webport_api = 80; // port for the api (can't be the same as 'webport_config', will throw an error) const int webport_api = 80; // port for the api (can't be the same as 'webport_config', will throw an error)
const int maxVolume = 100; // defines the volume steps (max. 255) const int maxVolume = 100; // defines the volume steps (max. 255)
const int EEPROM_WRITE_INTERVAL = 10000; // how long to wait until the next EEPROM saving process, in ms const int EEPROM_WRITE_INTERVAL = 10000; // how long to wait until the next EEPROM saving process, in ms
const bool runConfServer = true; // variable if the config server should be setup and run
const String playlistExtension = ".m3u"; // extension for playlist files const String playlistExtension = ".m3u"; // extension for playlist files
const String directoryPlaylistName = ".directory"; // name for directory playlists (not a path, just a filename without ext.!) const String directoryPlaylistName = ".directory"; // name for directory playlists (not a path, just a filename without ext.!)
const String wifiConfigPath = "/.wifi.conf"; // MAX LENGTH: 32 resp. 63 chars; path to configuration file; content: <WiFi SSID>\n<WiFi PSK> (ssid and password divided by an newline) const String wifiConfigPath = "/.wifi.conf"; // MAX LENGTH: 32 resp. 63 chars; path to configuration file; content: <WiFi SSID>\n<WiFi PSK> (ssid and password divided by an newline)
@@ -65,6 +63,15 @@ const char PREFERENCES_KEY_MUTED[] = "muted"; // preference
const char PREFERENCES_KEY_PLAYLIST_PATH[] = "playlist"; // preferences key name for playlist path (for state restore) const char PREFERENCES_KEY_PLAYLIST_PATH[] = "playlist"; // preferences key name for playlist path (for state restore)
const char PREFERENCES_KEY_PLAYLIST_INDEX[] = "pl-index"; // preferences key name for current playlist index (for state restore) const char PREFERENCES_KEY_PLAYLIST_INDEX[] = "pl-index"; // preferences key name for current playlist index (for state restore)
// all other variables needed
Audio audio; // Audio object (for playing audio, decoding mp3, ...)
WebServer api_server(webport_api); // web server for api (e.g. pause/play) + simple web UI
String defaultPlaylistFolder = "/audio"; // default playlist folder
String defaultPlaylist = defaultPlaylistFolder + "/" + directoryPlaylistName + playlistExtension; // default path to playlist
int currentWiFiMode = 0; // DON'T CHANGE IF NOT KNOWING WHAT YOU'RE DOING; selector for wether an AP (1) should be set up or a wifi (0) should be connected
bool apON = false; // DON'T CHANGE IF NOT KNOWING WHAT YOU'RE DOING; is the access point opened?
Preferences configuration;
long lastTimeEEPROMwritten = 0; // used to limit the number of eeprom write cycles
// create all variables for playback (will be set in 'void setup()' by 'restoreLastState();' call) // create all variables for playback (will be set in 'void setup()' by 'restoreLastState();' call)
int currentVolume = 100; // variable where current volume (0...maxVolume) is stored int currentVolume = 100; // variable where current volume (0...maxVolume) is stored
@@ -75,21 +82,10 @@ int eqHigh = 0; // equalizer high value between -40 and 6dB
bool muted = false; // currently muted (does not affect currentVolume) bool muted = false; // currently muted (does not affect currentVolume)
bool audioPlaying = false; // play song or not?; don't play by standard bool audioPlaying = false; // play song or not?; don't play by standard
String currentSongPath; // path to currently playing song String currentSongPath; // path to currently playing song
String currentPlaylist; // path to current playlist String currentPlaylist = defaultPlaylist; // path to current playlist
int currentPlaylistPosition; // the current position in the current playlist int currentPlaylistPosition = -1; // the current position in the current playlist
bool restore_old_state; // restore the old state of EEPROM? bool restore_old_state; // restore the old state of EEPROM?
// all other variables needed
Audio audio; // Audio object (for playing audio, decoding mp3, ...)
WebServer conf_server(webport_config); // web server for configuration (e.g. WiFi password setting)
WebServer api_server(webport_api); // web server for api (e.g. pause/play)
String defaultPlaylistFolder = "/audio"; // default playlist folder
String defaultPlaylist = defaultPlaylistFolder + "/" + directoryPlaylistName + playlistExtension; // default path to playlist
int currentWiFiMode = 0; // DON'T CHANGE IF NOT KNOWING WHAT YOU'RE DOING; selector for wether an AP (1) should be set up or a wifi (0) should be connected
bool apON = false; // DON'T CHANGE IF NOT KNOWING WHAT YOU'RE DOING; is the access point opened?
Preferences configuration;
long lastTimeEEPROMwritten = 0; // used to limit the number of eeprom write cycles
// struct for playback info (especially when playing mp3 files with id3 tags) // struct for playback info (especially when playing mp3 files with id3 tags)
struct playbackInfo { struct playbackInfo {
String title; // e.g. Big Buck Bunny String title; // e.g. Big Buck Bunny
@@ -121,8 +117,7 @@ void setup() {
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(); setupWiFi();
setupConfigWeb(); setupWeb();
setupApiWeb();
} else if (operation_mode == 1) { // things only need to be done if in standalone mode } else if (operation_mode == 1) { // things only need to be done if in standalone mode
// setup all pins (not SPI and other buses!) // setup all pins (not SPI and other buses!)
pinMode(sdCardEjectPin, INPUT); pinMode(sdCardEjectPin, INPUT);
@@ -147,7 +142,7 @@ void loop() {
setupWiFi(); // if connection was lost setupWiFi(); // if connection was lost
if (audioPlaying) audio.loop(); // play audio if not paused if (audioPlaying) audio.loop(); // play audio if not paused
if (esp_timer_get_time() % 60 == 0) { // REALLY NEEDED; audio playing won't work else! if (esp_timer_get_time() % 60 == 0) { // REALLY NEEDED; audio playing won't work else!
loopServer(); // listen on http ports all 60 ms api_server.handleClient(); // listen on http ports all 60 ms
} }
} else if (operation_mode == 1) { // things only need to be done if in standalone mode } else if (operation_mode == 1) { // things only need to be done if in standalone mode
if (audioPlaying) audio.loop(); // play audio if not paused if (audioPlaying) audio.loop(); // play audio if not paused

View File

@@ -39,22 +39,258 @@ bool isStringConvertable2Int(String toIntStr) {
return true; return true;
} }
void configRoot() {
void api_root() {
Serial.println("[HTTP] [Config] 200 - GET '/'"); Serial.println("[HTTP] [Config] 200 - GET '/'");
String html = "<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Configuration | "; String html = "<!doctype html>";
html += version; html += "<html><head>";
html += "</title></head><body><div style='text-align:center;'><br><br><p>Configuration of your</p><h3>NetSpeaker</h3><br><hr><br><p>Work in progress!</p></div></body></html>"; html += "<meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>";
html += "<title>Web UI | " + version + "</title>";
html += "<link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css' rel='stylesheet' integrity='sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN' crossorigin='anonymous'>";
html += "</head><body data-bs-theme='dark'>";
html += "<div class='text-center container-sm' style='padding: 4em 0em'>";
conf_server.send(200, "text/html", html); html += "<p>Welcome to</p>";
} html += "<h1>" + configuration.getString(PREFERENCES_KEY_FRIENDLY_NAME, "NetSpeaker") + "</h1>";
html += "<code>" + version + "</code>";
void apiRoot() { html += "<hr style='margin: 3em 0em;'>";
Serial.println("[HTTP] [API] 200 - GET '/'");
String html = "<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>API | "; // informations accordion
html += version; html += "<div class='accordion text-start' id='accordion-info'>";
html += "</title></head><body><div style='text-align:center;'><br><br><p>API of your</p><br><h3>NetSpeaker</h3><br><hr><br><p>Work in progress!</p></div></body></html>"; html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-info-playback' aria-expanded='true' aria-controls='accordion-info-playback'>";
html += " Playback information";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-info-playback' class='accordion-collapse collapse' data-bs-parent='#accordion-info'>";
html += " <div class='accordion-body'>";
html += " <table class='table table-striped'><tbody><tr>";
html += " <td>Playing</td>";
html += " <td class='text-end'>";
html += audioPlaying ? "yes" : "no";
html += " </td>";
html += " </tr><tr>";
html += " <td>Volume</td>";
html += " <td class='text-end'>" + String(currentVolume) + "/" + String(maxVolume) + "%</td>";
html += " </tr><tr>";
html += " <td>Muted</td>";
html += " <td class='text-end'>";
html += muted ? "yes" : "no";
html += " </td>";
html += " </tr><tr>";
html += " <td>Equalizer Low</td>";
html += " <td class='text-end'>" + String(eqLow-40) + "dB</td>";
html += " </tr><tr>";
html += " <td>Equalizer Mid</td>";
html += " <td class='text-end'>" + String(eqMid-40) + "dB</td>";
html += " </tr><tr>";
html += " <td>Equalizer High</td>";
html += " <td class='text-end'>" + String(eqHigh-40) + "dB</td>";
html += " </tr><tr>";
html += " <td>Balance (range -16 | +16)</td>";
html += " <td class='text-end'>" + String(balanceLevel-16) + "</td>";
html += " </tr><tr>";
html += " <td>Path to playlist</td>";
html += " <td class='text-end'>" + currentPlaylist + "</td>";
html += " </tr><tr>";
html += " <td>Index in playlist (starting from 0)</td>";
html += " <td class='text-end'>" + String(currentPlaylistPosition) + "</td>";
html += " </tr><tr>";
html += " <td>Resource type</td>";
html += " <td class='text-end'>" + pbInfo.type + "</td>";
html += " </tr><tr>";
html += " <td>Resource title</td>";
html += " <td class='text-end'>" + pbInfo.title + "</td>";
html += " </tr><tr>";
html += " <td>Resource album</td>";
html += " <td class='text-end'>" + pbInfo.album + "</td>";
html += " </tr><tr>";
html += " <td>Resource artist</td>";
html += " <td class='text-end'>" + pbInfo.artist + "</td>";
html += " </tr><tr>";
html += " <td>Resource track number</td>";
html += " <td class='text-end'>" + pbInfo.track + "</td>";
html += " </tr><tr>";
html += " <td>Resource year</td>";
html += " <td class='text-end'>" + pbInfo.year + "</td>";
html += " </tr><tr>";
html += " <td>Resource genre</td>";
html += " <td class='text-end'>" + pbInfo.genre + "</td>";
html += " </tr></tbody></table>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-info-general' aria-expanded='true' aria-controls='accordion-info-general'>";
html += " General information";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-info-general' class='accordion-collapse collapse' data-bs-parent='#accordion-info'>";
html += " <div class='accordion-body'>";
html += " <table class='table table-striped'><tbody><tr>";
html += " <td>Version</td>";
html += " <td class='text-end'>" + version + "</td>";
html += " </tr><tr>";
html += " <td>Friendly name</td>";
html += " <td class='text-end'>" + configuration.getString(PREFERENCES_KEY_FRIENDLY_NAME, "<not_given>") + "</td>";
html += " </tr><tr>";
html += " <td>Save & Restore state</td>";
html += " <td class='text-end'>";
html += configuration.getBool(PREFERENCES_KEY_RESTORE_OLD_STATE, false) ? "yes" : "no";
html += " </td>";
html += " </tr><tr>";
html += " <td>Save & Restore playing state (default no)</td>";
html += " <td class='text-end'>";
html += configuration.getBool(PREFERENCES_KEY_RESTORE_PLAYING, false) ? "yes" : "no";
html += " </td>";
html += " </tr><tr>";
html += " <td>WiFi SSID</td>";
html += " <td class='text-end'>" + configuration.getString(PREFERENCES_KEY_WIFI_SSID, "") + "</td>";
html += " </tr></tbody></table>";
html += " </div>";
html += " </div>";
html += " </div>";
html += "</div>";
html += "<hr style='margin: 3em 0em;'>";
// api functions accordion
html += "<div class='accordion text-start' id='accordion'>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-playback' aria-expanded='true' aria-controls='accordion-collapse-playback'>";
html += " Playback";
html += " &nbsp;<span class='badge text-bg-success'>/api/v1/playback/</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-playback' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-volume' aria-expanded='true' aria-controls='accordion-collapse-volume'>";
html += " Volume";
html += " &nbsp;<span class='badge text-bg-info'>/api/v1/volume/</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-volume' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-playlist' aria-expanded='true' aria-controls='accordion-collapse-playlist'>";
html += " Playlist";
html += " &nbsp;<span class='badge text-bg-success'>/api/v1/playlist/</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-playlist' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-balance' aria-expanded='true' aria-controls='accordion-collapse-balance'>";
html += " Balance";
html += " &nbsp;<span class='badge text-bg-info'>/api/v1/balance/</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-balance' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-eq' aria-expanded='true' aria-controls='accordion-collapse-eq'>";
html += " Equalizer";
html += " &nbsp;<span class='badge text-bg-info'>/api/v1/eq/</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-eq' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-frname' aria-expanded='true' aria-controls='accordion-collapse-frname'>";
html += " Friendly name";
html += " &nbsp;<span class='badge text-bg-warning'>/api/v1/system/name</span>"; // explicitly no trailing / as there are the two endpoints /name and /name/change
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-frname' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-wifi' aria-expanded='true' aria-controls='accordion-collapse-wifi'>";
html += " WiFi configuration";
html += " &nbsp;<span class='badge text-bg-warning'>/api/v1/system/wifi/</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-wifi' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-restore' aria-expanded='true' aria-controls='accordion-collapse-restore'>";
html += " Save & Restore";
html += " &nbsp;<span class='badge text-bg-warning'>/api/v1/system/restore_state/</span>";
html += " &nbsp;<span class='badge text-bg-warning'>/api/v1/system/restore_playing/</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-restore' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += " <div class='accordion-item'>";
html += " <h2 class='accordion-header'>";
html += " <button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-restart' aria-expanded='true' aria-controls='accordion-collapse-restart'>";
html += " Restart";
html += " &nbsp;<span class='badge text-bg-danger'>/api/v1/system/restart</span>";
html += " </button>";
html += " </h2>";
html += " <div id='accordion-collapse-restart' class='accordion-collapse collapse' data-bs-parent='#accordion'>";
html += " <div class='accordion-body'>";
html += " <strong>Work in progress</strong>";
html += " </div>";
html += " </div>";
html += " </div>";
html += "</div></div>";
html += "<script src='https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js' integrity='sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL' crossorigin='anonymous'></script>";
html += "</body></html>";
api_server.send(200, "text/html", html); api_server.send(200, "text/html", html);
} }
@@ -449,6 +685,51 @@ void api_v1_system_friendlyname_change() {
api_server.send(200, "application/json", generate_api_json(true, "\"friendly_name\": \"" + newFriendlyName + "\"")); api_server.send(200, "application/json", generate_api_json(true, "\"friendly_name\": \"" + newFriendlyName + "\""));
} }
void api_v1_system_restore_state() {
String option = api_server.pathArg(0);
bool success = true;
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/system/restore_state/%s'\n", option);
if (option == "get") {
// just here that calling .../get wont respond 404
} else if (option == "on") {
configuration.putBool(PREFERENCES_KEY_RESTORE_OLD_STATE, true);
} else if (option == "off") {
configuration.putBool(PREFERENCES_KEY_RESTORE_OLD_STATE, false);
} else {
success = false;
}
String content = "\"restore_state\": "; // prepare the http response
content += configuration.getBool(PREFERENCES_KEY_RESTORE_OLD_STATE, false) ? "true" : "false";
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
}
void api_v1_system_restore_playing() {
String option = api_server.pathArg(0);
bool success = true;
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/system/restore_playing/%s'\n", option);
if (option == "get") {
// just here that calling .../get wont respond 404
} else if (option == "on") {
configuration.putBool(PREFERENCES_KEY_RESTORE_PLAYING, true);
} else if (option == "off") {
configuration.putBool(PREFERENCES_KEY_RESTORE_PLAYING, false);
} else {
success = false;
}
String content = "\"restore_playing\": "; // prepare the http response
content += configuration.getBool(PREFERENCES_KEY_RESTORE_PLAYING, false) ? "true" : "false";
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
}
void api_v1_system_version() {
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/system/version'\n");
api_server.send(200, "application/json", generate_api_json(true, "\"version\": \"" + version + "\""));
}
void api_v1_system_wifi_getssid() { void api_v1_system_wifi_getssid() {
Serial.println("[HTTP] [API] 200 - GET '/api/v1/system/wifi/get_ssid'"); Serial.println("[HTTP] [API] 200 - GET '/api/v1/system/wifi/get_ssid'");
@@ -491,56 +772,39 @@ void api_v1_system_wifi_change() {
api_server.send(200, "application/json", generate_api_json(true, "\"ssid\": \"" + ssid + "\", \"psk\": \"" + psk + "\"")); api_server.send(200, "application/json", generate_api_json(true, "\"ssid\": \"" + ssid + "\", \"psk\": \"" + psk + "\""));
} }
void setupWeb() {
void setupConfigWeb() { api_server.on("/", api_root);
if (runConfServer) {
conf_server.onNotFound([]() {
Serial.println("[HTTP] [Config] 404: Not Found");
conf_server.send(404, "text/html", "<html><head><title>Not Found</title></head><body><h1>Not Found</h1><p>This resource does not exist on this server.</p></body></html>");
});
conf_server.on("/", configRoot);
Serial.println("[HTTP] [Config] Starting config server (http) on port " + String(webport_config));
conf_server.begin();
Serial.println("[HTTP] [Config] Started Config server");
}
}
void setupApiWeb() {
api_server.onNotFound([]() { api_server.onNotFound([]() {
Serial.println("[HTTP] [API] 404: Not Found"); Serial.println("[HTTP] [API] 404: Not Found");
api_server.send(404, "application/json", "{\"code\": 404, \"message\": \"Resource not found.\"}"); api_server.send(404, "application/json", "{\"code\": 404, \"message\": \"Resource not found.\"}");
}); });
api_server.on("/", apiRoot); api_server.on("/api/v1/playback/toggle", api_v1_playback_toggle);
api_server.on("/api/v1/playback/toggle", HTTP_GET, api_v1_playback_toggle); api_server.on("/api/v1/playback/play", api_v1_playback_play);
api_server.on("/api/v1/playback/play", HTTP_GET, api_v1_playback_play); api_server.on("/api/v1/playback/pause", api_v1_playback_pause);
api_server.on("/api/v1/playback/pause", HTTP_GET, api_v1_playback_pause); api_server.on("/api/v1/playback/next", api_v1_playback_next);
api_server.on("/api/v1/playback/next", HTTP_GET, api_v1_playback_next); api_server.on("/api/v1/playback/previous", api_v1_playback_previous);
api_server.on("/api/v1/playback/previous", HTTP_GET, api_v1_playback_previous); api_server.on("/api/v1/playback/info", api_v1_playback_info);
api_server.on("/api/v1/playback/info", HTTP_GET, api_v1_playback_info); api_server.on(UriBraces("/api/v1/playback/{}"), api_v1_playback_byindex);
api_server.on(UriBraces("/api/v1/playback/{}"), HTTP_GET, api_v1_playback_byindex); api_server.on("/api/v1/playlist/get", api_v1_playlist_get);
api_server.on("/api/v1/playlist/get", HTTP_GET, api_v1_playlist_get); api_server.on("/api/v1/playlist/create", api_v1_playlist_create);
api_server.on("/api/v1/playlist/create", HTTP_POST, api_v1_playlist_create); api_server.on("/api/v1/playlist/play", api_v1_playlist_play);
api_server.on("/api/v1/playlist/play", HTTP_POST, api_v1_playlist_play); api_server.on(UriBraces("/api/v1/volume/{}"), api_v1_volume);
api_server.on(UriBraces("/api/v1/volume/{}"), HTTP_GET, api_v1_volume); api_server.on(UriBraces("/api/v1/balance/{}"), api_v1_balance);
api_server.on(UriBraces("/api/v1/balance/{}"), HTTP_GET, api_v1_balance); api_server.on("/api/v1/eq/get", api_v1_eq_get);
api_server.on("/api/v1/eq/get", HTTP_GET, api_v1_eq_get); api_server.on(UriBraces("/api/v1/eq/reset"), api_v1_eq_reset);
api_server.on(UriBraces("/api/v1/eq/reset"), HTTP_GET, api_v1_eq_reset); api_server.on(UriBraces("/api/v1/eq/low/{}"), api_v1_eq_low);
api_server.on(UriBraces("/api/v1/eq/low/{}"), HTTP_GET, api_v1_eq_low); api_server.on(UriBraces("/api/v1/eq/mid/{}"), api_v1_eq_mid);
api_server.on(UriBraces("/api/v1/eq/mid/{}"), HTTP_GET, api_v1_eq_mid); api_server.on(UriBraces("/api/v1/eq/high/{}"), api_v1_eq_high);
api_server.on(UriBraces("/api/v1/eq/high/{}"), HTTP_GET, api_v1_eq_high); api_server.on("/api/v1/system/restart", api_v1_system_restart);
api_server.on("/api/v1/system/restart", HTTP_GET, api_v1_system_restart); api_server.on("/api/v1/system/name", api_v1_system_friendlyname_get);
api_server.on("/api/v1/system/name", HTTP_GET, api_v1_system_friendlyname_get); api_server.on("/api/v1/system/name/change", api_v1_system_friendlyname_change);
api_server.on("/api/v1/system/name/change", HTTP_POST, api_v1_system_friendlyname_change); api_server.on(UriBraces("/api/v1/system/restore_state/{}"), api_v1_system_restore_state);
api_server.on("/api/v1/system/wifi/change", HTTP_POST, api_v1_system_wifi_change); api_server.on(UriBraces("/api/v1/system/restore_playing/{}"), api_v1_system_restore_playing);
api_server.on("/api/v1/system/wifi/get_ssid", HTTP_GET, api_v1_system_wifi_getssid); api_server.on("/api/v1/system/version", api_v1_system_version);
api_server.on("/api/v1/system/wifi/change", api_v1_system_wifi_change);
api_server.on("/api/v1/system/wifi/get_ssid", api_v1_system_wifi_getssid);
Serial.println("[HTTP] [API] Starting API server (http) on port " + String(webport_api)); Serial.println("[HTTP] [API] Starting API server (http) on port " + String(webport_api));
api_server.begin(); api_server.begin();
Serial.println("[HTTP] [API] Started config server"); Serial.println("[HTTP] [API] Started API server");
}
void loopServer() {
api_server.handleClient();
if (runConfServer) conf_server.handleClient();
} }