Compare commits
74 Commits
v0.3.2
...
v0.4.1-dev
Author | SHA1 | Date | |
---|---|---|---|
0804478e2c | |||
4a65ca3612 | |||
d630622863 | |||
e95b8d0051 | |||
5353530f06 | |||
9397abafe3 | |||
d6f64ef107 | |||
2ef668d614 | |||
b672bc4535 | |||
ccd327e87e | |||
6301829e37 | |||
7608af2b06 | |||
4b8e126ad2 | |||
fbcf3fd6b9 | |||
d1eecbe7a0
|
|||
ad29e11a93 | |||
507f400a6c | |||
3c677bf04f | |||
6176b656c7 | |||
bf113272ee | |||
eee4d6c0f1 | |||
8815daab57 | |||
35138308ef | |||
9bdc57633b | |||
444d5a6152 | |||
0c3868d0b8 | |||
bc7efeb509 | |||
c2216f4a67 | |||
7458d093b5 | |||
a3a06a451d | |||
bedbc33f48 | |||
e6e8d781d5 | |||
1b39379a7b | |||
5875075edd
|
|||
2f4129d81f
|
|||
6e67a82722
|
|||
3a5b88a2d1
|
|||
fb31d82fbf
|
|||
6668d56171
|
|||
9009e55d4f
|
|||
59fcb3335e
|
|||
1ab1e2e1d0
|
|||
78b6eec8cb
|
|||
e85a045fe6
|
|||
e744d92b0e
|
|||
ae08930bca
|
|||
77eef80da0
|
|||
ce7db7b0fd
|
|||
8f90d6ff05
|
|||
2cce64b11c
|
|||
0dbcd45a1e
|
|||
e23db963cb
|
|||
e922af6c2b
|
|||
ab05d31006
|
|||
3a26e00319
|
|||
05a9ce083b
|
|||
85b306a6b5
|
|||
0ecb3099d4
|
|||
edb2c80e5e
|
|||
27a472e85c
|
|||
f03cb6fb54 | |||
cee502c3f1
|
|||
7bb65c355d | |||
f9f8f17803 | |||
58ddb4fb74 | |||
c940a2afec | |||
0c42b53902 | |||
566ee35935 | |||
40f4d615d6 | |||
676e5fcadb | |||
97205ac43f | |||
d67dea275e | |||
06da06c519 | |||
763254b603 |
36
API_DOC.txt
36
API_DOC.txt
@@ -1,36 +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/playlist/create POST JSON object Creates a playlist of the given directory
|
|
||||||
/api/v1/playlist/play POST JSON object Plays a playlist from the SD card
|
|
||||||
|
|
||||||
/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 Mute (does not affect volume)
|
|
||||||
/api/v1/volume/unmute GET JSON object Unmute (set volume to current volume)
|
|
||||||
/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)
|
|
||||||
|
|
65
README.md
65
README.md
@@ -3,7 +3,7 @@
|
|||||||
[](https://unlicense.org/)
|
[](https://unlicense.org/)
|
||||||
[](https://opensource.org/)
|
[](https://opensource.org/)
|
||||||
[](https://www.espressif.com/en/products/socs/esp32)
|
[](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.
|
||||||
@@ -16,6 +16,15 @@ via the constant operation_mode. Valid choices are:
|
|||||||
|
|
||||||
Any questions? Hopefully a look into the **Wiki** will help.
|
Any questions? Hopefully a look into the **Wiki** will help.
|
||||||
|
|
||||||
|
## API demo
|
||||||
|
|
||||||
|
One thing I have learnt while developing NetSpeaker is that implementing a web server
|
||||||
|
which servers hundreds of lines of html is... well sort of dumb (there previously was an API demo running
|
||||||
|
directly on the NetSpeaker machine which took loads of resources when requested).
|
||||||
|
You can see all this in [that commit](https://git.privacynerd.de/NetSpeaker/NetSpeaker/commit/35138308ef90ea1fccbf1b292eeb69a7d4e8a084).
|
||||||
|
This local API demo has been replaced by another [repository](https://git.privacynerd.de/NetSpeaker/NetSpeaker-API-Demo) containing
|
||||||
|
the Demo as HTML files (you can just open these in your web browser and type in you NetSpeaker's IP address and start playing).
|
||||||
|
|
||||||
|
|
||||||
## Audio compatibility
|
## Audio compatibility
|
||||||
|
|
||||||
@@ -28,6 +37,7 @@ Features, already implemented, or still in progress for the v1.0.0 release!
|
|||||||
|
|
||||||
- [x] SD card support for playing audio files
|
- [x] SD card support for playing audio files
|
||||||
- [x] Two operating modes, standalone mode fully implemented
|
- [x] Two operating modes, standalone mode fully implemented
|
||||||
|
- [x] two further operation modes to choose whether to run as standalone or interconnected at startup by pressing buttons
|
||||||
- [x] API in mode 0
|
- [x] API in mode 0
|
||||||
- [x] API methods:
|
- [x] API methods:
|
||||||
- [X] /api/v1/playback/toggle
|
- [X] /api/v1/playback/toggle
|
||||||
@@ -35,32 +45,51 @@ 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
|
||||||
- [ ] /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/playback/<index>
|
||||||
- [X] /api/v1/playlist/get
|
- [X] /api/v1/playlist/get
|
||||||
- [ ] /api/v1/playlist/append
|
- [X] /api/v1/playlist/create
|
||||||
- [ ] /api/v1/playlist/remove
|
- [X] /api/v1/playlist/play
|
||||||
- [X] /api/v1/volume/get
|
- [X] /api/v1/volume/get
|
||||||
|
- [X] /api/v1/volume/get_max
|
||||||
- [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/get
|
||||||
- [ ] /api/v1/system/name
|
- [X] /api/v1/balance/<0-32>
|
||||||
- [ ] /api/v1/system/info
|
- [X] /api/v1/eq/get
|
||||||
- [ ] /api/v1/system/name
|
- [X] /api/v1/eq/reset
|
||||||
- [ ] /api/v1/system/wifi/change
|
- [X] /api/v1/eq/low/get
|
||||||
- [ ] /api/v1/system/wifi/get_ssid
|
- [X] /api/v1/eq/low/reset
|
||||||
|
- [X] /api/v1/eq/low/<0-46>
|
||||||
|
- [X] /api/v1/eq/mid/get
|
||||||
|
- [X] /api/v1/eq/mid/reset
|
||||||
|
- [X] /api/v1/eq/mid/<0-46>
|
||||||
|
- [X] /api/v1/eq/high/get
|
||||||
|
- [X] /api/v1/eq/high/reset
|
||||||
|
- [X] /api/v1/eq/high/<0-46>
|
||||||
|
- [X] /api/v1/system/restart/
|
||||||
|
- [X] /api/v1/system/name
|
||||||
|
- [X] /api/v1/system/name/change
|
||||||
|
- [X] /api/v1/system/network_name
|
||||||
|
- [X] /api/v1/system/network_name/change
|
||||||
|
- [X] /api/v1/system/restore_state/{on,off,get}
|
||||||
|
- [X] /api/v1/system/restore_playing/{on,off,get}
|
||||||
|
- [X] /api/v1/system/tell_address/{on,off,get}
|
||||||
|
- [X] /api/v1/system/version
|
||||||
|
- [X] /api/v1/system/wifi/change
|
||||||
|
- [X] /api/v1/system/wifi/get_ssid
|
||||||
|
- [X] /api/v1/system/wifi/get_ap_creds
|
||||||
- [ ] /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
|
|
||||||
- [ ] 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 shown **sometimes**
|
- [ ] Add better encoding as umlauts are not displayed correctly **sometimes**
|
||||||
|
|
||||||
|
Please note that all API endpoints may not end with a `/`.
|
||||||
|
|
||||||
|
|
||||||
## Credits & Acknowledgements
|
## Credits & Acknowledgements
|
||||||
@@ -77,6 +106,13 @@ External librarys used:
|
|||||||
- Arduino ESP32-specific librarys (https://github.com/espressif/arduino-esp32/ as of 2023/12)
|
- Arduino ESP32-specific librarys (https://github.com/espressif/arduino-esp32/ as of 2023/12)
|
||||||
|
|
||||||
|
|
||||||
|
## Helpful links
|
||||||
|
|
||||||
|
- [Seperate audio task example which may solve the issue that when running the API server no IO is accesible (no buttons, status led, ...)](https://github.com/schreibfaul1/ESP32-audioI2S/tree/master/examples/separate_audiotask)
|
||||||
|
- [Wiki of the projects Audio library](https://github.com/schreibfaul1/ESP32-audioI2S/wiki)
|
||||||
|
- [API demo showing how to use the NetSpeaker's API](https://git.privacynerd.de/NetSpeaker/NetSpeaker-API-Demo)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Pull requests are welcome. For major changes, please open an issue first
|
Pull requests are welcome. For major changes, please open an issue first
|
||||||
@@ -101,4 +137,3 @@ successors. We intend this dedication to be an overt act of relinquishment in pe
|
|||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org/>
|
For more information, please refer to <http://unlicense.org/>
|
||||||
|
|
||||||
|
18
initial_config.ino
Normal file
18
initial_config.ino
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#import <Preferences.h>
|
||||||
|
|
||||||
|
const char PREFERENCES_KEY_WIFI_SSID[] = "wifi_ssid"; // preferences key name for wifi ssid; DO NOT CHANGE
|
||||||
|
const char PREFERENCES_KEY_WIFI_PSK[] = "wifi_psk"; // preferences key name for wifi psk; DO NOT CHANGE
|
||||||
|
const char WIFI_SSID[] = ""; // type in the wifi's ssid here
|
||||||
|
const char WIFI_PSK[] = ""; // and the wifi's passkey
|
||||||
|
Preferences config;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
config.begin("netspeaker", false);
|
||||||
|
config.clear();
|
||||||
|
config.putString(PREFERENCES_KEY_WIFI_SSID, WIFI_SSID);
|
||||||
|
config.putString(PREFERENCES_KEY_WIFI_PSK, WIFI_PSK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// empty loop
|
||||||
|
}
|
@@ -1,28 +1,39 @@
|
|||||||
/*
|
/*
|
||||||
This is free and unencumbered software released into the public domain.
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in
|
||||||
|
source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and
|
||||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
all copyright interest in the software to the public domain. We make this dedication for the benefit of
|
||||||
|
the public at large and to the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org/>
|
For more information, please refer to <http://unlicense.org/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "WiFi.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFi/src
|
#include <WiFi.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFi/src
|
||||||
#include "Audio.h" // https://github.com/schreibfaul1/ESP32-audioI2S
|
#include <Audio.h> // https://github.com/schreibfaul1/ESP32-audioI2S
|
||||||
#include "SPI.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/SPI/src
|
#include <SPI.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/SPI/src
|
||||||
#include "SD.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/src
|
#include <SD.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/src
|
||||||
#include "FS.h" // https://github.com/espressif/arduino-esp32/tree/master/libraries/FS/src
|
#include <FS.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/FS/src
|
||||||
#include <WebServer.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer
|
#include <WebServer.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer
|
||||||
#include <uri/UriBraces.h> // https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer
|
#include <uri/UriBraces.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer
|
||||||
|
#include <Preferences.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/Preferences/
|
||||||
|
#include <ESPmDNS.h> // https://github.com/espressif/arduino-esp32/blob/master/libraries/ESPmDNS/
|
||||||
|
|
||||||
|
|
||||||
// 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_tag = "v0.4.1-dev"; // version tag (with -dev appendix for dev versions)
|
||||||
const int operation_mode = 0; // 0: interconnected (no buttons, but api and wifi); 1: standalone (no wifi; no api)
|
const String version = "NetSpeaker " + version_tag; // version string used e.g. for access point ssid when wifi couldn't connect
|
||||||
|
const int operation_mode = 3; // 0: interconnected (no buttons, but api and wifi); 1: standalone (no wifi; no api);
|
||||||
|
// 2: choose by pressing any button at startup (not pressed: interconnected; pressed: standalone)
|
||||||
|
// 3: same as 2 but if not pressed: standalone; pressed: interconnected
|
||||||
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
|
||||||
const int SPI_MOSI = 23; // BOARD SPECIFIC
|
const int SPI_MOSI = 23; // BOARD SPECIFIC
|
||||||
@@ -32,37 +43,71 @@ const int I2S_BLCK = 4; // can be changed on need
|
|||||||
const int I2S_LRC = 15; // can be changed on need
|
const int I2S_LRC = 15; // can be changed on need
|
||||||
const int sdCardEjectPin = 13; // pin which is used to tell the program to eject the sd card (so that the file system doesn't brake)
|
const int sdCardEjectPin = 13; // pin which is used to tell the program to eject the sd card (so that the file system doesn't brake)
|
||||||
const int audioVolumePin = 25; // pin where the poti is connected to (wired as voltage divider)
|
const int audioVolumePin = 25; // pin where the poti is connected to (wired as voltage divider)
|
||||||
const int pausePlaybackPin = 12; // switch (the switching is done digitally, a button has to be connected, NO switch) between play and pause
|
const int pausePlaybackPin = 12; // switch (the switching is done digitally, a button has to be connected, NO switch) between play and pause)
|
||||||
const int forwardButtonPin = 14; // pin for forward button
|
const int forwardButtonPin = 14; // pin for forward button
|
||||||
const int backwardButtonPin = 27; // pin for backward btn
|
const int backwardButtonPin = 27; // pin for backward btn
|
||||||
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 bool runConfServer = true; // variable if the config server should be setup and run
|
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 WEB_RESTART_DELAY = 5000; // how long (in ms) to wait when a restart is requested by the api
|
||||||
|
const int startupChooserWaitTime = 5000; // how long to wait for user to choose the mode (in operation_modes 2 and 3)
|
||||||
|
const int startupChooserBlinkDelay = 500; // how long to wait for user to choose the mode (in operation_modes 2 and 3)
|
||||||
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 apSSID = version; // ssid of access point opened when not able to connect to wifi
|
const String apSSID = version; // ssid 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
|
const String apPSK = "aA16161Aa"; // pre-shared-key of access point opened when not able to connect to wifi
|
||||||
|
const char PREFERENCES_NAMESPACE[] = "netspeaker"; // namespace for preferences library (stores config data in the ESP32's built-in EEPROM)
|
||||||
|
const char PREFERENCES_KEY_WIFI_SSID[] = "wifi_ssid"; // preferences key name for wifi ssid
|
||||||
|
const char PREFERENCES_KEY_WIFI_PSK[] = "wifi_psk"; // preferences key name for wifi psk
|
||||||
|
const char PREFERENCES_KEY_FRIENDLY_NAME[] = "friendly_name"; // preferences key name for the NetSpeaker's friendly name
|
||||||
|
const char PREFERENCES_KEY_NETWORK_NAME[] = "network_name"; // preferences key name for the NetSpeaker's network name (<Network name>.local)
|
||||||
|
const char PREFERENCES_KEY_RESTORE_OLD_STATE[] = "restore_state"; // preferences key name for getting if the old state should be restored or not
|
||||||
|
const char PREFERENCES_KEY_RESTORE_PLAYING[] = "restore_playing"; // preferences key name for getting if the old state should be restored or not
|
||||||
|
const char PREFERENCES_KEY_VOLUME[] = "volume"; // preferences key name for old volume (for state restore)
|
||||||
|
const char PREFERENCES_KEY_BALANCE[] = "balance"; // preferences key name for old balance (for state restore)
|
||||||
|
const char PREFERENCES_KEY_EQ_LOW[] = "eq_low"; // preferences key name for old low equalizer value (for state restore)
|
||||||
|
const char PREFERENCES_KEY_EQ_MID[] = "eq_mid"; // preferences key name for old mid equalizer value (for state restore)
|
||||||
|
const char PREFERENCES_KEY_EQ_HIGH[] = "eq_high"; // preferences key name for old high equalizer value (for state restore)
|
||||||
|
const char PREFERENCES_KEY_PLAYING[] = "playing"; // preferences key name for info if currently playing (for state restore)
|
||||||
|
const char PREFERENCES_KEY_MUTED[] = "muted"; // preferences key name for info if currently muted
|
||||||
|
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_TELL_ADDRESS_AT_STARTUP[] = "tell_address"; // preferences key name for current playlist index (for state restore)
|
||||||
|
|
||||||
// create all needed variables
|
// all other variables needed
|
||||||
int currentVolume = 20; // variable where current volume (0...20) is stored
|
|
||||||
bool muted = false; // currently muted (does not affect currentVolume)
|
|
||||||
String currentSongPath; // path to currently playing song
|
|
||||||
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
|
WebServer api_server(webport_api); // web server for api (e.g. pause/play) + simple web UI
|
||||||
String currentPlaylist = "/audio/" + directoryPlaylistName + playlistExtension; // path to current playlist
|
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
|
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?
|
bool apON = false; // DON'T CHANGE IF NOT KNOWING WHAT YOU'RE DOING; is the access point opened?
|
||||||
WebServer conf_server(webport_config); // web server for configuration (e.g. WiFi password setting)
|
Preferences configuration;
|
||||||
WebServer api_server(webport_api); // web server for api (e.g. pause/play)
|
long lastTimeEEPROMwritten = 0; // used to limit the number of eeprom write cycles
|
||||||
|
int operationModeChosen = operation_mode; // if in operation modes 2 or 3, you can choose between "interconnected" (= 0) and "standalone" (=1). This variable is used to store the decision (made at startup!)
|
||||||
|
bool eof_speech_reached = false; // used to recognize the end of an tts speech (e.g. used for telling the address of the netspeaker)
|
||||||
|
bool wait_after_eof_speech = false; // used for the audio_eof_speech handler to determine what to do after reaching eof_speach
|
||||||
|
String default_network_name = "netspeaker"; // default network name (the part of the mDNS domain before .local (<networkName>.local will be registered))
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
String type; // e.g. local
|
String type; // e.g. local/google-tts/http
|
||||||
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
|
||||||
@@ -70,50 +115,142 @@ struct playbackInfo {
|
|||||||
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)
|
||||||
|
String tts_language; // text-to-speech language
|
||||||
};
|
};
|
||||||
struct playbackInfo pbInfo = { "", "", "", "", "", "", "", "", "" };
|
struct playbackInfo pbInfo = { "", "", "", "", "", "", "", "", "", "" };
|
||||||
|
|
||||||
|
|
||||||
|
// general helper when an system-halting error occurs (to show this, the LED blinks fast)
|
||||||
|
bool indicate_system_error() {
|
||||||
|
pinMode(readyPin, OUTPUT);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
digitalWrite(readyPin, LOW);
|
||||||
|
delay(150);
|
||||||
|
digitalWrite(readyPin, HIGH);
|
||||||
|
delay(150);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper for operation mode 2 and 3
|
||||||
|
bool wait_for_btn_press_and_blink(int blink_pin, int blink_delay, int wait_time) { // returns true for button pressed and false for button not pressed
|
||||||
|
pinMode(sdCardEjectPin, INPUT);
|
||||||
|
pinMode(backwardButtonPin, INPUT);
|
||||||
|
pinMode(pausePlaybackPin, INPUT);
|
||||||
|
pinMode(forwardButtonPin, INPUT);
|
||||||
|
pinMode(readyPin, OUTPUT);
|
||||||
|
|
||||||
|
int wait_timer_start = millis();
|
||||||
|
int blink_timer_start;
|
||||||
|
|
||||||
|
while ((millis() - wait_timer_start) < wait_time) { // wait_time in milliseconds!
|
||||||
|
// turn led on and wait for blink_delay while checking for button presses
|
||||||
|
digitalWrite(blink_pin, HIGH); // turn the LED on (HIGH is the voltage level)
|
||||||
|
blink_timer_start = millis();
|
||||||
|
while((millis() - blink_timer_start) < blink_delay) { // wait a given time period (delay in milliseconds!)
|
||||||
|
if(analogRead(sdCardEjectPin) > 4000 || analogRead(pausePlaybackPin) > 4000 || analogRead(forwardButtonPin) > 4000 || analogRead(backwardButtonPin) > 4000) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn led off and wait for blink_delay while checking for button presses
|
||||||
|
digitalWrite(blink_pin, LOW);
|
||||||
|
blink_timer_start = millis();
|
||||||
|
while((millis() - blink_timer_start) < blink_delay) {
|
||||||
|
if(analogRead(sdCardEjectPin) > 4000 || analogRead(pausePlaybackPin) > 4000 || analogRead(forwardButtonPin) > 4000 || analogRead(backwardButtonPin) > 4000) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
configuration.begin(PREFERENCES_NAMESPACE, false); // Initialize preferences library in RW mode
|
||||||
|
|
||||||
|
// restore process if configured so
|
||||||
|
if (configuration.getBool(PREFERENCES_KEY_RESTORE_OLD_STATE, true)) restoreLastState(); // standard of restore = true
|
||||||
|
|
||||||
// connect to sd card reader
|
// connect to sd card reader
|
||||||
Serial.println("[SETUP] Setting up SD-Card reader");
|
Serial.println(F("[SETUP] Setting up SD-Card reader"));
|
||||||
while (!setupSD(SD_CS, SPI_MISO, SPI_MOSI, SPI_SCK)) {
|
while (!setupSD(SD_CS, SPI_MISO, SPI_MOSI, SPI_SCK)) {
|
||||||
Serial.printf("[SETUP] Can't connect to SD card reader! Waiting for %d milliseconds...\n", retrySDMountTreshold);
|
Serial.printf("[SETUP] Can't connect to SD card reader! Waiting for %d milliseconds...\n", retrySDMountTreshold);
|
||||||
delay(retrySDMountTreshold);
|
delay(retrySDMountTreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation_mode == 0) { // things only need to be done if in interconnected mode
|
setupAudio(); // setup audio library
|
||||||
setupWiFi();
|
|
||||||
setupConfigWeb();
|
|
||||||
setupApiWeb();
|
// DEPENDING ON THE OPERATION MODE, DIFFERENT OPERATIONS NEED TO BE DONE
|
||||||
} else if (operation_mode == 1) { // things only need to be done if in standalone mode
|
// BEFORE STARTING. This is what the following code does.
|
||||||
|
|
||||||
|
// operation_mode 0: see below
|
||||||
|
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);
|
||||||
pinMode(backwardButtonPin, INPUT);
|
pinMode(backwardButtonPin, INPUT);
|
||||||
pinMode(pausePlaybackPin, INPUT);
|
pinMode(pausePlaybackPin, INPUT);
|
||||||
pinMode(forwardButtonPin, INPUT);
|
pinMode(forwardButtonPin, INPUT);
|
||||||
pinMode(readyPin, OUTPUT);
|
pinMode(readyPin, OUTPUT);
|
||||||
|
} else if (operation_mode == 2) { // if enabled choosing (mode 2: no press = interconnected, press = standalone)
|
||||||
|
bool runStandalone = wait_for_btn_press_and_blink(readyPin, startupChooserBlinkDelay, startupChooserWaitTime);
|
||||||
|
|
||||||
|
if (runStandalone) {
|
||||||
|
Serial.println(F("[SETUP] User pressed button; Running in 'Standalone' mode now!"));
|
||||||
|
operationModeChosen = 1;
|
||||||
|
digitalWrite(readyPin, HIGH);
|
||||||
} else {
|
} else {
|
||||||
Serial.println("[FATAL] PLEASE CHOOSE A OPERATION MODE! VALID OPTIONS: 0; 1. SLEEPING FOREVER.");
|
Serial.println(F("[SETUP] User didn't press any button; Running in 'Interconnected' mode now!"));
|
||||||
while (true) delay(100);
|
operationModeChosen = 0;
|
||||||
|
digitalWrite(readyPin, HIGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAudio(); // setup audio library
|
} else if (operation_mode == 3) { // if enabled choosing (mode 3: no press = standalone, press = interconnected)
|
||||||
createPlaylistFromDirectory("/audio"); // create playlist from default dir ("/audio")
|
bool runInterconnected = wait_for_btn_press_and_blink(readyPin, startupChooserBlinkDelay, startupChooserWaitTime);
|
||||||
|
|
||||||
|
if (runInterconnected) {
|
||||||
|
Serial.println(F("[SETUP] User pressed button; Running in 'Interconnected' mode now!"));
|
||||||
|
operationModeChosen = 0;
|
||||||
|
digitalWrite(readyPin, HIGH);
|
||||||
|
} else {
|
||||||
|
Serial.println(F("[SETUP] User didn't press any button; Running in 'Standalone' mode now!"));
|
||||||
|
operationModeChosen = 1;
|
||||||
|
digitalWrite(readyPin, HIGH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function is placed here because it could be that the operation mode is chosen to be interconnected - then this check needs to run afterwards!
|
||||||
|
if (operation_mode == 0 || operationModeChosen == 0) { // things only need to be done if in - or choosen to - interconnected mode
|
||||||
|
setupWiFi();
|
||||||
|
|
||||||
|
// set up the mDNS responder
|
||||||
|
setupMDNS();
|
||||||
|
|
||||||
|
// set up the API web server
|
||||||
|
setupWeb();
|
||||||
|
|
||||||
|
// now tell the address (can be turned off using the web API, state stored in the EEPROM)
|
||||||
|
tellAddressInfo(); // helper which uses google TTS to tell the address
|
||||||
|
}
|
||||||
|
if (operation_mode != 0 && operation_mode != 1 && operation_mode != 2 && operation_mode != 3) {
|
||||||
|
Serial.println(F("[FATAL] PLEASE CHOOSE A OPERATION MODE! VALID OPTIONS: 0; 1; 2; 3. SLEEPING FOREVER."));
|
||||||
|
indicate_system_error(); // halt system and blink readyPin
|
||||||
|
}
|
||||||
|
|
||||||
|
// AND start the current track (if restored from EEPROM, only nextAudio is doing something important)
|
||||||
|
// TODO: move createPlaylistFromDirectory to some place where it fits better and is not called uselessly
|
||||||
|
createPlaylistFromDirectory(defaultPlaylistFolder); // create playlist from default dir
|
||||||
nextAudio(); // play first element of the playlist
|
nextAudio(); // play first element of the playlist
|
||||||
digitalWrite(readyPin, HIGH); // show that startup is done and everything works fine
|
digitalWrite(readyPin, HIGH); // show that startup is done and everything works fine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if (operation_mode == 0) { // things only need to be done if in interconnected mode
|
if (operation_mode == 0 || operationModeChosen == 0) { // things only need to be done if in interconnected mode
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (operation_mode == 1 || operationModeChosen == 1) { // things only need to be done if in standalone mode
|
||||||
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
|
||||||
|
|
||||||
loopBtnListeners();
|
loopBtnListeners();
|
||||||
@@ -121,10 +258,16 @@ void loop() {
|
|||||||
// for SD card eject
|
// for SD card eject
|
||||||
if (analogRead(sdCardEjectPin) > 4000) { // bigger than 4000: so that it's not affected by a little touch of fingers (4095 is maximum; 0 minimum)
|
if (analogRead(sdCardEjectPin) > 4000) { // bigger than 4000: so that it's not affected by a little touch of fingers (4095 is maximum; 0 minimum)
|
||||||
SD.end(); // delete SD object and sync its cache to flash
|
SD.end(); // delete SD object and sync its cache to flash
|
||||||
|
writeLastState(); // save playback infos (volume, balance, equalizer, ...)
|
||||||
digitalWrite(readyPin, LOW); // indicate that SD card can get removed (the ready-LED will go off)
|
digitalWrite(readyPin, LOW); // indicate that SD card can get removed (the ready-LED will go off)
|
||||||
Serial.printf("[SETUP] Unmounting SD-Card, waiting for %dms and trying to remount (every %d milliseconds)...\n", waitOnSDCardEject, retrySDMountTreshold);
|
Serial.printf("[SETUP] Unmounting SD-Card, waiting for %dms and trying to remount (every %d milliseconds)...\n", waitOnSDCardEject, retrySDMountTreshold);
|
||||||
delay(waitOnSDCardEject);
|
delay(waitOnSDCardEject);
|
||||||
ESP.restart(); // reset everything and restart the program
|
ESP.restart(); // reset everything and restart the program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (millis() - lastTimeEEPROMwritten >= EEPROM_WRITE_INTERVAL) {
|
||||||
|
writeLastState(); // save current state to eeprom
|
||||||
|
lastTimeEEPROMwritten = millis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,56 +1,101 @@
|
|||||||
/*
|
/*
|
||||||
This is free and unencumbered software released into the public domain.
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in
|
||||||
|
source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and
|
||||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
all copyright interest in the software to the public domain. We make this dedication for the benefit of
|
||||||
|
the public at large and to the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org/>
|
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);
|
||||||
|
if (!muted) audio.setVolume(currentVolume);
|
||||||
|
else audio.setVolume(0); // mute if muted is true
|
||||||
|
audio.setBalance(balanceLevel);
|
||||||
|
audio.setTone(eqLow, eqMid, eqHigh);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tellAddressInfo() {
|
||||||
|
// TODO: make this Preference accessible via the Web API
|
||||||
|
if(configuration.getBool(PREFERENCES_KEY_TELL_ADDRESS_AT_STARTUP, true) && apON == false) { // only tell the name if the key is set (do it by default) and connected to wifi (for internet)
|
||||||
|
String tell_address_string = "Connected. Head over to " + WiFi.localIP().toString() + " or " + configuration.getString(PREFERENCES_KEY_NETWORK_NAME, default_network_name) + " dot local";
|
||||||
|
Serial.printf("[SETUP] Telling address now. String: %s\n", tell_address_string);
|
||||||
|
audio.connecttospeech(tell_address_string.c_str(), "en");
|
||||||
|
wait_after_eof_speech = true; // to prevent the eof_speech handler of the Audio.h library (defined below) to play next audio (as there's none at startup!)
|
||||||
|
while(!eof_speech_reached) { audio.loop(); } // wait till end of speech
|
||||||
|
wait_after_eof_speech = false; // turn calling nextAudio on again (the default behaviour)
|
||||||
|
eof_speech_reached = false; // reset the variable
|
||||||
|
Serial.println("[SETUP] Told the address.");
|
||||||
|
} else {
|
||||||
|
Serial.println("[SETUP] Did not tell my address because either not connected to WiFi or it's not configured so.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String nextAudio() {
|
String nextAudio() {
|
||||||
String url;
|
String url;
|
||||||
|
|
||||||
currentPlaylistPosition++; // increment once
|
currentPlaylistPosition++; // increment once
|
||||||
url = getURLFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
url = getURLFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
||||||
|
|
||||||
if (url == "") { // if the end of the playlist is reached go to start
|
if (url == "") { // if the end of the playlist is reached go to start (typically)
|
||||||
|
if(getURLFromPlaylist(currentPlaylist, 0) == "") { // untypically: prevent infinite loop if playlist doesn't exist
|
||||||
|
Serial.println(F("[ERROR] It seems like the current playlist does not exist! Defaulting to the default playlist!"));
|
||||||
|
currentPlaylist = defaultPlaylist;
|
||||||
|
currentPlaylistPosition = 0;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
// 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)
|
||||||
// these attributes are set by the audio_id3data function
|
// these attributes are set by the audio_id3data function (and the nextAudio and previousAudio functions)
|
||||||
pbInfo.title = "";
|
pbInfo.title = "";
|
||||||
pbInfo.album = "";
|
pbInfo.type = "";
|
||||||
pbInfo.track = "";
|
pbInfo.resourcePath = "";
|
||||||
pbInfo.artist = "";
|
pbInfo.artist = "";
|
||||||
|
pbInfo.track = "";
|
||||||
|
pbInfo.album = "";
|
||||||
pbInfo.year = "";
|
pbInfo.year = "";
|
||||||
pbInfo.genre = "";
|
pbInfo.genre = "";
|
||||||
pbInfo.copyright = "";
|
pbInfo.copyright = "";
|
||||||
|
pbInfo.tts_language = "";
|
||||||
|
|
||||||
|
pbInfo.resourcePath = url;
|
||||||
|
pbInfo.type = "";
|
||||||
|
|
||||||
|
|
||||||
|
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());
|
||||||
|
pbInfo.type = "http";
|
||||||
|
} 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
|
||||||
|
pbInfo.type = "local";
|
||||||
|
} else if (url.startsWith("speech://")) { // format: speech://CC@TEXT where CC is the country code, e.g. en or de
|
||||||
|
String language = url.substring(9, 11).c_str();
|
||||||
|
String speech_path = url.substring(12).c_str();
|
||||||
|
Serial.printf("[INFO] Starting speech from playlist (ID: %d, Language: %s, Text: %s)\n", currentPlaylistPosition, language, speech_path);
|
||||||
|
audio.connecttospeech(url.substring(12).c_str(), url.substring(9, 11).c_str());
|
||||||
|
pbInfo.type = "google-tts";
|
||||||
|
pbInfo.track = speech_path;
|
||||||
|
pbInfo.tts_language = language;
|
||||||
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@@ -62,29 +107,40 @@ String previousAudio() {
|
|||||||
if (currentPlaylistPosition < 0) currentPlaylistPosition = 0;
|
if (currentPlaylistPosition < 0) currentPlaylistPosition = 0;
|
||||||
|
|
||||||
url = getURLFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
url = getURLFromPlaylist(currentPlaylist, currentPlaylistPosition);
|
||||||
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";
|
|
||||||
|
|
||||||
// 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)
|
||||||
// these attributes are set by the audio_id3data function
|
// these attributes are set by the audio_id3data function (and the nextAudio and previousAudio functions)
|
||||||
pbInfo.title = "";
|
pbInfo.title = "";
|
||||||
pbInfo.album = "";
|
pbInfo.type = "";
|
||||||
pbInfo.track = "";
|
pbInfo.resourcePath = "";
|
||||||
pbInfo.artist = "";
|
pbInfo.artist = "";
|
||||||
|
pbInfo.track = "";
|
||||||
|
pbInfo.album = "";
|
||||||
pbInfo.year = "";
|
pbInfo.year = "";
|
||||||
pbInfo.genre = "";
|
pbInfo.genre = "";
|
||||||
pbInfo.copyright = "";
|
pbInfo.copyright = "";
|
||||||
|
pbInfo.tts_language = "";
|
||||||
|
|
||||||
|
pbInfo.resourcePath = url;
|
||||||
|
pbInfo.type = "";
|
||||||
|
|
||||||
|
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());
|
||||||
|
pbInfo.type = "http";
|
||||||
|
} 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
|
||||||
|
pbInfo.type = "local";
|
||||||
|
} else if (url.startsWith("speech://")) { // format: speech://CC@TEXT where CC is the country code, e.g. en or de
|
||||||
|
String language = url.substring(9, 11).c_str();
|
||||||
|
String speech_path = url.substring(12).c_str();
|
||||||
|
Serial.printf("[INFO] Starting speech from playlist (ID: %d, Language: %s, Text: %s)\n", currentPlaylistPosition, language, speech_path);
|
||||||
|
audio.connecttospeech(url.substring(12).c_str(), url.substring(9, 11).c_str());
|
||||||
|
pbInfo.type = "google-tts";
|
||||||
|
pbInfo.track = speech_path;
|
||||||
|
pbInfo.tts_language = language;
|
||||||
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@@ -115,11 +171,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +194,17 @@ 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);
|
||||||
|
|
||||||
|
if (wait_after_eof_speech) {
|
||||||
|
eof_speech_reached = true;
|
||||||
|
} else {
|
||||||
nextAudio();
|
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")) {
|
if (String(info).startsWith(F("End of webstream"))) {
|
||||||
nextAudio();
|
nextAudio();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,17 +215,17 @@ void audio_id3data(const char *info) { // id3 metadata
|
|||||||
String info_str = String(info);
|
String info_str = String(info);
|
||||||
if (info_str.startsWith("Album: ")) {
|
if (info_str.startsWith("Album: ")) {
|
||||||
pbInfo.album = info_str.substring(7);
|
pbInfo.album = info_str.substring(7);
|
||||||
} else if (info_str.startsWith("Title: ")) {
|
} else if (info_str.startsWith(F("Title: "))) {
|
||||||
pbInfo.title = info_str.substring(7);
|
pbInfo.title = info_str.substring(7);
|
||||||
} else if (info_str.startsWith("Track: ")) {
|
} else if (info_str.startsWith(F("Track: "))) {
|
||||||
pbInfo.track = info_str.substring(7);
|
pbInfo.track = info_str.substring(7);
|
||||||
} else if (info_str.startsWith("Artist: ")) {
|
} else if (info_str.startsWith(F("Artist: "))) {
|
||||||
pbInfo.artist = info_str.substring(8);
|
pbInfo.artist = info_str.substring(8);
|
||||||
} else if (info_str.startsWith("ContentType: ")) {
|
} else if (info_str.startsWith(F("ContentType: "))) {
|
||||||
pbInfo.genre = info_str.substring(13);
|
pbInfo.genre = info_str.substring(13);
|
||||||
} else if (info_str.startsWith("Year: ")) {
|
} else if (info_str.startsWith(F("Year: "))) {
|
||||||
pbInfo.year = info_str.substring(6);
|
pbInfo.year = info_str.substring(6);
|
||||||
} else if (info_str.startsWith("Copyright: ")) {
|
} else if (info_str.startsWith(F("Copyright: "))) {
|
||||||
pbInfo.copyright = info_str.substring(11);
|
pbInfo.copyright = info_str.substring(11);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,10 +249,9 @@ void audio_lasthost(const char *info) { // stream URL played
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *********************************************************************************** //
|
// *********************************************************************************** //
|
||||||
// *************** Code heavily inspired by schreibfaul1's wiki entry ************** //
|
// ******** Following function heavily inspired by schreibfaul1's wiki entry ******** //
|
||||||
// * https://github.com/schreibfaul1/ESP32-audioI2S/wiki#what-audio-events-are-there * //
|
// * 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
|
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);
|
Serial.printf("[Audio.h] ID3Image Found at position: %u | Length: %u\n", pos, size);
|
||||||
uint8_t buf[1024];
|
uint8_t buf[1024];
|
||||||
|
@@ -1,12 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
This is free and unencumbered software released into the public domain.
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in
|
||||||
|
source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and
|
||||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
all copyright interest in the software to the public domain. We make this dedication for the benefit of
|
||||||
|
the public at large and to the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org/>
|
For more information, please refer to <http://unlicense.org/>
|
||||||
*/
|
*/
|
||||||
@@ -20,58 +26,56 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
String getWiFiSSID(String confPath) {
|
// write preferences via the Preferences.h library
|
||||||
String ssid;
|
void writeLastState() {
|
||||||
char new_char;
|
if (configuration.getBool(PREFERENCES_KEY_RESTORE_OLD_STATE, true)) { // only save if restoration is wanted (to avoid unnecessary write cycles on eeprom)
|
||||||
|
// always check if the value is different on the EEPROM so that it has to be updated
|
||||||
if (!SD.exists(confPath)) return ""; // if the config file doesn't exist, return nothing and exit
|
Serial.printf("[RESTORE] Saving last state to EEPROM.\n");
|
||||||
File confFile = SD.open(confPath);
|
if (configuration.getInt(PREFERENCES_KEY_VOLUME, maxVolume) != currentVolume) configuration.putInt(PREFERENCES_KEY_VOLUME, currentVolume);
|
||||||
|
if (configuration.getInt(PREFERENCES_KEY_BALANCE, 0) != balanceLevel) configuration.putInt(PREFERENCES_KEY_BALANCE, balanceLevel);
|
||||||
ssid = (char)confFile.read();
|
if (configuration.getInt(PREFERENCES_KEY_EQ_LOW, 0) != eqLow) configuration.putInt(PREFERENCES_KEY_EQ_LOW, eqLow);
|
||||||
while (ssid[ssid.length() - 1] != '\n' && confFile.available()) {
|
if (configuration.getInt(PREFERENCES_KEY_EQ_MID, 0) != eqMid) configuration.putInt(PREFERENCES_KEY_EQ_MID, eqMid);
|
||||||
new_char = (char)confFile.read();
|
if (configuration.getInt(PREFERENCES_KEY_EQ_HIGH, 0) != eqHigh) configuration.putInt(PREFERENCES_KEY_EQ_HIGH, eqHigh);
|
||||||
if (new_char == '\n') break;
|
if (configuration.getBool(PREFERENCES_KEY_PLAYING, false) != audioPlaying) configuration.putBool(PREFERENCES_KEY_PLAYING, audioPlaying);
|
||||||
ssid += new_char;
|
if (configuration.getBool(PREFERENCES_KEY_MUTED, false) != muted) configuration.putBool(PREFERENCES_KEY_MUTED, muted);
|
||||||
|
if (configuration.getString(PREFERENCES_KEY_PLAYLIST_PATH, defaultPlaylist) != currentPlaylist) configuration.putString(PREFERENCES_KEY_PLAYLIST_PATH, currentPlaylist);
|
||||||
|
if ((configuration.getInt(PREFERENCES_KEY_PLAYLIST_INDEX, 0) - 1) != currentPlaylistPosition) configuration.putInt(PREFERENCES_KEY_PLAYLIST_INDEX, currentPlaylistPosition);
|
||||||
|
Serial.printf("[RESTORE] Saved.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssid;
|
|
||||||
}
|
|
||||||
String getWiFiSSID() {
|
|
||||||
return getWiFiSSID(wifiConfigPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getWiFiPSK(String confPath) {
|
void restoreLastState() {
|
||||||
String line1;
|
Serial.println("[RESTORE] Starting to restore the last saved state...");
|
||||||
String psk;
|
currentVolume = configuration.getInt(PREFERENCES_KEY_VOLUME, maxVolume); // maximum volume as default
|
||||||
char new_char;
|
Serial.printf("[RESTORE] PREFERENCES_KEY_VOLUME = %i\n", currentVolume);
|
||||||
|
balanceLevel = configuration.getInt(PREFERENCES_KEY_BALANCE, 0); // 0 (middle) as default
|
||||||
if (!SD.exists(confPath)) return ""; // if the config file doesn't exist, return nothing and exit
|
Serial.printf("[RESTORE] PREFERENCES_KEY_BALANCE = %i\n", balanceLevel);
|
||||||
File confFile = SD.open(confPath);
|
eqLow = configuration.getInt(PREFERENCES_KEY_EQ_LOW, 0); // 0dB (normal) as default
|
||||||
|
Serial.printf("[RESTORE] PREFERENCES_KEY_EQ_LOW = %i\n", eqLow);
|
||||||
// skip the first line
|
eqMid = configuration.getInt(PREFERENCES_KEY_EQ_MID, 0); // 0dB (normal) as default
|
||||||
line1 = (char)confFile.read(); // read character...
|
Serial.printf("[RESTORE] PREFERENCES_KEY_EQ_MID = %i\n", eqMid);
|
||||||
while (line1[line1.length() - 1] != '\n' && confFile.available()) line1 += (char)confFile.read(); // ... as long as a newline hits
|
eqHigh = configuration.getInt(PREFERENCES_KEY_EQ_HIGH, 0); // 0dB (normal) as default
|
||||||
|
Serial.printf("[RESTORE] PREFERENCES_KEY_EQ_MID = %i\n", eqHigh);
|
||||||
psk = (char)confFile.read();
|
if (configuration.getBool(PREFERENCES_KEY_RESTORE_PLAYING, false)) audioPlaying = configuration.getBool(PREFERENCES_KEY_PLAYING, false); // only restore playing if wanted; not playing as default
|
||||||
while (confFile.available()) {
|
Serial.printf("[RESTORE] PREFERENCES_KEY_PLAYING = %s\n", audioPlaying ? "true" : "false");
|
||||||
new_char = (char)confFile.read();
|
muted = configuration.getBool(PREFERENCES_KEY_MUTED, false);
|
||||||
if (new_char == '\n') break;
|
Serial.printf("[RESTORE] PREFERENCES_KEY_MUTED = %s\n", muted ? "true" : "false");
|
||||||
psk += new_char;
|
if( operation_mode == 0 ) { // only restore playlist if in interconnected mode (operation_mode = 0)
|
||||||
|
currentPlaylist = configuration.getString(PREFERENCES_KEY_PLAYLIST_PATH, defaultPlaylist); // default folder
|
||||||
|
Serial.printf("[RESTORE] PREFERENCES_KEY_PLAYLIST_PATH = %s\n", currentPlaylist.c_str());
|
||||||
}
|
}
|
||||||
|
currentPlaylistPosition = configuration.getInt(PREFERENCES_KEY_PLAYLIST_INDEX, 0) - 1; // -1 as default playlist position (is incremented with the first nextAudio() call)
|
||||||
|
Serial.printf("[RESTORE] PREFERENCES_KEY_PLAYLIST_INDEX = %i\n", currentPlaylistPosition);
|
||||||
|
|
||||||
return psk;
|
Serial.println(F("[RESTORE] Restored."));
|
||||||
}
|
}
|
||||||
String getWiFiPSK() {
|
|
||||||
return getWiFiPSK(wifiConfigPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool createPlaylistFromDirectory(String folderpath) { // create a .m3u playlist from all directory contents (the directory 'folderpath' is used)
|
bool createPlaylistFromDirectory(String folderpath) { // create a .m3u playlist from all directory contents (the directory 'folderpath' is used)
|
||||||
File folder;
|
File folder;
|
||||||
File playlist;
|
File playlist;
|
||||||
|
|
||||||
if (folderpath[folderpath.length() - 1] == '/' && folderpath.length() > 1) { // check if file is ending with a "/" - because it wont work otherwise
|
if (folderpath[folderpath.length() - 1] == '/' && folderpath.length() > 1) { // check if file is ending with a "/" - because it wont work otherwise
|
||||||
Serial.printf("[ERROR] Can't use a folder path with trailing /\n");
|
Serial.println(F("[ERROR] Can't use a folder path with trailing /"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,17 +1,23 @@
|
|||||||
/*
|
/*
|
||||||
This is free and unencumbered software released into the public domain.
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in
|
||||||
|
source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and
|
||||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
all copyright interest in the software to the public domain. We make this dedication for the benefit of
|
||||||
|
the public at large and to the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org/>
|
For more information, please refer to <http://unlicense.org/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String generate_api_json(bool success, String content, bool plain) {
|
String send_api_json(bool success, String content, bool plain) {
|
||||||
String json = "{\"success\": "; // success
|
String json = "{\"success\": "; // success
|
||||||
json += success ? "true" : "false";
|
json += success ? "true" : "false";
|
||||||
json += ", ";
|
json += ", ";
|
||||||
@@ -24,88 +30,105 @@ String generate_api_json(bool success, String content, bool plain) {
|
|||||||
}
|
}
|
||||||
json += "}";
|
json += "}";
|
||||||
|
|
||||||
|
api_server.sendHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
api_server.send(200, "application/json", json);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
String generate_api_json(bool success) { // if you just want the basic json frame with the basic info
|
String send_api_json(bool success) { // if you just want the basic json frame with the basic info
|
||||||
return generate_api_json(success, "", true);
|
return send_api_json(success, "", true);
|
||||||
}
|
}
|
||||||
String generate_api_json(bool success, String content) { // when info is to be embedded
|
String send_api_json(bool success, String content) { // when info is to be embedded
|
||||||
return generate_api_json(success, content, false);
|
return send_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() {
|
---- API FUNCTIONS ----
|
||||||
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 | ";
|
|
||||||
html += version;
|
|
||||||
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>";
|
|
||||||
|
|
||||||
conf_server.send(200, "text/html", html);
|
|
||||||
}
|
|
||||||
|
|
||||||
void apiRoot() {
|
|
||||||
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 | ";
|
|
||||||
html += version;
|
|
||||||
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>";
|
|
||||||
|
|
||||||
api_server.send(200, "text/html", html);
|
|
||||||
}
|
|
||||||
|
|
||||||
void api_v1_playback_toggle() {
|
void api_v1_playback_toggle() {
|
||||||
Serial.println("[HTTP] [API] 200 - GET '/api/v1/playback/toggle'");
|
Serial.println("[HTTP] [API] 200 - '/api/v1/playback/toggle'");
|
||||||
|
|
||||||
audioPlaying = !audioPlaying;
|
audioPlaying = !audioPlaying;
|
||||||
Serial.printf("[INFO] Playback has switched: %s\n", audioPlaying ? "Playing" : "Paused");
|
Serial.printf("[INFO] Playback has switched: %s\n", audioPlaying ? "Playing" : "Paused");
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true));
|
send_api_json(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playback_play() {
|
void api_v1_playback_play() {
|
||||||
Serial.println("[HTTP] [API] 200 - GET '/_api/v1/playback/play'");
|
Serial.println("[HTTP] [API] 200 - '/_api/v1/playback/play'");
|
||||||
|
|
||||||
audioPlaying = true;
|
audioPlaying = true;
|
||||||
Serial.printf("[INFO] Playback has switched: Playing\n");
|
Serial.printf("[INFO] Playback has switched: Playing\n");
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true));
|
send_api_json(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playback_pause() {
|
void api_v1_playback_pause() {
|
||||||
Serial.println("[HTTP] [API] 200 - GET '/api/v1/playback/pause'");
|
Serial.println("[HTTP] [API] 200 - '/api/v1/playback/pause'");
|
||||||
|
|
||||||
audioPlaying = false;
|
audioPlaying = false;
|
||||||
Serial.printf("[INFO] Playback has switched: Paused\n");
|
Serial.printf("[INFO] Playback has switched: Paused\n");
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true));
|
send_api_json(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 - '/api/v1/playback/next'");
|
||||||
|
|
||||||
nextAudio();
|
nextAudio();
|
||||||
String content = "\"resource_playlist_index\": ";
|
String content = "\"resource_playlist_index\": ";
|
||||||
content += String(currentPlaylistPosition);
|
content += String(currentPlaylistPosition);
|
||||||
|
|
||||||
|
send_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 - '/api/v1/playback/previous'");
|
||||||
|
|
||||||
previousAudio();
|
previousAudio();
|
||||||
String content = "\"resource_playlist_index\": ";
|
String content = "\"resource_playlist_index\": ";
|
||||||
content += String(currentPlaylistPosition);
|
content += String(currentPlaylistPosition);
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true, content));
|
send_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 - '/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 {
|
||||||
|
send_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 {
|
||||||
|
send_api_json(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = "\"resource_playlist_index\": ";
|
||||||
|
content += String(currentPlaylistPosition);
|
||||||
|
send_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 - '/api/v1/volume/%s'\n", option);
|
||||||
|
|
||||||
if (option == "up") {
|
if (option == "up") {
|
||||||
currentVolume++;
|
currentVolume++;
|
||||||
@@ -117,66 +140,177 @@ void api_v1_playback_volume() {
|
|||||||
muted = true;
|
muted = true;
|
||||||
} else if (option == "unmute") {
|
} else if (option == "unmute") {
|
||||||
muted = false;
|
muted = false;
|
||||||
|
} else if (option == "toggle_mute") {
|
||||||
|
muted = !muted;
|
||||||
} 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") {
|
} else if (option == "get_max") {
|
||||||
currentVolume = 0;
|
send_api_json(success, "\"volume_max\": " + String(maxVolume));
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!muted) audio.setVolume(currentVolume); // set volume if not muted
|
if (!muted) audio.setVolume(currentVolume); // set volume if not muted and in range
|
||||||
else audio.setVolume(0); // if muted, set volume to 0
|
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\": ";
|
||||||
content += muted ? "true" : "false";
|
content += muted ? "true" : "false";
|
||||||
api_server.send(200, "application/json", generate_api_json(success, content)); // generate json and send it
|
content += ", \"volume_max\": " + String(maxVolume);
|
||||||
|
send_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 - '/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);
|
||||||
|
send_api_json(success, content); // generate json and send it
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_eq_get() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/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);
|
||||||
|
|
||||||
|
send_api_json(true, content); // generate json and send it
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_eq_reset() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/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);
|
||||||
|
|
||||||
|
send_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 - '/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 equalizer 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);
|
||||||
|
send_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 - '/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 equalizer 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);
|
||||||
|
send_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 - '/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 equalizer 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);
|
||||||
|
send_api_json(success, content); // generate json and send it
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playback_info() {
|
void api_v1_playback_info() {
|
||||||
Serial.println("[HTTP] [API] 200 - GET '/api/v1/playback/info'");
|
Serial.println("[HTTP] [API] 200 - '/api/v1/playback/info'");
|
||||||
String content = "\"resource_path\": \"" + pbInfo.resourcePath + "\", "; // resource's path
|
String content = "\"resource_path\": \"" + pbInfo.resourcePath + "\", "; // resource's path
|
||||||
content += "\"resource_type\": \"" + pbInfo.type + "\", "; // type
|
content += "\"resource_type\": \"" + pbInfo.type + "\", "; // type
|
||||||
content += "\"resource_title\": \"" + pbInfo.title + "\", "; // title
|
content += "\"resource_title\": \"" + pbInfo.title + "\", "; // title
|
||||||
@@ -186,14 +320,15 @@ void api_v1_playback_info() {
|
|||||||
content += "\"resource_year\": \"" + pbInfo.year + "\", "; // year
|
content += "\"resource_year\": \"" + pbInfo.year + "\", "; // year
|
||||||
content += "\"resource_genre\": \"" + pbInfo.genre + "\", "; // genre
|
content += "\"resource_genre\": \"" + pbInfo.genre + "\", "; // genre
|
||||||
content += "\"resource_copyright\": \"" + pbInfo.copyright + "\", "; // copyright
|
content += "\"resource_copyright\": \"" + pbInfo.copyright + "\", "; // copyright
|
||||||
|
content += "\"resource_tts_language\": \"" + pbInfo.tts_language + "\", "; // language
|
||||||
content += "\"resource_playlist_path\": \"" + currentPlaylist + "\", "; // playlist path
|
content += "\"resource_playlist_path\": \"" + currentPlaylist + "\", "; // playlist path
|
||||||
content += "\"resource_playlist_index\": " + String(currentPlaylistPosition); // playlist index (starting from 0)
|
content += "\"resource_playlist_index\": " + String(currentPlaylistPosition); // playlist index (starting from 0)
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true, content));
|
send_api_json(true, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playlist_get() {
|
void api_v1_playlist_get() {
|
||||||
Serial.println("[HTTP] [API] 200 - GET '/api/v1/playlist/get'");
|
Serial.println("[HTTP] [API] 200 - '/api/v1/playlist/get'");
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
String content = "\"playlist\": {";
|
String content = "\"playlist\": {";
|
||||||
@@ -217,16 +352,16 @@ void api_v1_playlist_get() {
|
|||||||
}
|
}
|
||||||
content += "}";
|
content += "}";
|
||||||
|
|
||||||
api_server.send(200, "application/json", generate_api_json(true, content));
|
send_api_json(true, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playlist_create() {
|
void api_v1_playlist_create() {
|
||||||
Serial.println("[HTTP] [API] 200 - POST '/api/v1/playlist/create'");
|
Serial.println("[HTTP] [API] 200 - '/api/v1/playlist/create'");
|
||||||
|
|
||||||
// get the right POST param
|
// get the right POST param
|
||||||
String folderPath;
|
String folderPath;
|
||||||
for (int i = 0; i < api_server.args(); i++) {
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
if (api_server.argName(i) == "folderPath") {
|
if (api_server.argName(i) == "folder_path") {
|
||||||
folderPath = api_server.arg(i);
|
folderPath = api_server.arg(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -236,81 +371,280 @@ void api_v1_playlist_create() {
|
|||||||
if (code) { // if creating the playlist was successful
|
if (code) { // if creating the playlist was successful
|
||||||
String playlistPath = folderPath + "/" + directoryPlaylistName + playlistExtension;
|
String playlistPath = folderPath + "/" + directoryPlaylistName + playlistExtension;
|
||||||
String content = "\"playlist_path\": \"" + playlistPath + "\"";
|
String content = "\"playlist_path\": \"" + playlistPath + "\"";
|
||||||
api_server.send(200, "application/json", generate_api_json(true, content));
|
send_api_json(true, content);
|
||||||
} else {
|
} else {
|
||||||
api_server.send(200, "application/json", generate_api_json(false));
|
send_api_json(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_playlist_play() {
|
void api_v1_playlist_play() {
|
||||||
Serial.println("[HTTP] [API] 200 - POST '/api/v1/playlist/play'");
|
Serial.println("[HTTP] [API] 200 - '/api/v1/playlist/play'");
|
||||||
|
|
||||||
// get the right POST param
|
// get the right POST param
|
||||||
String playlistPath;
|
String playlistPath;
|
||||||
for (int i = 0; i < api_server.args(); i++) {
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
if (api_server.argName(i) == "playlistPath") {
|
if (api_server.argName(i) == "playlist_path") {
|
||||||
playlistPath = api_server.arg(i);
|
playlistPath = api_server.arg(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getURLFromPlaylist(playlistPath, 0) == "") { // if playlist is empty or nonexistent
|
if (getURLFromPlaylist(playlistPath, 0) == "") { // get the first item from the playlist to check whether the pl is empty or nonexistent
|
||||||
api_server.send(200, "application/json", generate_api_json(false)); // send no success info
|
send_api_json(false); // send no success info
|
||||||
} else {
|
} else {
|
||||||
currentPlaylist = playlistPath;
|
currentPlaylist = playlistPath;
|
||||||
currentPlaylistPosition = -1;
|
currentPlaylistPosition = -1;
|
||||||
nextAudio();
|
nextAudio();
|
||||||
api_server.send(200, "application/json", generate_api_json(true)); // just return the success info
|
send_api_json(true); // just return the success info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void api_v1_settings_restart() {
|
void api_v1_system_restart() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/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.sendHeader("Access-Control-Allow-Origin", "*");
|
||||||
api_server.send(200, "application/json", "{\"restart\": true, \"wait_time\": 5000}");
|
api_server.send(200, "application/json", "{\"restart\": true, \"wait_time\": 5000}");
|
||||||
|
delay(WEB_RESTART_DELAY);
|
||||||
ESP.restart(); // reset everything and restart the program
|
ESP.restart(); // reset everything and restart the program
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupConfigWeb() {
|
void api_v1_system_friendlyname_get() {
|
||||||
if (runConfServer) {
|
Serial.println("[HTTP] [API] 200 - '/api/v1/system/name'");
|
||||||
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));
|
send_api_json(true, "\"friendly_name\": \"" + configuration.getString(PREFERENCES_KEY_FRIENDLY_NAME) + "\"");
|
||||||
conf_server.begin();
|
|
||||||
Serial.println("[HTTP] [Config] Started Config server");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupApiWeb() {
|
void api_v1_system_friendlyname_change() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/api/v1/system/name/change'");
|
||||||
|
|
||||||
|
String currentFriendlyName = configuration.getString(PREFERENCES_KEY_FRIENDLY_NAME, "");
|
||||||
|
String newFriendlyName;
|
||||||
|
bool arg_given = false;
|
||||||
|
// get the right post param (by key)
|
||||||
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
|
if (api_server.argName(i) == "friendly_name") {
|
||||||
|
arg_given = true;
|
||||||
|
newFriendlyName = api_server.arg(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// define what to do if nothing really changed (or no argument was given)
|
||||||
|
if (newFriendlyName == currentFriendlyName || !arg_given) {
|
||||||
|
send_api_json(false, "\"friendly_name\": \"" + currentFriendlyName + "\""); // return with no success ("false")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.putString(PREFERENCES_KEY_FRIENDLY_NAME, newFriendlyName);
|
||||||
|
Serial.printf("[INFO] Changed friendly name from \"%s\" to \"%s\".\n", currentFriendlyName, newFriendlyName);
|
||||||
|
|
||||||
|
send_api_json(true, "\"friendly_name\": \"" + newFriendlyName + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_networkname_get() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/api/v1/system/network_name'");
|
||||||
|
|
||||||
|
send_api_json(true, "\"network_name\": \"" + configuration.getString(PREFERENCES_KEY_NETWORK_NAME, default_network_name) + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_networkname_change() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/api/v1/system/network_name/change'");
|
||||||
|
|
||||||
|
String currentNetworkName = configuration.getString(PREFERENCES_KEY_NETWORK_NAME, default_network_name);
|
||||||
|
String newNetworkName;
|
||||||
|
bool arg_given = false;
|
||||||
|
bool apply_changes = false; // whether changes should be applied directly
|
||||||
|
// get the right post param (by key)
|
||||||
|
for (int i = 0; i < api_server.args(); i++) {
|
||||||
|
if (api_server.argName(i) == "apply_changes") {
|
||||||
|
apply_changes = true;
|
||||||
|
}
|
||||||
|
if (api_server.argName(i) == "network_name") {
|
||||||
|
arg_given = true;
|
||||||
|
newNetworkName = api_server.arg(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// define what to do if nothing really changed (or no argument was given)
|
||||||
|
if (newNetworkName == currentNetworkName || !arg_given) {
|
||||||
|
send_api_json(false, "\"network_name\": \"" + currentNetworkName + "\", \"apply_changes\": false"); // return with no success ("false")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.putString(PREFERENCES_KEY_NETWORK_NAME, newNetworkName);
|
||||||
|
Serial.printf("[INFO] Changed network name from \"%s\" to \"%s\".\n", currentNetworkName, newNetworkName);
|
||||||
|
|
||||||
|
if(apply_changes) {
|
||||||
|
setupMDNS();
|
||||||
|
}
|
||||||
|
|
||||||
|
send_api_json(true, "\"network_name\": \"" + newNetworkName + "\", \"apply_changes\": " + (apply_changes ? "true" : "false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_restore_state() {
|
||||||
|
String option = api_server.pathArg(0);
|
||||||
|
bool success = true;
|
||||||
|
Serial.printf("[HTTP] [API] 200 - '/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 if (option == "toggle") { // toggle; default to true
|
||||||
|
configuration.putBool(PREFERENCES_KEY_RESTORE_OLD_STATE, !configuration.getBool(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";
|
||||||
|
send_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 - '/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 if (option == "toggle") { // toggle; default to false (do not restore playing by default)
|
||||||
|
configuration.putBool(PREFERENCES_KEY_RESTORE_PLAYING, !configuration.getBool(PREFERENCES_KEY_RESTORE_PLAYING, true));
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = "\"restore_playing\": "; // prepare the http response
|
||||||
|
content += configuration.getBool(PREFERENCES_KEY_RESTORE_PLAYING, false) ? "true" : "false";
|
||||||
|
send_api_json(success, content); // generate json and send it
|
||||||
|
}
|
||||||
|
void api_v1_system_tell_address() {
|
||||||
|
String option = api_server.pathArg(0);
|
||||||
|
bool success = true;
|
||||||
|
Serial.printf("[HTTP] [API] 200 - '/api/v1/system/tell_address/%s'\n", option);
|
||||||
|
|
||||||
|
if (option == "get") {
|
||||||
|
// just here that calling .../get wont respond 404
|
||||||
|
} else if (option == "on") {
|
||||||
|
configuration.putBool(PREFERENCES_KEY_TELL_ADDRESS_AT_STARTUP, true);
|
||||||
|
} else if (option == "off") {
|
||||||
|
configuration.putBool(PREFERENCES_KEY_TELL_ADDRESS_AT_STARTUP, false);
|
||||||
|
} else if (option == "toggle") { // toggle; default to true (tell by default)
|
||||||
|
configuration.putBool(PREFERENCES_KEY_TELL_ADDRESS_AT_STARTUP, !configuration.getBool(PREFERENCES_KEY_TELL_ADDRESS_AT_STARTUP, false));
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = "\"tell_address\": "; // prepare the http response
|
||||||
|
content += configuration.getBool(PREFERENCES_KEY_TELL_ADDRESS_AT_STARTUP, true) ? "true" : "false";
|
||||||
|
send_api_json(success, content); // generate json and send it
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_version() {
|
||||||
|
Serial.printf("[HTTP] [API] 200 - '/api/v1/system/version'\n");
|
||||||
|
|
||||||
|
send_api_json(true, "\"version\": \"" + version + "\", " + "\"version_tag\": \"" + version_tag + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_wifi_getssid() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/api/v1/system/wifi/get_ssid'");
|
||||||
|
|
||||||
|
send_api_json(true, "\"wifi_ssid\": \"" + configuration.getString(PREFERENCES_KEY_WIFI_SSID) + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
api_v1_system_wifi_get_ap_creds() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/api/v1/system/wifi/get_ap_creds'");
|
||||||
|
|
||||||
|
send_api_json(true, "\"ap_ssid\": \"" + apSSID + "\", \"ap_psk\": \"" + apPSK + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void api_v1_system_wifi_change() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/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
|
||||||
|
configuration.putString(PREFERENCES_KEY_WIFI_SSID, ssid);
|
||||||
|
configuration.putString(PREFERENCES_KEY_WIFI_PSK, psk);
|
||||||
|
} else if (ssid != "" && psk == "") { // just the ssid is given
|
||||||
|
configuration.putString(PREFERENCES_KEY_WIFI_SSID, ssid);
|
||||||
|
} else if (ssid == "" && psk != "") { // just the psk is given
|
||||||
|
configuration.putString(PREFERENCES_KEY_WIFI_PSK, psk);
|
||||||
|
} else { // none of ssid or psk is given
|
||||||
|
send_api_json(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[INFO] Changed wifi credentials (SSID: '%s' | PSK: '%s')\n", ssid.c_str(), psk.c_str());
|
||||||
|
send_api_json(true, "\"ssid\": \"" + ssid + "\", \"psk\": \"" + psk + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupWeb() {
|
||||||
|
api_server.on("/", []() {
|
||||||
|
Serial.println("[HTTP] [API] 200 - '/'");
|
||||||
|
char message[] = "{\"message\": \"Welcome to your NetSpeaker's API! More info can be found at https://git.privacynerd.de/NetSpeaker/NetSpeaker\"}";
|
||||||
|
api_server.sendHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
api_server.send(200, "application/json", message);
|
||||||
|
}); // on /
|
||||||
api_server.onNotFound([]() {
|
api_server.onNotFound([]() {
|
||||||
Serial.println("[HTTP] [API] 404: Not Found");
|
Serial.println("[HTTP] [API] 404: Not Found");
|
||||||
|
api_server.sendHeader("Access-Control-Allow-Origin", "*");
|
||||||
api_server.send(404, "application/json", "{\"code\": 404, \"message\": \"Resource not found.\"}");
|
api_server.send(404, "application/json", "{\"code\": 404, \"message\": \"Resource not found.\"}");
|
||||||
});
|
}); // on NotFound (the fallback if nothing else configured for the requested URL)
|
||||||
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("/api/v1/playlist/get", HTTP_GET, api_v1_playlist_get);
|
api_server.on("/api/v1/playlist/get", api_v1_playlist_get);
|
||||||
api_server.on("/api/v1/playlist/create", HTTP_POST, api_v1_playlist_create);
|
api_server.on("/api/v1/playlist/create", api_v1_playlist_create);
|
||||||
api_server.on("/api/v1/playlist/play", HTTP_POST, api_v1_playlist_play);
|
api_server.on("/api/v1/playlist/play", api_v1_playlist_play);
|
||||||
api_server.on(UriBraces("/api/v1/volume/{}"), HTTP_GET, api_v1_playback_volume);
|
api_server.on(UriBraces("/api/v1/volume/{}"), api_v1_volume);
|
||||||
api_server.on("/api/v1/settings/restart", HTTP_GET, api_v1_settings_restart);
|
api_server.on(UriBraces("/api/v1/balance/{}"), api_v1_balance);
|
||||||
|
api_server.on("/api/v1/eq/get", api_v1_eq_get);
|
||||||
|
api_server.on("/api/v1/eq/reset", api_v1_eq_reset);
|
||||||
|
api_server.on(UriBraces("/api/v1/eq/low/{}"), api_v1_eq_low);
|
||||||
|
api_server.on(UriBraces("/api/v1/eq/mid/{}"), api_v1_eq_mid);
|
||||||
|
api_server.on(UriBraces("/api/v1/eq/high/{}"), api_v1_eq_high);
|
||||||
|
api_server.on("/api/v1/system/restart", api_v1_system_restart);
|
||||||
|
api_server.on("/api/v1/system/name", api_v1_system_friendlyname_get);
|
||||||
|
api_server.on("/api/v1/system/name/change", api_v1_system_friendlyname_change);
|
||||||
|
api_server.on("/api/v1/system/network_name", api_v1_system_networkname_get);
|
||||||
|
api_server.on("/api/v1/system/network_name/change", api_v1_system_networkname_change);
|
||||||
|
api_server.on(UriBraces("/api/v1/system/restore_state/{}"), api_v1_system_restore_state);
|
||||||
|
api_server.on(UriBraces("/api/v1/system/restore_playing/{}"), api_v1_system_restore_playing);
|
||||||
|
api_server.on(UriBraces("/api/v1/system/tell_address/{}"), api_v1_system_tell_address);
|
||||||
|
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);
|
||||||
|
api_server.on("/api/v1/system/wifi/get_ap_creds", api_v1_system_wifi_get_ap_creds);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
@@ -1,42 +1,43 @@
|
|||||||
/*
|
/*
|
||||||
This is free and unencumbered software released into the public domain.
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in
|
||||||
|
source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and
|
||||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
all copyright interest in the software to the public domain. We make this dedication for the benefit of
|
||||||
|
the public at large and to the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org/>
|
For more information, please refer to <http://unlicense.org/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
void setupWiFi() {
|
void setupWiFi() {
|
||||||
if(WiFi.status() == WL_CONNECTED) { return; } // return if not connected
|
if(WiFi.status() == WL_CONNECTED) { return; } // return if not connected
|
||||||
switch(currentWiFiMode) {
|
switch(currentWiFiMode) {
|
||||||
case 0: {
|
case 0: {
|
||||||
Serial.println("[WiFi] Connecting to WiFi...");
|
Serial.println(F("[WiFi] Connecting to WiFi..."));
|
||||||
String WiFiSSIDstr = getWiFiSSID();
|
String WiFiSSID = configuration.getString(PREFERENCES_KEY_WIFI_SSID);
|
||||||
String WiFiPSKstr = getWiFiPSK();
|
String WiFiPSK = configuration.getString(PREFERENCES_KEY_WIFI_PSK);
|
||||||
if(WiFiPSKstr == String() || WiFiSSIDstr == String()) {
|
if(WiFiPSK == String() || WiFiSSID == String()) {
|
||||||
currentWiFiMode = 1;
|
currentWiFiMode = 1;
|
||||||
Serial.printf("[WiFi] No WiFi configured\n");
|
Serial.printf("[WiFi] No WiFi configured\n");
|
||||||
setupWiFi(); // recursive call to reach the 'case 1' statement
|
setupWiFi(); // recursive call to reach the 'case 1' statement
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
char WiFiSSID[33];
|
|
||||||
char WiFiPSK[64];
|
|
||||||
|
|
||||||
// copy to char arrays as the WiFi library needs these (maximum length 32/63 chars)
|
//Serial.printf("[WiFi] Credentials: SSID '%s' | PSK '%s'\n", WiFiSSID.c_str(), WiFiPSK.c_str()); // kept here for debugging wifi problems
|
||||||
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; }
|
|
||||||
|
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
WiFi.mode(WIFI_AP_STA);
|
WiFi.mode(WIFI_AP_STA);
|
||||||
WiFi.begin(WiFiSSID, WiFiPSK);
|
WiFi.begin(WiFiSSID.c_str(), WiFiPSK.c_str());
|
||||||
// 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);
|
||||||
@@ -53,9 +54,23 @@ void setupWiFi() {
|
|||||||
WiFi.mode(WIFI_AP);
|
WiFi.mode(WIFI_AP);
|
||||||
WiFi.softAP(apSSID, apPSK);
|
WiFi.softAP(apSSID, apPSK);
|
||||||
apON = true;
|
apON = true;
|
||||||
Serial.printf("[WiFi] Opened AP successfully\n");
|
Serial.println(F("[WiFi] Opened AP successfully"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setupMDNS() {
|
||||||
|
MDNS.end();
|
||||||
|
// start the mDNS responder
|
||||||
|
Serial.println(F("[SETUP] Starting mDNS responder"));
|
||||||
|
if (!MDNS.begin(configuration.getString(PREFERENCES_KEY_NETWORK_NAME, default_network_name))) { // get network name from EEPROM (default to default_network_name)
|
||||||
|
Serial.println(F("[SETUP] Error setting up mDNS responder!"));
|
||||||
|
indicate_system_error(); // halt system and blink readyPin
|
||||||
|
}
|
||||||
|
Serial.println(F("[SETUP] Started mDNS responder"));
|
||||||
|
|
||||||
|
// Add service to MDNS-SD
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
}
|
Reference in New Issue
Block a user