Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
3a5b88a2d1
|
|||
fb31d82fbf
|
|||
6668d56171
|
|||
9009e55d4f
|
|||
59fcb3335e
|
|||
1ab1e2e1d0
|
|||
78b6eec8cb
|
|||
e85a045fe6
|
|||
e744d92b0e
|
|||
ae08930bca
|
|||
77eef80da0
|
|||
ce7db7b0fd
|
|||
8f90d6ff05
|
|||
2cce64b11c
|
11
README.md
11
README.md
@@ -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:
|
||||||
|
|
||||||
|
@@ -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,24 +63,9 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
// 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 balanceLevel = 0; // left-right balance between -16 to 16 inclusive (both)
|
|
||||||
int eqLow = 0; // equalizer low value between -40 and 6dB
|
|
||||||
int eqMid = 0; // equalizer mid value between -40 and 6dB
|
|
||||||
int eqHigh = 0; // equalizer high value between -40 and 6dB
|
|
||||||
bool muted = false; // currently muted (does not affect currentVolume)
|
|
||||||
bool audioPlaying = false; // play song or not?; don't play by standard
|
|
||||||
String currentSongPath; // path to currently playing song
|
|
||||||
String currentPlaylist; // path to current playlist
|
|
||||||
int currentPlaylistPosition; // the current position in the current playlist
|
|
||||||
bool restore_old_state; // restore the old state of EEPROM?
|
|
||||||
|
|
||||||
// all other variables needed
|
// all other variables needed
|
||||||
Audio audio; // Audio object (for playing audio, decoding mp3, ...)
|
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) + simple web UI
|
||||||
WebServer api_server(webport_api); // web server for api (e.g. pause/play)
|
|
||||||
String defaultPlaylistFolder = "/audio"; // default playlist folder
|
String defaultPlaylistFolder = "/audio"; // default playlist folder
|
||||||
String defaultPlaylist = defaultPlaylistFolder + "/" + directoryPlaylistName + playlistExtension; // default path to playlist
|
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
|
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
|
||||||
@@ -90,6 +73,19 @@ bool apON = false;
|
|||||||
Preferences configuration;
|
Preferences configuration;
|
||||||
long lastTimeEEPROMwritten = 0; // used to limit the number of eeprom write cycles
|
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)
|
||||||
|
int currentVolume = 100; // variable where current volume (0...maxVolume) is stored
|
||||||
|
int balanceLevel = 0; // left-right balance between -16 to 16 inclusive (both)
|
||||||
|
int eqLow = 0; // equalizer low value between -40 and 6dB
|
||||||
|
int eqMid = 0; // equalizer mid value between -40 and 6dB
|
||||||
|
int eqHigh = 0; // equalizer high value between -40 and 6dB
|
||||||
|
bool muted = false; // currently muted (does not affect currentVolume)
|
||||||
|
bool audioPlaying = false; // play song or not?; don't play by standard
|
||||||
|
String currentSongPath; // path to currently playing song
|
||||||
|
String currentPlaylist = defaultPlaylist; // path to current playlist
|
||||||
|
int currentPlaylistPosition = -1; // the current position in the current playlist
|
||||||
|
bool restore_old_state; // restore the old state of EEPROM?
|
||||||
|
|
||||||
// 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
|
||||||
@@ -166,7 +161,7 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (millis() - lastTimeEEPROMwritten >= EEPROM_WRITE_INTERVAL) {
|
if (millis() - lastTimeEEPROMwritten >= EEPROM_WRITE_INTERVAL) {
|
||||||
writeLastState(); // save current state to eeprom
|
writeLastState(); // save current state to eeprom
|
||||||
lastTimeEEPROMwritten = millis();
|
lastTimeEEPROMwritten = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 += " <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 += " <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 += " <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 += " <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 += " <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 += " <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 += " <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 += " <span class='badge text-bg-warning'>/api/v1/system/restore_state/</span>";
|
||||||
|
html += " <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 += " <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();
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user