diff --git a/README.md b/README.md
index 5ff99f2..f44595c 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Finally, the long announced Tonuino-PCB for Wemos' Lolin32 is [there](https://gi
* 01.11.2020: Added directive `SD_NOT_MANDATORY_ENABLE`: for debugging puposes SD can be bypassed at boot.
* 17.11.2020: Introduced a distinct volume for headphone for optional headphone-PCB.
* 20.11.2020: Added directive `MEASURE_BATTERY_VOLTAGE`: monitoring battery's voltage is now supported.
-* 25.11.2020: WiFi can npw be activated/deactivated instantly by pressing two buttons.
+* 25.11.2020: WiFi can now be enabled/disabled instantly by pressing two buttons.
* 28.11.2020: Battery's voltage can now be visualized by Neopixel by short-press of rotary encoder's burtton.
* 28.11.2020: Added directive `PLAY_LAST_RFID_AFTER_REBOOT`: Tonuino will recall the last RFID played after reboot.
* 05.12.2020: Added filebrowser to webgui (thanks @mariolukas for contribution!)
@@ -22,6 +22,7 @@ Finally, the long announced Tonuino-PCB for Wemos' Lolin32 is [there](https://gi
* 11.12.2020: Revised GUI-design (thanks @mariolukas for contribution!) + (untested) PCB added for Wemos Lolin D32 + gerberfiles for headphone-PCB
* 18.12.2020: Added SD-MMC 1 Bit-mode (`SD_MMC_1BIT_MODE`). This mode needs one GPIO less and provides almost doubled speed (compared to SPI) for FTP-transfers (thanks @tueddy for contribution!)
* 18.12.2020: Added support for RFID-reader PN5180 (`RFID_READER_TYPE_PN5180`). PN5180 has better RFID-range/sensitivity and can read ISO-15693 / iCode SLIX2-tags aka 'Tonies' (thanks @tueddy for contribution!)
+* 20.12.2020: Due to memory-issues with webstreams, FTP needs to be activated by pressing pause+next-button now
More to come...
## Known bugs
@@ -355,7 +356,13 @@ After having Tonuino running on your ESP32 in your local WiFi, the webinterface-
* General-configuration (volume (speaker + headphone), neopixel-brightness (night-mode + initial), sleep after inactivity)
### FTP (optional)
-In order to avoid exposing uSD-card or disassembling the Tonuino all the time for adding new music, it's possible to transfer music onto the uSD-card using FTP. Please make sure to set the max. number of parallel connections to ONE in your FTP-client. My recommendation is [Filezilla](https://filezilla-project.org/). But don't expect fast data-transfer. Initially it was around 145 kB/s but after modifying ftp-server-lib (changing from 4 kB static-buffer to 16 kB heap-buffer) I saw rates improving to around 185 kB/s. Please note: if music is played in parallel, this rate decrases dramatically! So better stop playback when doing a FTP-transfer. However, playback sounds normal if a FTP-upload is performed in parallel. Default-user and password are set to `esp32` / `esp32` but can be changed later via GUI.
+* In order to avoid exposing uSD-card or disassembling Tonuino all the time for adding new music, it's possible to transfer music to the uSD-card using FTP.
+* Default-user and password are set to `esp32` / `esp32` but can be changed later via GUI.
+* FTP needs to be activated after boot by pressing `PAUSE` + `NEXT`-buttons (in parallel) first! Neopixel flashes green (1x) if enabling was successful.
+* Make sure to set the max. number of parallel connections to ONE in your FTP-client.
+* Software: my recommendation is [Filezilla](https://filezilla-project.org/).
+* Don't expect a super fast data-transfer; it's around 180 kB/s (SPI-mode) and >=300 kB/s (MMC-mode).
+* Please note: if music is played in parallel, this rate decrases dramatically! So better stop playback when doing a FTP-transfer.
### Files / ID3-tags (IMPORTANT!)
Make sure to not use filenames that contain German 'Umlaute'. I've been told this is also true for mp3's ID3-tags. Also better remove coverarts from the files.
diff --git a/src/logmessages.h b/src/logmessages.h
index 06da04f..5962993 100644
--- a/src/logmessages.h
+++ b/src/logmessages.h
@@ -18,7 +18,7 @@ static const char freeMemory[] PROGMEM = "Freier Speicher";
static const char writeEntryToNvs[] PROGMEM = "Schreibe Eintrag in NVS";
static const char freeMemoryAfterFree[] PROGMEM = "Freier Speicher nach Aufräumen";
static const char releaseMemoryOfOldPlaylist[] PROGMEM = "Gebe Speicher der alten Playlist frei.";
-static const char dirOrFileDoesNotExist[] PROGMEM = "Datei oder Verzeichnis existiert nicht!";
+static const char dirOrFileDoesNotExist[] PROGMEM = "Datei oder Verzeichnis existiert nicht ";
static const char unableToAllocateMemForPlaylist[] PROGMEM = "Speicher für Playlist konnte nicht allokiert werden!";
static const char unableToAllocateMem[] PROGMEM = "Speicher konnte nicht allokiert werden!";
static const char fileModeDetected[] PROGMEM = "Dateimodus erkannt.";
@@ -163,4 +163,6 @@ static const char notADirectory[] PROGMEM = "Kein Verzeichnis";
static const char sdMountedMmc1BitMode[] PROGMEM = "Versuche SD-Karte wird im SD_MMC-Modus (1 Bit) zu mounten...";
static const char sdMountedSpiMode[] PROGMEM = "Versuche SD-Karte wird im SPI-Modus zu mounten...";
static const char backupRecoveryWebsite[] PROGMEM = "
Das Backup-File wird eingespielt...
Zur letzten Seite zurückkehren.
";
-static const char restartWebsite[] PROGMEM = "Der Tonuino wird neu gestartet...
Zur letzten Seite zurückkehren.
";
\ No newline at end of file
+static const char restartWebsite[] PROGMEM = "Der Tonuino wird neu gestartet...
Zur letzten Seite zurückkehren.
";
+static const char mqttMsgReceived[] PROGMEM = "MQTT-Nachricht empfangen";
+static const char trackPausedAtPos[] PROGMEM = "Titel pausiert bei Position";
diff --git a/src/logmessages_EN.h b/src/logmessages_EN.h
index 65bc64f..86258c8 100644
--- a/src/logmessages_EN.h
+++ b/src/logmessages_EN.h
@@ -18,7 +18,7 @@ static const char freeMemory[] PROGMEM = "Free memory";
static const char writeEntryToNvs[] PROGMEM = "Storing data to NVS";
static const char freeMemoryAfterFree[] PROGMEM = "Free memory after cleaning";
static const char releaseMemoryOfOldPlaylist[] PROGMEM = "Releasing memory of old playlist.";
-static const char dirOrFileDoesNotExist[] PROGMEM = "File of directory does not exist!";
+static const char dirOrFileDoesNotExist[] PROGMEM = "File of directory does not exist";
static const char unableToAllocateMemForPlaylist[] PROGMEM = "Unable to allocate memory for playlist!";
static const char unableToAllocateMem[] PROGMEM = "Unable to allocate memory!";
static const char fileModeDetected[] PROGMEM = "File-mode detected.";
@@ -163,4 +163,6 @@ static const char notADirectory[] PROGMEM = "Not a directory";
static const char sdMountedMmc1Bit[] PROGMEM = "SD-card in SD_MMC 1 Bit-mode configured...";
static const char sdMountedSpiMode[] PROGMEM = "SD card mounted in SPI-mode configured...";
static const char backupRecoveryWebsite[] PROGMEM = "Backup-file is being applied...
Back to last page.
";
-static const char restartWebsite[] PROGMEM = "Tonuino is being restarted...
Back to last page.
";
\ No newline at end of file
+static const char restartWebsite[] PROGMEM = "Tonuino is being restarted...
Back to last page.
";
+static const char mqttMsgReceived[] PROGMEM = "MQTT-message received";
+static const char trackPausedAtPos[] PROGMEM = "Track paused at position";
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 55dcf37..0b3dda4 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -269,6 +269,8 @@ TaskHandle_t rfid;
// FTP
#ifdef FTP_ENABLE
FtpServer ftpSrv;
+ bool ftpEnableLastStatus = false;
+ bool ftpEnableCurrentStatus = false;
#endif
// Info: SSID / password are stored in NVS
@@ -420,29 +422,20 @@ void IRAM_ATTR onTimer() {
}
#endif
-/**
- * Creates a new file on the SD Card.
- * @param fs
- * @param path
- * @param message
- */
+// Creates a new file on the SD-card.
void createFile(fs::FS &fs, const char * path, const char * message) {
- //snprintf(logBuf, serialLoglength, "Writing file: %s\n", path);
snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(writingFile), path);
loggerNl(logBuf, LOGLEVEL_DEBUG);
File file = fs.open(path, FILE_WRITE);
if (!file) {
snprintf(logBuf, serialLoglength, "%s", (char *) FPSTR(failedOpenFileForWrite));
- //snprintf(logBuf, serialLoglength, "Failed to open file for writing");
loggerNl(logBuf, LOGLEVEL_ERROR);
return;
}
if (file.print(message)) {
- //snprintf(logBuf, serialLoglength, "File written");
snprintf(logBuf, serialLoglength, "%s", (char *) FPSTR(fileWritten));
loggerNl(logBuf, LOGLEVEL_DEBUG);
} else {
- //snprintf(logBuf, serialLoglength, "Write failed");
snprintf(logBuf, serialLoglength, "%s", (char *) FPSTR(writeFailed));
loggerNl(logBuf, LOGLEVEL_ERROR);
}
@@ -454,14 +447,7 @@ bool fileExists(fs::FS &fs, const char *file) {
return fs.exists(file);
}
-/**
- * Appends raw input to a file
- * @param fs
- * @param path
- * @param text
- */
-
-
+// Appends raw input to a file
void appendToFile(fs::FS &fs, const char *path, const char *text) {
File file = fs.open(path, FILE_APPEND);
esp_task_wdt_reset();
@@ -513,7 +499,7 @@ void appendNodeToJSONFile(fs::FS &fs, const char * path, const char *filename, c
}
}
-// Checks if a path is valid. (e.g. hidden path is not valid)
+// Checks if a path is valid. (e.g. hidden path is not valid)
bool pathValid(const char *_fileItem) {
const char ch = '/';
char *subst;
@@ -688,6 +674,19 @@ void doButtonActions(void) {
return;
}
+ // FTP-enable
+ #ifdef FTP_ENABLE
+ if (!ftpEnableLastStatus && !ftpEnableCurrentStatus) {
+ if (buttons[0].isPressed && buttons[2].isPressed) {
+ ftpEnableLastStatus = true;
+ #ifdef NEOPIXEL_ENABLE
+ showLedOk = true;
+ #endif
+ }
+ }
+ return;
+ #endif
+
for (uint8_t i=0; i < sizeof(buttons) / sizeof(buttons[0]); i++) {
if (buttons[i].isPressed) {
if (buttons[i].lastReleasedTimestamp > buttons[i].lastPressedTimestamp) {
@@ -884,7 +883,7 @@ void callback(const char *topic, const byte *payload, uint32_t length) {
char *receivedString = strndup((char*)payload, length);
char *mqttTopic = strdup(topic);
- snprintf(logBuf, serialLoglength, "MQTT-Nachricht empfangen: [Topic: %s] [Kommando: %s]", mqttTopic, receivedString);
+ snprintf(logBuf, serialLoglength, "%s: [Topic: %s] [Command: %s]", (char *) FPSTR(mqttMsgReceived), mqttTopic, receivedString);
loggerNl(logBuf, LOGLEVEL_INFO);
// Go to sleep?
@@ -1324,7 +1323,7 @@ char ** returnPlaylistFromSD(File _fileOrDirectory) {
snprintf(logBuf, serialLoglength, "%s: %d", (char *) FPSTR(numberOfValidFiles), cnt);
loggerNl(logBuf, LOGLEVEL_NOTICE);
- return ++files; // return ptr+1 (starting at 1st payload-item)
+ return ++files; // return ptr+1 (starting at 1st payload-item); ptr+0 contains number of items
}
@@ -1352,7 +1351,11 @@ size_t nvsRfidWriteWrapper (const char *_rfidCardId, const char *_track, const u
}
snprintf(prefBuf, sizeof(prefBuf) / sizeof(prefBuf[0]), "%s%s%s%u%s%d%s%u", stringDelimiter, trackBuf, stringDelimiter, _playPosition, stringDelimiter, _playMode, stringDelimiter, _trackLastPlayed);
- snprintf(logBuf, serialLoglength, "Schreibe '%s' in NVS für RFID-Card-ID %s mit playmode %d und letzter Track %u\n", prefBuf, _rfidCardId, _playMode, _trackLastPlayed);
+ #if (LANGUAGE == 1)
+ snprintf(logBuf, serialLoglength, "Schreibe '%s' in NVS für RFID-Card-ID %s mit playmode %d und letzter Track %u\n", prefBuf, _rfidCardId, _playMode, _trackLastPlayed);
+ #else
+ snprintf(logBuf, serialLoglength, "Write '%s' to NVS for RFID-Card-ID %s with playmode %d and last track %u\n", prefBuf, _rfidCardId, _playMode, _trackLastPlayed);
+ #endif
logger(logBuf, LOGLEVEL_INFO);
loggerNl(prefBuf, LOGLEVEL_INFO);
#ifdef NEOPIXEL_ENABLE
@@ -1394,7 +1397,11 @@ void playAudio(void *parameter) {
playProperties.pausePlay = !playProperties.pausePlay;
}
audio.stopSong();
- snprintf(logBuf, serialLoglength, "%s mit %d Titel(n)", (char *) FPSTR(newPlaylistReceived), playProperties.numberOfTracks);
+ #if (LANGUAGE == 1)
+ snprintf(logBuf, serialLoglength, "%s mit %d Titel(n)", (char *) FPSTR(newPlaylistReceived), playProperties.numberOfTracks);
+ #else
+ snprintf(logBuf, serialLoglength, "%s with %d track(s)", (char *) FPSTR(newPlaylistReceived), playProperties.numberOfTracks);
+ #endif
loggerNl(logBuf, LOGLEVEL_NOTICE);
Serial.print(F("Free heap: "));
Serial.println(ESP.getFreeHeap());
@@ -1454,7 +1461,7 @@ void playAudio(void *parameter) {
trackCommand = 0;
loggerNl((char *) FPSTR(cmndPause), LOGLEVEL_INFO);
if (playProperties.saveLastPlayPosition && !playProperties.pausePlay) {
- snprintf(logBuf, serialLoglength, "Titel wurde bei Position %u pausiert.", audio.getFilePos());
+ snprintf(logBuf, serialLoglength, "%s: %u", FPSTR(trackPausedAtPos), audio.getFilePos());
loggerNl(logBuf, LOGLEVEL_INFO);
nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), audio.getFilePos(), playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks);
}
@@ -1625,7 +1632,11 @@ void playAudio(void *parameter) {
nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + 0), 0, playProperties.playMode, 0, playProperties.numberOfTracks);
}
#ifdef MQTT_ENABLE
- publishMqtt((char *) FPSTR(topicTrackState), "", false);
+ #if (LANGUAGE == 1)
+ publishMqtt((char *) FPSTR(topicTrackState), "", false);
+ #else
+ publishMqtt((char *) FPSTR(topicTrackState), "", false);
+ #endif
#endif
playProperties.playlistFinished = true;
playProperties.playMode = NO_PLAYLIST;
@@ -1659,7 +1670,7 @@ void playAudio(void *parameter) {
} else {
// Files from SD
if (!FSystem.exists(*(playProperties.playlist + playProperties.currentTrackNumber))) { // Check first if file/folder exists
- snprintf(logBuf, serialLoglength, "Datei/Ordner '%s' existiert nicht", *(playProperties.playlist + playProperties.currentTrackNumber));
+ snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(dirOrFileDoesNotExist), *(playProperties.playlist + playProperties.currentTrackNumber));
loggerNl(logBuf, LOGLEVEL_ERROR);
playProperties.trackFinished = true;
continue;
@@ -1678,7 +1689,11 @@ void playAudio(void *parameter) {
#ifdef MQTT_ENABLE
publishMqtt((char *) FPSTR(topicTrackState), buf, false);
#endif
- snprintf(logBuf, serialLoglength, "'%s' wird abgespielt (%d von %d)", *(playProperties.playlist + playProperties.currentTrackNumber), (playProperties.currentTrackNumber+1) , playProperties.numberOfTracks);
+ #if (LANGUAGE == 1)
+ snprintf(logBuf, serialLoglength, "'%s' wird abgespielt (%d von %d)", *(playProperties.playlist + playProperties.currentTrackNumber), (playProperties.currentTrackNumber+1) , playProperties.numberOfTracks);
+ #else
+ snprintf(logBuf, serialLoglength, "'%s' is being played (%d of %d)", *(playProperties.playlist + playProperties.currentTrackNumber), (playProperties.currentTrackNumber+1) , playProperties.numberOfTracks);
+ #endif
loggerNl(logBuf, LOGLEVEL_NOTICE);
playProperties.playlistFinished = false;
}
@@ -1931,12 +1946,6 @@ void showLed(void *parameter) {
continue;
}
#endif
- /*#ifdef FTP_ENABLE
- if (ftpSrv.isConnected()) { // Workaround: after moving Neopixel's task to 2nd cpu-core, FTP-transfer-rate decreased. By disabling Neopixel-animation, this can be rescued a bit
- vTaskDelay(portTICK_RATE_MS*100);
- continue;
- }
- #endif*/
if (!bootComplete) { // Rotates orange unless boot isn't complete
FastLED.clear();
for (uint8_t led = 0; led < NUM_LEDS; led++) {
@@ -3009,7 +3018,11 @@ void accessPointStart(const char *SSID, IPAddress ip, IPAddress netmask) {
});
wServer.on("/restart", HTTP_GET, [] (AsyncWebServerRequest *request) {
- request->send(200, "text/html", "ESP wird neu gestartet...");
+ #if (LANGUAGE == 1)
+ request->send(200, "text/html", "ESP wird neu gestartet...");
+ #else
+ request->send(200, "text/html", "ESP is being restarted...");
+ #endif
Serial.flush();
ESP.restart();
});
@@ -3061,6 +3074,16 @@ bool writeWifiStatusToNVS(bool wifiStatus) {
}
+#ifdef FTP_ENABLE
+ void ftpManager(void) {
+ if (ftpEnableLastStatus && !ftpEnableCurrentStatus) {
+ ftpEnableCurrentStatus = true;
+ ftpSrv.begin(FSystem, ftpUser, ftpPassword);
+ Serial.println("FTP aktiviert");
+ }
+ }
+#endif
+
// Provides management for WiFi
wl_status_t wifiManager(void) {
// If wifi whould not be activated, return instantly
@@ -3107,11 +3130,12 @@ wl_status_t wifiManager(void) {
if (WiFi.status() == WL_CONNECTED) {
myIP = WiFi.localIP();
- snprintf(logBuf, serialLoglength, "Aktuelle IP: %d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
- loggerNl(logBuf, LOGLEVEL_NOTICE);
- #ifdef FTP_ENABLE
- ftpSrv.begin(FSystem, ftpUser, ftpPassword);
+ #if (LANGUAGE == 1)
+ snprintf(logBuf, serialLoglength, "Aktuelle IP: %d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
+ #else
+ snprintf(logBuf, serialLoglength, "Current IP: %d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
#endif
+ loggerNl(logBuf, LOGLEVEL_NOTICE);
} else { // Starts AP if WiFi-connect wasn't successful
accessPointStart((char *) FPSTR(accessPointNetworkSSID), apIP, apNetmask);
}
@@ -3200,7 +3224,11 @@ bool processJsonRequest(char *_serialJson) {
JsonObject object = doc.as();
if (error) {
- Serial.print(F("deserializeJson() failed: "));
+ #if (LANGUAGE == 1)
+ Serial.print(F("deserializeJson() fehlgeschlagen: "));
+ #else
+ Serial.print(F("deserializeJson() failed: "));
+ #endif
Serial.println(error.c_str());
return false;
}
@@ -3664,7 +3692,6 @@ void setup() {
NULL, /* Task input parameter */
1 | portPRIVILEGE_BIT, /* Priority of the task */
&LED, /* Task handle. */
-// 1 /* Core where the task should run */
0 /* Core where the task should run */
);
#endif
@@ -3687,10 +3714,10 @@ void setup() {
#endif
#ifndef SINGLE_SPI_ENABLE
- #ifdef SD_MMC_1BIT_MODE
- while (!SD_MMC.begin("/sdcard", true)) {
+ #ifdef SD_MMC_1BIT_MODE
+ while (!SD_MMC.begin("/sdcard", true)) {
#else
- while (!SD.begin(SPISD_CS, spiSD)) {
+ while (!SD.begin(SPISD_CS, spiSD)) {
#endif
#else
while (!SD.begin(SPISD_CS)) {
@@ -3712,7 +3739,7 @@ void setup() {
Serial.println(F("|_ _|___ ___| | | | | | | "));
Serial.println(F(" | | | . | | | |- -| | | | | | "));
Serial.println(F(" |_| |___|_|_|_____|_____|_|___|_____| "));
- Serial.println(F(" ESP-32 version"));
+ Serial.println(F(" ESP32-version"));
Serial.println(F(""));
// show SD card type
@@ -4029,6 +4056,7 @@ void setup() {
void loop() {
webserverStart();
+ ftpManager();
#ifdef HEADPHONE_ADJUST_ENABLE
headphoneVolumeManager();
#endif
@@ -4050,12 +4078,16 @@ void loop() {
}
#endif
#ifdef FTP_ENABLE
- ftpSrv.handleFTP();
+ if (ftpEnableLastStatus && ftpEnableCurrentStatus) {
+ ftpSrv.handleFTP();
+ }
#endif
}
#ifdef FTP_ENABLE
- if (ftpSrv.isConnected()) {
- lastTimeActiveTimestamp = millis(); // Re-adjust timer while client is connected to avoid ESP falling asleep
+ if (ftpEnableLastStatus && ftpEnableCurrentStatus) {
+ if (ftpSrv.isConnected()) {
+ lastTimeActiveTimestamp = millis(); // Re-adjust timer while client is connected to avoid ESP falling asleep
+ }
}
#endif
#ifdef PLAY_LAST_RFID_AFTER_REBOOT
diff --git a/src/settings.h b/src/settings.h
index 51e0e0e..3cf6377 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -3,7 +3,7 @@
//########################## MODULES #################################
#define MDNS_ENABLE // When enabled, you don't have to handle with Tonuino's IP-address. If hostname is set to "tonuino", you can reach it via tonuino.local
#define MQTT_ENABLE // Make sure to configure mqtt-server and (optionally) username+pwd
-#define FTP_ENABLE // Enables FTP-server
+#define FTP_ENABLE // Enables FTP-server; DON'T FORGET TO ACTIVATE AFTER BOOT BY PRESSING PAUSE + NEXT-BUTTONS (IN PARALLEL)!
#define NEOPIXEL_ENABLE // Don't forget configuration of NUM_LEDS if enabled
#define NEOPIXEL_REVERSE_ROTATION // Some Neopixels are adressed/soldered counter-clockwise. This can be configured here.
#define LANGUAGE 1 // 1 = deutsch; 2 = english