From f4a2a95e5fc0f27c53715acc6c70d0a58afee9b3 Mon Sep 17 00:00:00 2001 From: Torsten Stauder Date: Sun, 20 Dec 2020 17:12:05 +0100 Subject: [PATCH] Improved localization + FTP is disabled after boot as per default --- README.md | 11 +++- src/logmessages.h | 6 ++- src/logmessages_EN.h | 6 ++- src/main.cpp | 126 +++++++++++++++++++++++++++---------------- src/settings.h | 2 +- 5 files changed, 97 insertions(+), 54 deletions(-) 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