Compare commits
34 Commits
v0.2.0-dev
...
v0.2.5
Author | SHA1 | Date | |
---|---|---|---|
f9f8f17803 | |||
58ddb4fb74 | |||
c940a2afec | |||
0c42b53902 | |||
566ee35935 | |||
40f4d615d6 | |||
676e5fcadb | |||
97205ac43f | |||
d67dea275e | |||
06da06c519 | |||
763254b603 | |||
d06f2da73d | |||
cd62f273c1 | |||
90bd0f68c3 | |||
af662aa327 | |||
f53ef3c5dc | |||
425e579211 | |||
0fe0668659 | |||
3b12974ce2 | |||
5ee15af74d | |||
f2988c2eb5 | |||
87042e72e0 | |||
98a91e4974 | |||
e3c5d4b6c9 | |||
8904840428 | |||
c4362f998c | |||
ddb9a1e49e | |||
61e167d036 | |||
da55732450 | |||
d68d773090 | |||
2d41b65ca8 | |||
b1a677ab35 | |||
3b6811073a | |||
72e60c34e0 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
# ignore IDE specific files and folders
|
# ignore IDE specific files and folders
|
||||||
/.theia/
|
.theia/
|
33
API_DOC.txt
33
API_DOC.txt
@@ -1,33 +0,0 @@
|
|||||||
API Location Method Returns Redirects to Description
|
|
||||||
---------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
/api/ API root
|
|
||||||
----------------
|
|
||||||
|
|
||||||
|
|
||||||
/api/v1/playback/ Playback functions
|
|
||||||
----------------
|
|
||||||
/api/v1/playback/toggle GET JSON object Toggle Playback
|
|
||||||
/api/v1/playback/play GET JSON object Start playback
|
|
||||||
/api/v1/playback/pause GET JSON object Pause playback
|
|
||||||
/api/v1/playback/next GET JSON object Play next audio
|
|
||||||
/api/v1/playback/previous GET JSON object Play previous audio
|
|
||||||
/api/v1/playback/info GET JSON object Get the title, artist, album, path, type, ... of the current played resource
|
|
||||||
|
|
||||||
/api/v1/playlist/
|
|
||||||
----------------
|
|
||||||
/api/v1/playlist/get GET JSON object Returns the whole current playlist
|
|
||||||
|
|
||||||
/api/v1/volume/
|
|
||||||
----------------
|
|
||||||
/api/v1/volume/up GET JSON object Higher the volume (max. 20)
|
|
||||||
/api/v1/volume/down GET JSON object Lower the volume (min. 0)
|
|
||||||
/api/v1/volume/mute GET JSON object Set volume to 0
|
|
||||||
/api/v1/volume/get GET JSON object Do nothing, just get the volume
|
|
||||||
/api/v1/volume/<0-20> GET JSON object Set volume to a specific value between 0 and 20
|
|
||||||
|
|
||||||
/api/v1/settings/
|
|
||||||
----------------
|
|
||||||
/api/v1/settings/restart GET JSON object Performs a reboot of the microcontroller (after waiting 5000ms)
|
|
||||||
|
|
51
README.md
51
README.md
@@ -1,5 +1,11 @@
|
|||||||
# NetSpeaker
|
# NetSpeaker
|
||||||
|
|
||||||
|
[](https://unlicense.org/)
|
||||||
|
[](https://opensource.org/)
|
||||||
|
[](https://www.espressif.com/en/products/socs/esp32)
|
||||||
|
[]()
|
||||||
|
|
||||||
|
|
||||||
NetSpeaker is a project that aims to make it easier to build your own sound system, just like sqeezebox, for example.
|
NetSpeaker is a project that aims to make it easier to build your own sound system, just like sqeezebox, for example.
|
||||||
|
|
||||||
There are two operating modes, to adapt to the specific use case - default mode is 0. The mode can be set at compile time
|
There are two operating modes, to adapt to the specific use case - default mode is 0. The mode can be set at compile time
|
||||||
@@ -8,6 +14,13 @@ via the constant operation_mode. Valid choices are:
|
|||||||
- 0: interconnected (HTTP API and WiFi, no buttons); useful for bigger projects
|
- 0: interconnected (HTTP API and WiFi, no buttons); useful for bigger projects
|
||||||
- 1: standalone (Buttons, no WiFi and HTTP API); useful if you want to build a small player
|
- 1: standalone (Buttons, no WiFi and HTTP API); useful if you want to build a small player
|
||||||
|
|
||||||
|
Any questions? Hopefully a look into the **Wiki** will help.
|
||||||
|
|
||||||
|
|
||||||
|
## Audio compatibility
|
||||||
|
|
||||||
|
For audio compatibilty, please have a look at [schreibfaul1's git repository's wiki](https://github.com/schreibfaul1/ESP32-audioI2S/wiki#which-external-dacs-can-be-used), where he goes into that in detail.
|
||||||
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
@@ -22,25 +35,41 @@ Features, already implemented, or still in progress for the v1.0.0 release!
|
|||||||
- [X] /api/v1/playback/pause
|
- [X] /api/v1/playback/pause
|
||||||
- [X] /api/v1/playback/next
|
- [X] /api/v1/playback/next
|
||||||
- [X] /api/v1/playback/previous
|
- [X] /api/v1/playback/previous
|
||||||
|
- [X] /api/v1/playback/<index>
|
||||||
- [X] /api/v1/playback/info
|
- [X] /api/v1/playback/info
|
||||||
- [ ] /api/v1/playback/id3_image
|
- [ ] /api/v1/playback/id3_image
|
||||||
- [X] /api/v1/playlist/get
|
- [X] /api/v1/playlist/get
|
||||||
- [ ] /api/v1/playlist/append
|
|
||||||
- [ ] /api/v1/playlist/remove
|
|
||||||
- [X] /api/v1/volume/get
|
- [X] /api/v1/volume/get
|
||||||
- [X] /api/v1/volume/up
|
- [X] /api/v1/volume/up
|
||||||
- [X] /api/v1/volume/down
|
- [X] /api/v1/volume/down
|
||||||
- [X] /api/v1/volume/mute
|
- [X] /api/v1/volume/mute
|
||||||
- [X] /api/v1/volume/<0-20>
|
- [X] /api/v1/volume/<0-20>
|
||||||
- [X] /api/v1/settings/restart/
|
- [X] /api/v1/balance/<0-32>
|
||||||
|
- [X] /api/v1/balance/get
|
||||||
|
- [X] /api/v1/eq/get
|
||||||
|
- [X] /api/v1/eq/low/get
|
||||||
|
- [X] /api/v1/eq/low/<0-46>
|
||||||
|
- [X] /api/v1/eq/mid/get
|
||||||
|
- [X] /api/v1/eq/mid/<0-46>
|
||||||
|
- [X] /api/v1/eq/high/get
|
||||||
|
- [X] /api/v1/eq/high/<0-46>
|
||||||
|
- [X] /api/v1/system/restart/
|
||||||
|
- [ ] /api/v1/system/name
|
||||||
|
- [ ] /api/v1/system/info
|
||||||
|
- [X] /api/v1/system/wifi/change
|
||||||
|
- [X] /api/v1/system/wifi/get_ssid
|
||||||
|
- [ ] /api/v1/files/get
|
||||||
|
- [ ] /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 configuration server running on port 8080
|
||||||
- [ ] Edit wifi connection
|
- [ ] Edit wifi connection
|
||||||
- [ ] Edit friendly name of the NetSpeaker
|
- [ ] Edit friendly name of the NetSpeaker
|
||||||
|
- [X] Improve the volume endpoint handler (currently pretty undynamic - not anymore :)
|
||||||
|
- [ ] Add better encoding as umlauts are not shown **sometimes**
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
## Credits & Acknowledgements
|
||||||
|
|
||||||
Thanks to...
|
Thanks to...
|
||||||
|
|
||||||
@@ -48,6 +77,11 @@ Thanks to...
|
|||||||
- 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
|
||||||
|
|
||||||
|
External librarys used:
|
||||||
|
|
||||||
|
- ESP32-audioI2S library (https://github.com/schreibfaul1/ESP32-audioI2S as of 2023/12)
|
||||||
|
- Arduino ESP32-specific librarys (https://github.com/espressif/arduino-esp32/ as of 2023/12)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -55,11 +89,10 @@ Pull requests are welcome. For major changes, please open an issue first
|
|||||||
to discuss what you would like to change.
|
to discuss what you would like to change.
|
||||||
Please also make sure to test things carefully before contributing them.
|
Please also make sure to test things carefully before contributing them.
|
||||||
|
|
||||||
|
## Mirrors
|
||||||
|
|
||||||
## External librarys used:
|
- [Privacynerd's Gitea (main)](https://git.privacynerd.de/NetSpeaker/NetSpeaker/)
|
||||||
|
- [Codeberg](https://codeberg.org/NetSpeaker/NetSpeaker)
|
||||||
- ESP32-audioI2S library (https://github.com/schreibfaul1/ESP32-audioI2S as of 2023/12)
|
|
||||||
- Arduino ESP32-specific librarys (https://github.com/espressif/arduino-esp32/ as of 2023/12)
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
BIN
examples-diy/standalone/NetSpeaker-Case_Back.jpg
Normal file
BIN
examples-diy/standalone/NetSpeaker-Case_Back.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 MiB |
BIN
examples-diy/standalone/NetSpeaker-Case_Front.jpg
Normal file
BIN
examples-diy/standalone/NetSpeaker-Case_Front.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
BIN
examples-diy/standalone/NetSpeaker-Case_Part1.png
Normal file
BIN
examples-diy/standalone/NetSpeaker-Case_Part1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
examples-diy/standalone/NetSpeaker-Case_Part2.png
Normal file
BIN
examples-diy/standalone/NetSpeaker-Case_Part2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@@ -21,7 +21,7 @@ For more information, please refer to <http://unlicense.org/>
|
|||||||
|
|
||||||
|
|
||||||
// define all constants
|
// define all constants
|
||||||
const String version = "NetSpeaker v0.2.0-dev"; // version string used e.g. for access point ssid when wifi couldn't connect
|
const String version = "NetSpeaker v0.2.5-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
|
||||||
@@ -48,9 +48,15 @@ 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
|
const String apPSK = "aA16161Aa"; // pre-shared-key of access point opened when not able to connect to wifi
|
||||||
|
|
||||||
// create all needed variables
|
// create all needed variables
|
||||||
int currentVolume = 20; // variable where current volume (0...20) is stored
|
int currentVolume = 50; // variable where current volume (0...20) is stored
|
||||||
|
int maxVolume = 100; // defines the volume steps (max. 255)
|
||||||
|
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)
|
||||||
String currentSongPath; // path to currently playing song
|
String currentSongPath; // path to currently playing song
|
||||||
bool audioPlaying = true; // play song or not?
|
bool audioPlaying = false; // play song or not?
|
||||||
Audio audio; // Audio object (for playing audio, decoding mp3, ...)
|
Audio audio; // Audio object (for playing audio, decoding mp3, ...)
|
||||||
int currentPlaylistPosition = -1; // the current position in the current playlist
|
int currentPlaylistPosition = -1; // the current position in the current playlist
|
||||||
String currentPlaylist = "/audio/" + directoryPlaylistName + playlistExtension; // path to current playlist
|
String currentPlaylist = "/audio/" + directoryPlaylistName + playlistExtension; // path to current playlist
|
||||||
@@ -65,12 +71,12 @@ struct playbackInfo {
|
|||||||
String resourcePath; // e.g. /audio/XXX.mp3
|
String resourcePath; // e.g. /audio/XXX.mp3
|
||||||
String artist; // e.g. Blender Foundation
|
String artist; // e.g. Blender Foundation
|
||||||
String track; // e.g. 01/02 or 01
|
String track; // e.g. 01/02 or 01
|
||||||
String album;
|
String album; // album
|
||||||
String year; // id3 tag year
|
String year; // id3 tag year
|
||||||
String genre; // id3 tag content type
|
String genre; // id3 tag content type
|
||||||
String copyright; // id3 tag (special)
|
String copyright; // id3 tag (special)
|
||||||
};
|
};
|
||||||
struct playbackInfo pbInfo = { "", "", "", "", "", "" };
|
struct playbackInfo pbInfo = { "", "", "", "", "", "", "", "", "" };
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200); // setup Serial console (over USB) with baud 115200
|
Serial.begin(115200); // setup Serial console (over USB) with baud 115200
|
||||||
|
@@ -13,24 +13,33 @@ For more information, please refer to <http://unlicense.org/>
|
|||||||
|
|
||||||
void setupAudio() {
|
void setupAudio() {
|
||||||
audio.setPinout(I2S_BLCK, I2S_LRC, I2S_DOUT); // tell the audio library what output pins to use
|
audio.setPinout(I2S_BLCK, I2S_LRC, I2S_DOUT); // tell the audio library what output pins to use
|
||||||
audio.setVolumeSteps(20);
|
audio.setVolumeSteps(maxVolume);
|
||||||
Serial.printf("[SETUP] Set up audio card successfully (Pins: BLCK %d | LRC %d | DOUT %d)\n", I2S_BLCK, I2S_LRC, I2S_DOUT);
|
Serial.printf("[SETUP] Set up audio card successfully (Pins: BLCK %d | LRC %d | DOUT %d)\n", I2S_BLCK, I2S_LRC, I2S_DOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
String nextAudio() {
|
String nextAudio() {
|
||||||
String songPath;
|
String url;
|
||||||
|
|
||||||
currentPlaylistPosition++; // increment once
|
currentPlaylistPosition++; // increment once
|
||||||
songPath = getSongFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
url = getURLFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
||||||
if (songPath == "") { // if the end of the playlist is reached go to start
|
|
||||||
|
if (url == "") { // if the end of the playlist is reached go to start
|
||||||
currentPlaylistPosition = -1; // the next time "nextAudio()" is called it will increment to 0
|
currentPlaylistPosition = -1; // the next time "nextAudio()" is called it will increment to 0
|
||||||
nextAudio(); // recursive call
|
nextAudio(); // recursive call
|
||||||
return ""; // exit
|
return ""; // exit
|
||||||
}
|
}
|
||||||
Serial.printf("[INFO] Starting next audio from playlist (ID: %d, Path: %s)\n", currentPlaylistPosition, songPath.c_str());
|
|
||||||
audio.connecttoFS(SD, songPath.c_str()); // play the next song
|
|
||||||
|
|
||||||
pbInfo.resourcePath = songPath;
|
if (url.startsWith("http")) {
|
||||||
|
Serial.printf("[INFO] Starting stream from playlist (ID: %d, URL: %s)\n", currentPlaylistPosition, url.c_str());
|
||||||
|
audio.connecttohost(url.c_str());
|
||||||
|
} else if (url.startsWith("/")) {
|
||||||
|
Serial.printf("[INFO] Starting audio from playlist (ID: %d, Path: %s)\n", currentPlaylistPosition, url.c_str());
|
||||||
|
audio.connecttoFS(SD, url.c_str()); // play the next song
|
||||||
|
} else if (url.startsWith("speech://")) { // format: speech://CC@TEXT where CC is the country code, e.g. en or de
|
||||||
|
Serial.printf("[INFO] Starting speech from playlist (ID: %d, Language: %s, Text: %s)\n", currentPlaylistPosition, url.substring(9, 11).c_str(), url.substring(12).c_str());
|
||||||
|
audio.connecttospeech(url.substring(12).c_str(), url.substring(9, 11).c_str());
|
||||||
|
}
|
||||||
|
pbInfo.resourcePath = url;
|
||||||
pbInfo.type = "local";
|
pbInfo.type = "local";
|
||||||
|
|
||||||
// clear everything (if a file hadn't got id3 tags so that there won't keep the old names)
|
// clear everything (if a file hadn't got id3 tags so that there won't keep the old names)
|
||||||
@@ -43,20 +52,28 @@ String nextAudio() {
|
|||||||
pbInfo.genre = "";
|
pbInfo.genre = "";
|
||||||
pbInfo.copyright = "";
|
pbInfo.copyright = "";
|
||||||
|
|
||||||
return songPath;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
String previousAudio() {
|
String previousAudio() {
|
||||||
String songPath;
|
String url;
|
||||||
|
|
||||||
currentPlaylistPosition--;
|
currentPlaylistPosition--;
|
||||||
if (currentPlaylistPosition < 0) currentPlaylistPosition = 0;
|
if (currentPlaylistPosition < 0) currentPlaylistPosition = 0;
|
||||||
|
|
||||||
songPath = getSongFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
url = getURLFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
||||||
Serial.printf("[INFO] Starting previous audio from playlist (ID: %d, Path: %s)\n", currentPlaylistPosition, songPath.c_str());
|
if (url.startsWith("http")) {
|
||||||
audio.connecttoFS(SD, songPath.c_str()); // play the previous song
|
Serial.printf("[INFO] Starting stream from playlist (ID: %d, URL: %s)\n", currentPlaylistPosition, url.c_str());
|
||||||
|
audio.connecttohost(url.c_str());
|
||||||
|
} else if (url.startsWith("/")) {
|
||||||
|
Serial.printf("[INFO] Starting audio from playlist (ID: %d, Path: %s)\n", currentPlaylistPosition, url.c_str());
|
||||||
|
audio.connecttoFS(SD, url.c_str()); // play the next song
|
||||||
|
} else if (url.startsWith("speech://")) { // format: speech://CC@TEXT where CC is the country code, e.g. en or de
|
||||||
|
Serial.printf("[INFO] Starting speech from playlist (ID: %d, Language: %s, Text: %s)\n", currentPlaylistPosition, url.substring(9, 11).c_str(), url.substring(12).c_str());
|
||||||
|
audio.connecttospeech(url.substring(12).c_str(), url.substring(9, 11).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
pbInfo.resourcePath = songPath;
|
pbInfo.resourcePath = url;
|
||||||
pbInfo.type = "local";
|
pbInfo.type = "local";
|
||||||
|
|
||||||
// clear everything (if a file hadn't got id3 tags so that there won't keep the old names)
|
// clear everything (if a file hadn't got id3 tags so that there won't keep the old names)
|
||||||
@@ -69,7 +86,7 @@ String previousAudio() {
|
|||||||
pbInfo.genre = "";
|
pbInfo.genre = "";
|
||||||
pbInfo.copyright = "";
|
pbInfo.copyright = "";
|
||||||
|
|
||||||
return songPath;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchPlaybackIfButtonClicked() {
|
void switchPlaybackIfButtonClicked() {
|
||||||
@@ -98,11 +115,11 @@ void backwardButtonHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setAudioVolume() {
|
void setAudioVolume() {
|
||||||
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))
|
int newCurrentVolume = analogRead(audioVolumePin) / (4095/maxVolume); // read voltage from audioVolumePin and divide by the calculated steps (4095 is the maximum input)
|
||||||
if (currentVolume != newCurrentVolume) { // just do it if the volume changed
|
if (currentVolume != newCurrentVolume) { // just do it if the volume changed
|
||||||
currentVolume = newCurrentVolume;
|
currentVolume = newCurrentVolume;
|
||||||
audio.setVolume(currentVolume); // set volume
|
audio.setVolume(currentVolume); // set volume
|
||||||
Serial.printf("[INFO] Set volume to %d/20!\n", currentVolume);
|
Serial.printf("[INFO] Set volume to %d/%d!\n", currentVolume, maxVolume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +138,14 @@ void audio_eof_mp3(const char *info) { // MUST HAVE THIS NAME (audio_eof_mp3) b
|
|||||||
}
|
}
|
||||||
void audio_eof_speech(const char *info) {
|
void audio_eof_speech(const char *info) {
|
||||||
Serial.printf("[Audio.h] EOF_Speech %s\n", info);
|
Serial.printf("[Audio.h] EOF_Speech %s\n", info);
|
||||||
|
nextAudio();
|
||||||
}
|
}
|
||||||
void audio_info(const char *info) {
|
void audio_info(const char *info) {
|
||||||
Serial.printf("[Audio.h] Info %s\n", info);
|
Serial.printf("[Audio.h] Info %s\n", info);
|
||||||
|
|
||||||
|
if(String(info).startsWith("End of webstream")) {
|
||||||
|
nextAudio();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void audio_id3data(const char *info) { // id3 metadata
|
void audio_id3data(const char *info) { // id3 metadata
|
||||||
Serial.printf("[Audio.h] ID3Data %s\n", info);
|
Serial.printf("[Audio.h] ID3Data %s\n", info);
|
||||||
@@ -163,4 +185,47 @@ void audio_icyurl(const char *info) { // Name of radio station
|
|||||||
}
|
}
|
||||||
void audio_lasthost(const char *info) { // stream URL played
|
void audio_lasthost(const char *info) { // stream URL played
|
||||||
Serial.printf("[Audio.h] Lasthost %s\n", info);
|
Serial.printf("[Audio.h] Lasthost %s\n", info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *********************************************************************************** //
|
||||||
|
// ******** Following function heavily inspired by schreibfaul1's wiki entry ******** //
|
||||||
|
// * https://github.com/schreibfaul1/ESP32-audioI2S/wiki#what-audio-events-are-there * //
|
||||||
|
// *********************************************************************************** //
|
||||||
|
void audio_id3image(File &file, const size_t pos, const size_t size) { // cover image
|
||||||
|
Serial.printf("[Audio.h] ID3Image Found at position: %u | Length: %u\n", pos, size);
|
||||||
|
uint8_t buf[1024];
|
||||||
|
file.seek(pos + 1); // skip 1 byte encoding
|
||||||
|
char mimeType[255]; // mime-type (null terminated)
|
||||||
|
for (uint8_t i = 0u; i < 255; i++) {
|
||||||
|
mimeType[i] = file.read();
|
||||||
|
if (uint8_t(mimeType[i]) == 0) break;
|
||||||
|
}
|
||||||
|
Serial.printf("[Audio.h] ID3Image MimeType: %s\n", mimeType);
|
||||||
|
uint8_t imageType = file.read(); // image type (1 Byte)
|
||||||
|
Serial.printf("[Audio.h] ID3Image ImageType: %d\n", imageType);
|
||||||
|
for (uint8_t i = 0u; i < 255; i++) { // description (null terminated)
|
||||||
|
buf[i] = file.read();
|
||||||
|
if (uint8_t(buf[i]) == 0) break;
|
||||||
|
}
|
||||||
|
// raw image data
|
||||||
|
String imgPath = "/.current";
|
||||||
|
if (String(mimeType) == "image/jpeg") imgPath += ".jpg";
|
||||||
|
else if (String(mimeType) == "image/png") imgPath += ".png";
|
||||||
|
else if (String(mimeType) == "image/svg") imgPath += ".svg";
|
||||||
|
else imgPath += ".img";
|
||||||
|
File coverFile = SD.open(imgPath, FILE_WRITE);
|
||||||
|
size_t len = size;
|
||||||
|
while (len) {
|
||||||
|
uint16_t bytesRead = file.read(buf, sizeof(buf));
|
||||||
|
if (len >= bytesRead) len -= bytesRead;
|
||||||
|
else {
|
||||||
|
bytesRead = len;
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
coverFile.write(buf, bytesRead);
|
||||||
|
}
|
||||||
|
// write changes to SD card
|
||||||
|
coverFile.flush();
|
||||||
|
coverFile.close();
|
||||||
|
Serial.printf("[Audio.h] ID3Image Cover file written to '%s'\n", imgPath.c_str());
|
||||||
|
}
|
||||||
|
@@ -20,6 +20,21 @@ int setupSD(int SD_CS, int SPI_MISO, int SPI_MOSI, int SPI_SCK) {
|
|||||||
return ret; // returns 1 if everything is OK, 0 if an error occured (eg. wiring not correct)
|
return ret; // returns 1 if everything is OK, 0 if an error occured (eg. wiring not correct)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool writeWiFiSecrets(String confPath, String ssid, String psk) {
|
||||||
|
File wifiSecrets = SD.open(confPath, FILE_WRITE);
|
||||||
|
|
||||||
|
wifiSecrets.printf("%s\n", ssid.c_str());
|
||||||
|
wifiSecrets.printf("%s", psk.c_str());
|
||||||
|
|
||||||
|
wifiSecrets.flush();
|
||||||
|
wifiSecrets.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool writeWiFiSecrets(String ssid, String psk) {
|
||||||
|
return writeWiFiSecrets(wifiConfigPath, ssid, psk);
|
||||||
|
}
|
||||||
|
|
||||||
String getWiFiSSID(String confPath) {
|
String getWiFiSSID(String confPath) {
|
||||||
String ssid;
|
String ssid;
|
||||||
char new_char;
|
char new_char;
|
||||||
@@ -28,7 +43,7 @@ String getWiFiSSID(String confPath) {
|
|||||||
File confFile = SD.open(confPath);
|
File confFile = SD.open(confPath);
|
||||||
|
|
||||||
ssid = (char)confFile.read();
|
ssid = (char)confFile.read();
|
||||||
while (ssid[ssid.length() - 1] != '\n' && confFile.available()) {
|
while (ssid[ssid.length() - 1] != '\n' || ssid[ssid.length() - 1] != '\r' && confFile.available()) {
|
||||||
new_char = (char)confFile.read();
|
new_char = (char)confFile.read();
|
||||||
if (new_char == '\n') break;
|
if (new_char == '\n') break;
|
||||||
ssid += new_char;
|
ssid += new_char;
|
||||||
@@ -55,7 +70,7 @@ String getWiFiPSK(String confPath) {
|
|||||||
psk = (char)confFile.read();
|
psk = (char)confFile.read();
|
||||||
while (confFile.available()) {
|
while (confFile.available()) {
|
||||||
new_char = (char)confFile.read();
|
new_char = (char)confFile.read();
|
||||||
if (new_char == '\n') break;
|
if (new_char == '\n' || new_char == '\r') break;
|
||||||
psk += new_char;
|
psk += new_char;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +110,7 @@ bool createPlaylistFromDirectory(String folderpath) { // create a .m3u playlist
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getSongFromPlaylist(String path, int position) {
|
String getURLFromPlaylist(String path, int position) {
|
||||||
String song;
|
String song;
|
||||||
File playlist;
|
File playlist;
|
||||||
char currentChar;
|
char currentChar;
|
||||||
|
@@ -32,7 +32,12 @@ String generate_api_json(bool success) { // if you just want the basic json fra
|
|||||||
String generate_api_json(bool success, String content) { // when info is to be embedded
|
String generate_api_json(bool success, String content) { // when info is to be embedded
|
||||||
return generate_api_json(success, content, false);
|
return generate_api_json(success, content, false);
|
||||||
}
|
}
|
||||||
|
bool isStringConvertable2Int(String toIntStr) {
|
||||||
|
for (int i = 0; i < toIntStr.length(); i++) { // check if a non-digit character is in the given string
|
||||||
|
if (!isDigit((char)toIntStr[i])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void configRoot() {
|
void configRoot() {
|
||||||
Serial.println("[HTTP] [Config] 200 - GET '/'");
|
Serial.println("[HTTP] [Config] 200 - GET '/'");
|
||||||
@@ -84,29 +89,51 @@ void api_v1_playback_pause() {
|
|||||||
void api_v1_playback_next() {
|
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 songPath = nextAudio();
|
nextAudio();
|
||||||
String content = "\"resource_path\": \"";
|
String content = "\"resource_playlist_index\": ";
|
||||||
content += songPath;
|
|
||||||
content += "\", \"resource_playlist_index\": ";
|
|
||||||
content += String(currentPlaylistPosition);
|
content += String(currentPlaylistPosition);
|
||||||
|
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true, content));
|
api_server.send(200, "application/json", generate_api_json(true, content));
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playback_previous() {
|
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 songPath = previousAudio();
|
previousAudio();
|
||||||
String content = "\"resource_path\": \"";
|
String content = "\"resource_playlist_index\": ";
|
||||||
content += songPath;
|
|
||||||
content += "\", \"resource_playlist_index\": ";
|
|
||||||
content += String(currentPlaylistPosition);
|
content += String(currentPlaylistPosition);
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true, content));
|
api_server.send(200, "application/json", generate_api_json(true, content));
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playback_volume() {
|
void api_v1_playback_byindex() {
|
||||||
|
String option = api_server.pathArg(0);
|
||||||
|
String toIntStr;
|
||||||
|
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/playback/%s'\n", option);
|
||||||
|
|
||||||
|
// convert string to int
|
||||||
|
for (int i = 0; i < option.length(); i++) {
|
||||||
|
if (isDigit((char)option[i])) toIntStr += option[i];
|
||||||
|
else {
|
||||||
|
api_server.send(200, "application/json", generate_api_json(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int index = toIntStr.toInt();
|
||||||
|
|
||||||
|
if (getURLFromPlaylist(currentPlaylist, index) != "") { // if this index exists
|
||||||
|
currentPlaylistPosition = index - 1;
|
||||||
|
nextAudio(); // the clean way of playing the next resource
|
||||||
|
} else {
|
||||||
|
api_server.send(200, "application/json", generate_api_json(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = "\"resource_playlist_index\": ";
|
||||||
|
content += String(currentPlaylistPosition);
|
||||||
|
api_server.send(200, "application/json", generate_api_json(true, content));
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_volume() {
|
||||||
String option = api_server.pathArg(0);
|
String option = api_server.pathArg(0);
|
||||||
bool success = true;
|
bool success = true;
|
||||||
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/volume/%s'\n", option);
|
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/volume/%s'\n", option);
|
||||||
@@ -118,58 +145,170 @@ void api_v1_playback_volume() {
|
|||||||
currentVolume--;
|
currentVolume--;
|
||||||
if (currentVolume < 0) currentVolume = 0;
|
if (currentVolume < 0) currentVolume = 0;
|
||||||
} else if (option == "mute") {
|
} else if (option == "mute") {
|
||||||
currentVolume = 0; // TODO: remember the previous volume and just mute it; unmute has to be implemented then
|
muted = true;
|
||||||
|
} else if (option == "unmute") {
|
||||||
|
muted = false;
|
||||||
} else if (option == "get") {
|
} else if (option == "get") {
|
||||||
// just here that no 'success: false' is sent
|
// 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 {
|
} else {
|
||||||
success = false;
|
String toIntStr;
|
||||||
|
int volumeLevel;
|
||||||
|
// convert string to int
|
||||||
|
for (int i = 0; i < option.length(); i++) {
|
||||||
|
if (isDigit((char)option[i])) toIntStr += option[i];
|
||||||
|
else success = false;
|
||||||
|
}
|
||||||
|
if (success && toIntStr.toInt() <= maxVolume) currentVolume = toIntStr.toInt();
|
||||||
|
else success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
audio.setVolume(currentVolume); // set volume
|
if (!muted) audio.setVolume(currentVolume); // set volume if not muted and in range
|
||||||
|
else audio.setVolume(0); // if muted, set volume to 0
|
||||||
|
|
||||||
String content = "\"volume\": "; // prepare the http response
|
String content = "\"volume\": "; // prepare the http response
|
||||||
content += String(currentVolume);
|
content += String(currentVolume);
|
||||||
|
content += ", \"muted\": ";
|
||||||
|
content += muted ? "true" : "false";
|
||||||
|
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_balance() {
|
||||||
|
String option = api_server.pathArg(0);
|
||||||
|
bool success = true;
|
||||||
|
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/balance/%s'\n", option);
|
||||||
|
|
||||||
|
if (option == "right") {
|
||||||
|
balanceLevel--;
|
||||||
|
if (balanceLevel < -16) balanceLevel = -16;
|
||||||
|
} else if (option == "left") {
|
||||||
|
balanceLevel++;
|
||||||
|
if (balanceLevel > 16) balanceLevel = 16;
|
||||||
|
} else if (option == "get") {
|
||||||
|
// just here that no 'success: false' is sent
|
||||||
|
} else {
|
||||||
|
String toIntStr; // between 0 and 32
|
||||||
|
// convert string to int
|
||||||
|
for (int i = 0; i < option.length(); i++) {
|
||||||
|
if (isDigit((char)option[i])) toIntStr += option[i];
|
||||||
|
else success = false;
|
||||||
|
}
|
||||||
|
if (success && toIntStr.toInt() <= 32) balanceLevel = toIntStr.toInt() - 16; // as the input string is between 0 and 32
|
||||||
|
else success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio.setBalance(balanceLevel); // set volume if not muted and in range
|
||||||
|
|
||||||
|
String content = "\"balance\": "; // prepare the http response
|
||||||
|
content += String(balanceLevel);
|
||||||
|
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_eq_get() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - GET '/api/v1/eq/get");
|
||||||
|
|
||||||
|
String content = "\"equalizer_low\": "; // prepare the http response
|
||||||
|
content += String(eqLow);
|
||||||
|
content += ", \"equalizer_mid\": "; // prepare the http response
|
||||||
|
content += String(eqMid);
|
||||||
|
content += ", \"equalizer_high\": "; // prepare the http response
|
||||||
|
content += String(eqHigh);
|
||||||
|
|
||||||
|
api_server.send(200, "application/json", generate_api_json(true, content)); // generate json and send it
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_eq_reset() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - GET '/api/v1/eq/reset");
|
||||||
|
|
||||||
|
eqLow = 0;
|
||||||
|
eqMid = 0;
|
||||||
|
eqHigh = 0;
|
||||||
|
audio.setTone(0, 0, 0);
|
||||||
|
|
||||||
|
Serial.printf("[INFO] Set balance to %ddB | %ddB | %ddB!\n", eqLow, eqMid, eqHigh);
|
||||||
|
|
||||||
|
String content = "\"equalizer_low\": "; // prepare the http response
|
||||||
|
content += String(eqLow);
|
||||||
|
content += ", \"equalizer_mid\": "; // prepare the http response
|
||||||
|
content += String(eqMid);
|
||||||
|
content += ", \"equalizer_high\": "; // prepare the http response
|
||||||
|
content += String(eqHigh);
|
||||||
|
|
||||||
|
api_server.send(200, "application/json", generate_api_json(true, content)); // generate json and send it
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_eq_low() {
|
||||||
|
String option = api_server.pathArg(0);
|
||||||
|
bool success = true;
|
||||||
|
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/eq/low/%s'\n", option);
|
||||||
|
|
||||||
|
if (option == "get") {
|
||||||
|
// just here to make /get available
|
||||||
|
} else if (option == "reset") {
|
||||||
|
eqLow = 0;
|
||||||
|
} else if (isStringConvertable2Int(option)) {
|
||||||
|
int res = option.toInt();
|
||||||
|
if (res > 46) { // the maximum is 6dB and there will be 40 subtracted one line beneath
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
eqLow = res - 40;
|
||||||
|
Serial.printf("[INFO] Set balance to %ddB | %ddB | %ddB!\n", eqLow, eqMid, eqHigh);
|
||||||
|
}
|
||||||
|
} else success = false;
|
||||||
|
|
||||||
|
audio.setTone(eqLow, eqMid, eqHigh); // set volume if not muted and in range
|
||||||
|
|
||||||
|
String content = "\"equalizer_low\": "; // prepare the http response
|
||||||
|
content += String(eqLow);
|
||||||
|
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
|
||||||
|
}
|
||||||
|
void api_v1_eq_mid() {
|
||||||
|
String option = api_server.pathArg(0);
|
||||||
|
bool success = true;
|
||||||
|
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/eq/mid/%s'\n", option);
|
||||||
|
|
||||||
|
if (option == "get") {
|
||||||
|
// just here to make /get available
|
||||||
|
} else if (option == "reset") {
|
||||||
|
eqMid = 0;
|
||||||
|
} else if (isStringConvertable2Int(option)) {
|
||||||
|
int res = option.toInt();
|
||||||
|
if (res > 46) { // the maximum is 6dB and there will be 40 subtracted one line beneath
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
eqMid = res - 40;
|
||||||
|
Serial.printf("[INFO] Set balance to %ddB | %ddB | %ddB!\n", eqLow, eqMid, eqHigh);
|
||||||
|
}
|
||||||
|
} else success = false;
|
||||||
|
|
||||||
|
audio.setTone(eqLow, eqMid, eqHigh); // set volume if not muted and in range
|
||||||
|
|
||||||
|
String content = "\"equalizer_mid\": "; // prepare the http response
|
||||||
|
content += String(eqMid);
|
||||||
|
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
|
||||||
|
}
|
||||||
|
void api_v1_eq_high() {
|
||||||
|
String option = api_server.pathArg(0);
|
||||||
|
bool success = true;
|
||||||
|
Serial.printf("[HTTP] [API] 200 - GET '/api/v1/eq/high/%s'\n", option);
|
||||||
|
|
||||||
|
if (option == "get") {
|
||||||
|
// just here to make /get available
|
||||||
|
} else if (option == "reset") {
|
||||||
|
eqHigh = 0;
|
||||||
|
} else if (isStringConvertable2Int(option)) {
|
||||||
|
int res = option.toInt();
|
||||||
|
if (res > 46) { // the maximum is 6dB and there will be 40 subtracted one line beneath
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
eqHigh = res - 40;
|
||||||
|
Serial.printf("[INFO] Set balance to %dB | %ddB | %ddB!\n", eqLow, eqMid, eqHigh);
|
||||||
|
}
|
||||||
|
} else success = false;
|
||||||
|
|
||||||
|
audio.setTone(eqLow, eqMid, eqHigh); // set volume if not muted and in range
|
||||||
|
|
||||||
|
String content = "\"equalizer_high\": "; // prepare the http response
|
||||||
|
content += String(eqHigh);
|
||||||
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
|
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +338,7 @@ void api_v1_playlist_get() {
|
|||||||
bool firstRun = true;
|
bool firstRun = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
currentSong = getSongFromPlaylist(currentPlaylist, index);
|
currentSong = getURLFromPlaylist(currentPlaylist, index);
|
||||||
if (currentSong == "") {
|
if (currentSong == "") {
|
||||||
content += "\"";
|
content += "\"";
|
||||||
break;
|
break;
|
||||||
@@ -218,15 +357,102 @@ void api_v1_playlist_get() {
|
|||||||
api_server.send(200, "application/json", generate_api_json(true, content));
|
api_server.send(200, "application/json", generate_api_json(true, content));
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_settings_restart() {
|
void api_v1_playlist_create() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - POST '/api/v1/playlist/create'");
|
||||||
|
|
||||||
|
// get the right POST param
|
||||||
|
String folderPath;
|
||||||
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
|
if (api_server.argName(i) == "folderPath") {
|
||||||
|
folderPath = api_server.arg(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool code = createPlaylistFromDirectory(folderPath);
|
||||||
|
if (code) { // if creating the playlist was successful
|
||||||
|
String playlistPath = folderPath + "/" + directoryPlaylistName + playlistExtension;
|
||||||
|
String content = "\"playlist_path\": \"" + playlistPath + "\"";
|
||||||
|
api_server.send(200, "application/json", generate_api_json(true, content));
|
||||||
|
} else {
|
||||||
|
api_server.send(200, "application/json", generate_api_json(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_playlist_play() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - POST '/api/v1/playlist/play'");
|
||||||
|
|
||||||
|
// get the right POST param
|
||||||
|
String playlistPath;
|
||||||
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
|
if (api_server.argName(i) == "playlistPath") {
|
||||||
|
playlistPath = api_server.arg(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getURLFromPlaylist(playlistPath, 0) == "") { // if playlist is empty or nonexistent
|
||||||
|
api_server.send(200, "application/json", generate_api_json(false)); // send no success info
|
||||||
|
} else {
|
||||||
|
currentPlaylist = playlistPath;
|
||||||
|
currentPlaylistPosition = -1;
|
||||||
|
nextAudio();
|
||||||
|
api_server.send(200, "application/json", generate_api_json(true)); // just return the success info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_restart() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - GET '/api/v1/system/restart'");
|
||||||
|
|
||||||
SD.end(); // delete SD object and sync its cache to flash
|
SD.end(); // delete SD object and sync its cache to flash
|
||||||
WiFi.disconnect(); // disconnect wifi
|
WiFi.disconnect(); // disconnect wifi
|
||||||
Serial.printf("[INFO] Restarting after 5000ms.\n", waitOnSDCardEject, retrySDMountTreshold);
|
Serial.printf("[INFO] Restarting after 5000ms.\n", waitOnSDCardEject, retrySDMountTreshold);
|
||||||
delay(5000);
|
|
||||||
api_server.send(200, "application/json", "{\"restart\": true, \"wait_time\": 5000}");
|
api_server.send(200, "application/json", "{\"restart\": true, \"wait_time\": 5000}");
|
||||||
|
delay(5000);
|
||||||
ESP.restart(); // reset everything and restart the program
|
ESP.restart(); // reset everything and restart the program
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void api_v1_system_wifi_getssid() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - GET '/api/v1/system/wifi/get_ssid'");
|
||||||
|
|
||||||
|
api_server.send(200, "application/json", generate_api_json(true, "\"wifi_ssid\": \"" + getWiFiSSID() + "\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_wifi_change() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - POST '/api/v1/system/wifi/change'");
|
||||||
|
|
||||||
|
// get the right POST params
|
||||||
|
String ssid = "";
|
||||||
|
String psk = "";
|
||||||
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
|
if (api_server.argName(i) == "ssid") {
|
||||||
|
ssid = api_server.arg(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
|
if (api_server.argName(i) == "psk") {
|
||||||
|
psk = api_server.arg(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssid != "" && psk != "") { // both ssid and psk are given
|
||||||
|
writeWiFiSecrets(ssid, psk);
|
||||||
|
} else if (ssid != "" && psk == "") { // just the ssid is given
|
||||||
|
writeWiFiSecrets(ssid, getWiFiPSK());
|
||||||
|
} else if (ssid == "" && psk != "") { // just the psk is given
|
||||||
|
writeWiFiSecrets(getWiFiSSID(), psk);
|
||||||
|
} else { // none of ssid or psk is given
|
||||||
|
api_server.send(200, "application/json", generate_api_json(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[INFO] Changed wifi credentials (SSID: '%s' | PSK: '%s')\n", ssid.c_str(), psk.c_str());
|
||||||
|
api_server.send(200, "application/json", generate_api_json(true, "\"ssid\": \"" + ssid + "\", \"psk\": \"" + psk + "\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void setupConfigWeb() {
|
void setupConfigWeb() {
|
||||||
if (runConfServer) {
|
if (runConfServer) {
|
||||||
conf_server.onNotFound([]() {
|
conf_server.onNotFound([]() {
|
||||||
@@ -247,15 +473,26 @@ void setupApiWeb() {
|
|||||||
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("/", 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("/api/v1/playlist/get", api_v1_playlist_get);
|
api_server.on(UriBraces("/api/v1/playback/{}"), HTTP_GET, api_v1_playback_byindex);
|
||||||
api_server.on(UriBraces("/api/v1/volume/{}"), api_v1_playback_volume);
|
api_server.on("/api/v1/playlist/get", HTTP_GET, api_v1_playlist_get);
|
||||||
api_server.on("/api/v1/settings/restart", api_v1_settings_restart);
|
api_server.on("/api/v1/playlist/create", HTTP_POST, api_v1_playlist_create);
|
||||||
|
api_server.on("/api/v1/playlist/play", HTTP_POST, api_v1_playlist_play);
|
||||||
|
api_server.on(UriBraces("/api/v1/volume/{}"), HTTP_GET, api_v1_volume);
|
||||||
|
api_server.on(UriBraces("/api/v1/balance/{}"), HTTP_GET, api_v1_balance);
|
||||||
|
api_server.on("/api/v1/eq/get", HTTP_GET, api_v1_eq_get);
|
||||||
|
api_server.on(UriBraces("/api/v1/eq/reset"), HTTP_GET, api_v1_eq_reset);
|
||||||
|
api_server.on(UriBraces("/api/v1/eq/low/{}"), HTTP_GET, api_v1_eq_low);
|
||||||
|
api_server.on(UriBraces("/api/v1/eq/mid/{}"), HTTP_GET, api_v1_eq_mid);
|
||||||
|
api_server.on(UriBraces("/api/v1/eq/high/{}"), HTTP_GET, api_v1_eq_high);
|
||||||
|
api_server.on("/api/v1/system/restart", HTTP_GET, api_v1_system_restart);
|
||||||
|
api_server.on("/api/v1/system/wifi/change", HTTP_POST, api_v1_system_wifi_change);
|
||||||
|
api_server.on("/api/v1/system/wifi/get_ssid", HTTP_GET, 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();
|
||||||
|
@@ -32,11 +32,13 @@ void setupWiFi() {
|
|||||||
for(int i = 0; i<33; i++) { if(WiFiSSIDstr.length() >= i) {WiFiSSID[i] = WiFiSSIDstr[i];} else break; }
|
for(int i = 0; i<33; i++) { if(WiFiSSIDstr.length() >= i) {WiFiSSID[i] = WiFiSSIDstr[i];} else break; }
|
||||||
for(int i = 0; i<64; i++) { if(WiFiPSKstr.length() >= i) {WiFiPSK[i] = WiFiPSKstr[i];} else break; }
|
for(int i = 0; i<64; i++) { if(WiFiPSKstr.length() >= i) {WiFiPSK[i] = WiFiPSKstr[i];} else break; }
|
||||||
|
|
||||||
|
//Serial.printf("[WiFi] Credentials: SSID '%s' | PSK '%s'\n", WiFiSSID, WiFiPSK); // kept here fordebugging wifi problems
|
||||||
|
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
WiFi.mode(WIFI_AP_STA);
|
WiFi.mode(WIFI_AP_STA);
|
||||||
WiFi.begin(WiFiSSID, WiFiPSK);
|
WiFi.begin(WiFiSSID, WiFiPSK);
|
||||||
// wait for 10 seconds for wifi to connect
|
// wait for 10 seconds for wifi to connect
|
||||||
int start_timer = millis(); while(WiFi.status() != WL_CONNECTED) { if((millis()-start_timer) > 20000) break; }
|
int start_timer = millis(); while(WiFi.status() != WL_CONNECTED) { if((millis()-start_timer) > 20000) break; delay(10); }
|
||||||
if(WiFi.status() != WL_CONNECTED) {
|
if(WiFi.status() != WL_CONNECTED) {
|
||||||
currentWiFiMode = 1;
|
currentWiFiMode = 1;
|
||||||
Serial.printf("[WiFi] Unable to connect to %s\n", WiFiSSID);
|
Serial.printf("[WiFi] Unable to connect to %s\n", WiFiSSID);
|
||||||
|
Reference in New Issue
Block a user