diff --git a/Hardware-Plaforms/ESP32-A1S-Audiokit/README.md b/Hardware-Plaforms/ESP32-A1S-Audiokit/README.md index 2cec25f..73b4623 100644 --- a/Hardware-Plaforms/ESP32-A1S-Audiokit/README.md +++ b/Hardware-Plaforms/ESP32-A1S-Audiokit/README.md @@ -2,7 +2,7 @@ * As there's a lack of GPIOs, it's necessary to share a single SPI-instance by SD and RFID. * The board provides 6 keys but due to lack of free GPIOs, we need them for other purposes. Additionaly the problem is, that all keys are equipped with capacitors (maybe to debounce) which makes it hard to use those GPIOs for other purposes. That's why I unsoldered R66, 67, 68, 69, 70 (all 0 Ohms) to free these GPIOs from the capacitors. * Please note: key1 still works but if you additionaly want to use keys2-6, you can use GPIO36 along with analogRead() by using voltage-dividers. But first one has to calculate + solder resistor-pairs 56/61, 57/62, 58/63, 59/64 to 'build' those voltage-dividers. Without doing that online Key1 is usable as it doesn't need resistors. However, didn't solder/test dividers so far. -* When switching over to use analogRead() one has to modify buttonHandler() in my code. +* When switching over to use analogRead() one has to modify Button_Cyclic() in my code. * Additionaly I unsoldered resistor R14 in order to deactivate LED D4 (probably not necessary) ## GPIOs (outdated! Best have a look at settings-espa1s.h) diff --git a/platformio.ini b/platformio.ini index 9d94376..50b8a13 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,6 +25,7 @@ lib_deps_external = https://github.com/Arduino-IRremote/Arduino-IRremote.git https://github.com/kkloesener/MFRC522_I2C.git https://github.com/miguelbalboa/rfid.git + https://github.com/tuniii/LogRingBuffer.git [env:common] platform = espressif32 diff --git a/src/AudioPlayer.cpp b/src/AudioPlayer.cpp new file mode 100644 index 0000000..f8fa029 --- /dev/null +++ b/src/AudioPlayer.cpp @@ -0,0 +1,1141 @@ +#include +#include +#include +#include "settings.h" +#include "Audio.h" +#include "AudioPlayer.h" +#include "Common.h" +#include "Led.h" +#include "Log.h" +#include "MemX.h" +#include "Mqtt.h" +#include "Port.h" +#include "Queues.h" +#include "Rfid.h" +#include "RotaryEncoder.h" +#include "SdCard.h" +#include "System.h" +#include "Wlan.h" + +#define AUDIOPLAYER_VOLUME_MAX 21u +#define AUDIOPLAYER_VOLUME_MIN 0u +#define AUDIOPLAYER_VOLUME_INIT 3u + +playProps gPlayProperties; + +// Volume +static uint8_t AudioPlayer_CurrentVolume = AUDIOPLAYER_VOLUME_INIT; +static uint8_t AudioPlayer_MaxVolume = AUDIOPLAYER_VOLUME_MAX; +static uint8_t AudioPlayer_MaxVolumeSpeaker = AUDIOPLAYER_VOLUME_MAX; +static uint8_t AudioPlayer_MinVolume = AUDIOPLAYER_VOLUME_MIN; +static uint8_t AudioPlayer_InitVolume = AUDIOPLAYER_VOLUME_INIT; + +#ifdef HEADPHONE_ADJUST_ENABLE +static bool AudioPlayer_HeadphoneLastDetectionState; +static uint32_t AudioPlayer_HeadphoneLastDetectionTimestamp = 0u; +static uint8_t AudioPlayer_MaxVolumeHeadphone = 11u; // Maximum volume that can be adjusted in headphone-mode (default; can be changed later via GUI) +#endif + +static void AudioPlayer_Task(void *parameter); +static void AudioPlayer_HeadphoneVolumeManager(void); +static char **AudioPlayer_ReturnPlaylistFromWebstream(const char *_webUrl); +static int AudioPlayer_ArrSortHelper(const void *a, const void *b); +static void AudioPlayer_SortPlaylist(const char **arr, int n); +static void AudioPlayer_SortPlaylist(char *str[], const uint32_t count); +static size_t AudioPlayer_NvsRfidWriteWrapper(const char *_rfidCardId, const char *_track, const uint32_t _playPosition, const uint8_t _playMode, const uint16_t _trackLastPlayed, const uint16_t _numberOfTracks); + +void AudioPlayer_Init(void) +{ +#ifndef USE_LAST_VOLUME_AFTER_REBOOT + // Get initial volume from NVS + uint32_t nvsInitialVolume = gPrefsSettings.getUInt("initVolume", 0); +#else + // Get volume used at last shutdown + uint32_t nvsInitialVolume = gPrefsSettings.getUInt("previousVolume", 999); + if (nvsInitialVolume == 999) + { + gPrefsSettings.putUInt("previousVolume", AudioPlayer_GetInitVolume()); + nvsInitialVolume = AudioPlayer_GetInitVolume(); + } + else + { + Log_Println((char *)FPSTR(rememberLastVolume), LOGLEVEL_ERROR); + } +#endif + if (nvsInitialVolume) + { + AudioPlayer_SetInitVolume(nvsInitialVolume); + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(restoredInitialLoudnessFromNvs), nvsInitialVolume); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putUInt("initVolume", AudioPlayer_GetInitVolume()); + Log_Println((char *)FPSTR(wroteInitialLoudnessToNvs), LOGLEVEL_ERROR); + } + + // Get maximum volume for speaker from NVS + uint32_t nvsMaxVolumeSpeaker = gPrefsSettings.getUInt("maxVolumeSp", 0); + if (nvsMaxVolumeSpeaker) + { + AudioPlayer_SetMaxVolumeSpeaker(nvsMaxVolumeSpeaker); + AudioPlayer_SetMaxVolume(nvsMaxVolumeSpeaker); + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(restoredMaxLoudnessForSpeakerFromNvs), nvsMaxVolumeSpeaker); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putUInt("maxVolumeSp", nvsMaxVolumeSpeaker); + Log_Println((char *)FPSTR(wroteMaxLoudnessForSpeakerToNvs), LOGLEVEL_ERROR); + } + +#ifdef HEADPHONE_ADJUST_ENABLE + pinMode(HP_DETECT, INPUT); + AudioPlayer_HeadphoneLastDetectionState = Port_Read(HP_DETECT); + + // Get maximum volume for headphone from NVS + uint32_t nvsAudioPlayer_MaxVolumeHeadphone = gPrefsSettings.getUInt("maxVolumeHp", 0); + if (nvsAudioPlayer_MaxVolumeHeadphone) + { + AudioPlayer_MaxVolumeHeadphone = nvsAudioPlayer_MaxVolumeHeadphone; + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(restoredMaxLoudnessForHeadphoneFromNvs), nvsAudioPlayer_MaxVolumeHeadphone); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putUInt("maxVolumeHp", nvsAudioPlayer_MaxVolumeHeadphone); + Log_Println((char *)FPSTR(wroteMaxLoudnessForHeadphoneToNvs), LOGLEVEL_ERROR); + } +#endif + // Adjust volume depending on headphone is connected and volume-adjustment is enabled + AudioPlayer_SetupVolume(); + + xTaskCreatePinnedToCore( + AudioPlayer_Task, /* Function to implement the task */ + "mp3play", /* Name of the task */ + 11000, /* Stack size in words */ + NULL, /* Task input parameter */ + 2 | portPRIVILEGE_BIT, /* Priority of the task */ + NULL, /* Task handle. */ + 1 /* Core where the task should run */ + ); +} + +void AudioPlayer_Cyclic(void) +{ + AudioPlayer_HeadphoneVolumeManager(); +} + +uint8_t AudioPlayer_GetCurrentVolume(void) +{ + return AudioPlayer_CurrentVolume; +} + +void AudioPlayer_SetCurrentVolume(uint8_t value) +{ + AudioPlayer_CurrentVolume = value; +} + +uint8_t AudioPlayer_GetMaxVolume(void) +{ + return AudioPlayer_MaxVolume; +} + +void AudioPlayer_SetMaxVolume(uint8_t value) +{ + AudioPlayer_MaxVolume = value; +} + +uint8_t AudioPlayer_GetMaxVolumeSpeaker(void) +{ + return AudioPlayer_MaxVolumeSpeaker; +} + +void AudioPlayer_SetMaxVolumeSpeaker(uint8_t value) +{ + AudioPlayer_MaxVolumeSpeaker = value; +} + +uint8_t AudioPlayer_GetMinVolume(void) +{ + return AudioPlayer_MinVolume; +} + +void AudioPlayer_SetMinVolume(uint8_t value) +{ + AudioPlayer_MinVolume = value; +} + +uint8_t AudioPlayer_GetInitVolume(void) +{ + return AudioPlayer_InitVolume; +} + +void AudioPlayer_SetInitVolume(uint8_t value) +{ + AudioPlayer_InitVolume = value; +} + +// Set maxVolume depending on headphone-adjustment is enabled and headphone is/is not connected +void AudioPlayer_SetupVolume(void) +{ +#ifndef HEADPHONE_ADJUST_ENABLE + AudioPlayer_MaxVolume = AudioPlayer_MaxVolumeSpeaker; + return; +#else + if (Port_Read(HP_DETECT)) + { + AudioPlayer_MaxVolume = AudioPlayer_MaxVolumeSpeaker; // 1 if headphone is not connected +#ifdef PLAY_MONO_SPEAKER + gPlayProperties.newPlayMono = true; +#else + gPlayProperties.newPlayMono = false; +#endif + } + else + { + AudioPlayer_MaxVolume = AudioPlayer_MaxVolumeHeadphone; // 0 if headphone is connected (put to GND) + gPlayProperties.newPlayMono = false; // always stereo for headphones! + } + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(maxVolumeSet), AudioPlayer_MaxVolume); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + return; +#endif +} + +void AudioPlayer_HeadphoneVolumeManager(void) +{ +#ifdef HEADPHONE_ADJUST_ENABLE + bool currentHeadPhoneDetectionState = Port_Read(HP_DETECT); + + if (AudioPlayer_HeadphoneLastDetectionState != currentHeadPhoneDetectionState && (millis() - AudioPlayer_HeadphoneLastDetectionTimestamp >= headphoneLastDetectionDebounce)) + { + if (currentHeadPhoneDetectionState) + { + AudioPlayer_MaxVolume = AudioPlayer_MaxVolumeSpeaker; +#ifdef PLAY_MONO_SPEAKER + gPlayProperties.newPlayMono = true; +#else + gPlayProperties.newPlayMono = false; +#endif + } + else + { + AudioPlayer_MaxVolume = AudioPlayer_MaxVolumeHeadphone; + gPlayProperties.newPlayMono = false; // Always stereo for headphones + if (AudioPlayer_GetCurrentVolume() > AudioPlayer_MaxVolume) + { + AudioPlayer_VolumeToQueueSender(AudioPlayer_MaxVolume, true); // Lower volume for headphone if headphone's maxvolume is exceeded by volume set in speaker-mode + } + } + AudioPlayer_HeadphoneLastDetectionState = currentHeadPhoneDetectionState; + AudioPlayer_HeadphoneLastDetectionTimestamp = millis(); + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(maxVolumeSet), AudioPlayer_MaxVolume); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } +#endif +} + +// Function to play music as task +void AudioPlayer_Task(void *parameter) +{ + static Audio audio; + uint8_t settleCount = 0; + audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); + audio.setVolume(AudioPlayer_GetInitVolume()); + audio.forceMono(gPlayProperties.currentPlayMono); + if (gPlayProperties.currentPlayMono) + { + audio.setTone(3, 0, 0); + } + + uint8_t currentVolume; + static BaseType_t trackQStatus; + static uint8_t trackCommand = 0; + bool audioReturnCode; + + for (;;) + { + if (xQueueReceive(gVolumeQueue, ¤tVolume, 0) == pdPASS) + { + snprintf(Log_Buffer, Log_BufferLength, "%s: %d", (char *)FPSTR(newLoudnessReceivedQueue), currentVolume); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + audio.setVolume(currentVolume); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicLoudnessState), currentVolume, false); +#endif + } + + if (xQueueReceive(gTrackControlQueue, &trackCommand, 0) == pdPASS) + { + snprintf(Log_Buffer, Log_BufferLength, "%s: %d", (char *)FPSTR(newCntrlReceivedQueue), trackCommand); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + + trackQStatus = xQueueReceive(gTrackQueue, &gPlayProperties.playlist, 0); + if (trackQStatus == pdPASS || gPlayProperties.trackFinished || trackCommand != 0) + { + if (trackQStatus == pdPASS) + { + if (gPlayProperties.pausePlay) + { + gPlayProperties.pausePlay = !gPlayProperties.pausePlay; + } + audio.stopSong(); +#if (LANGUAGE == 1) + snprintf(Log_Buffer, Log_BufferLength, "%s mit %d Titel(n)", (char *)FPSTR(newPlaylistReceived), gPlayProperties.numberOfTracks); +#else + snprintf(Log_Buffer, Log_BufferLength, "%s with %d track(s)", (char *)FPSTR(newPlaylistReceived), gPlayProperties.numberOfTracks); +#endif + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + Serial.print(F("Free heap: ")); + Serial.println(ESP.getFreeHeap()); + + // If we're in audiobook-mode and apply a modification-card, we don't + // want to save lastPlayPosition for the mod-card but for the card that holds the playlist + if (gCurrentRfidTagId != NULL) + { + strncpy(gPlayProperties.playRfidTag, gCurrentRfidTagId, sizeof(gPlayProperties.playRfidTag) / sizeof(gPlayProperties.playRfidTag[0])); + } + } + if (gPlayProperties.trackFinished) + { + gPlayProperties.trackFinished = false; + if (gPlayProperties.playMode == NO_PLAYLIST) + { + gPlayProperties.playlistFinished = true; + continue; + } + if (gPlayProperties.saveLastPlayPosition) + { // Don't save for AUDIOBOOK_LOOP because not necessary + if (gPlayProperties.currentTrackNumber + 1 < gPlayProperties.numberOfTracks) + { + // Only save if there's another track, otherwise it will be saved at end of playlist anyway + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), 0, gPlayProperties.playMode, gPlayProperties.currentTrackNumber + 1, gPlayProperties.numberOfTracks); + } + } + if (gPlayProperties.sleepAfterCurrentTrack) + { // Go to sleep if "sleep after track" was requested + System_RequestSleep(); + break; + } + if (!gPlayProperties.repeatCurrentTrack) + { // If endless-loop requested, track-number will not be incremented + gPlayProperties.currentTrackNumber++; + } + else + { + Log_Println((char *)FPSTR(repeatTrackDueToPlaymode), LOGLEVEL_INFO); + Led_Indicate(LedIndicatorType::Rewind); + } + } + + if (gPlayProperties.playlistFinished && trackCommand != 0) + { + Log_Println((char *)FPSTR(noPlaymodeChangeIfIdle), LOGLEVEL_NOTICE); + trackCommand = 0; + System_IndicateError(); + continue; + } + /* Check if track-control was called + (stop, start, next track, prev. track, last track, first track...) */ + switch (trackCommand) + { + case STOP: + audio.stopSong(); + trackCommand = 0; + Log_Println((char *)FPSTR(cmndStop), LOGLEVEL_INFO); + gPlayProperties.pausePlay = true; + gPlayProperties.playlistFinished = true; + gPlayProperties.playMode = NO_PLAYLIST; + continue; + + case PAUSEPLAY: + audio.pauseResume(); + trackCommand = 0; + Log_Println((char *)FPSTR(cmndPause), LOGLEVEL_INFO); + if (gPlayProperties.saveLastPlayPosition && !gPlayProperties.pausePlay) + { + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(trackPausedAtPos), audio.getFilePos()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), audio.getFilePos(), gPlayProperties.playMode, gPlayProperties.currentTrackNumber, gPlayProperties.numberOfTracks); + } + gPlayProperties.pausePlay = !gPlayProperties.pausePlay; + continue; + + case NEXTTRACK: + if (gPlayProperties.pausePlay) + { + audio.pauseResume(); + gPlayProperties.pausePlay = !gPlayProperties.pausePlay; + } + if (gPlayProperties.repeatCurrentTrack) + { // End loop if button was pressed + gPlayProperties.repeatCurrentTrack = !gPlayProperties.repeatCurrentTrack; + char rBuf[2]; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); +#endif + } + if (gPlayProperties.currentTrackNumber + 1 < gPlayProperties.numberOfTracks) + { + gPlayProperties.currentTrackNumber++; + if (gPlayProperties.saveLastPlayPosition) + { + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), 0, gPlayProperties.playMode, gPlayProperties.currentTrackNumber, gPlayProperties.numberOfTracks); + Log_Println((char *)FPSTR(trackStartAudiobook), LOGLEVEL_INFO); + } + Log_Println((char *)FPSTR(cmndNextTrack), LOGLEVEL_INFO); + if (!gPlayProperties.playlistFinished) + { + audio.stopSong(); + } + } + else + { + Log_Println((char *)FPSTR(lastTrackAlreadyActive), LOGLEVEL_NOTICE); + trackCommand = 0; + System_IndicateError(); + continue; + } + trackCommand = 0; + break; + + case PREVIOUSTRACK: + if (gPlayProperties.pausePlay) + { + audio.pauseResume(); + gPlayProperties.pausePlay = !gPlayProperties.pausePlay; + } + if (gPlayProperties.repeatCurrentTrack) + { // End loop if button was pressed + gPlayProperties.repeatCurrentTrack = !gPlayProperties.repeatCurrentTrack; + char rBuf[2]; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); +#endif + } + if (gPlayProperties.currentTrackNumber > 0) + { + // play previous track when current track time is small, else play current track again + if (audio.getAudioCurrentTime() < 2) + { + gPlayProperties.currentTrackNumber--; + } + if (gPlayProperties.saveLastPlayPosition) + { + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), 0, gPlayProperties.playMode, gPlayProperties.currentTrackNumber, gPlayProperties.numberOfTracks); + Log_Println((char *)FPSTR(trackStartAudiobook), LOGLEVEL_INFO); + } + + Log_Println((char *)FPSTR(cmndPrevTrack), LOGLEVEL_INFO); + if (!gPlayProperties.playlistFinished) + { + audio.stopSong(); + } + } + else + { + if (gPlayProperties.playMode == WEBSTREAM) + { + Log_Println((char *)FPSTR(trackChangeWebstream), LOGLEVEL_INFO); + System_IndicateError(); + trackCommand = 0; + continue; + } + if (gPlayProperties.saveLastPlayPosition) + { + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), 0, gPlayProperties.playMode, gPlayProperties.currentTrackNumber, gPlayProperties.numberOfTracks); + } + audio.stopSong(); + Led_Indicate(LedIndicatorType::Rewind); + audioReturnCode = audio.connecttoFS(gFSystem, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); + // consider track as finished, when audio lib call was not successful + if (!audioReturnCode) + { + System_IndicateError(); + gPlayProperties.trackFinished = true; + continue; + } + Log_Println((char *)FPSTR(trackStart), LOGLEVEL_INFO); + trackCommand = 0; + continue; + } + trackCommand = 0; + break; + + case FIRSTTRACK: + if (gPlayProperties.pausePlay) + { + audio.pauseResume(); + gPlayProperties.pausePlay = !gPlayProperties.pausePlay; + } + if (gPlayProperties.currentTrackNumber > 0) + { + gPlayProperties.currentTrackNumber = 0; + if (gPlayProperties.saveLastPlayPosition) + { + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), 0, gPlayProperties.playMode, gPlayProperties.currentTrackNumber, gPlayProperties.numberOfTracks); + Log_Println((char *)FPSTR(trackStartAudiobook), LOGLEVEL_INFO); + } + Log_Println((char *)FPSTR(cmndFirstTrack), LOGLEVEL_INFO); + if (!gPlayProperties.playlistFinished) + { + audio.stopSong(); + } + } + else + { + Log_Println((char *)FPSTR(firstTrackAlreadyActive), LOGLEVEL_NOTICE); + System_IndicateError(); + trackCommand = 0; + continue; + } + trackCommand = 0; + break; + + case LASTTRACK: + if (gPlayProperties.pausePlay) + { + audio.pauseResume(); + gPlayProperties.pausePlay = !gPlayProperties.pausePlay; + } + if (gPlayProperties.currentTrackNumber + 1 < gPlayProperties.numberOfTracks) + { + gPlayProperties.currentTrackNumber = gPlayProperties.numberOfTracks - 1; + if (gPlayProperties.saveLastPlayPosition) + { + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), 0, gPlayProperties.playMode, gPlayProperties.currentTrackNumber, gPlayProperties.numberOfTracks); + Log_Println((char *)FPSTR(trackStartAudiobook), LOGLEVEL_INFO); + } + Log_Println((char *)FPSTR(cmndLastTrack), LOGLEVEL_INFO); + if (!gPlayProperties.playlistFinished) + { + audio.stopSong(); + } + } + else + { + Log_Println((char *)FPSTR(lastTrackAlreadyActive), LOGLEVEL_NOTICE); + System_IndicateError(); + trackCommand = 0; + continue; + } + trackCommand = 0; + break; + + case 0: + break; + + default: + trackCommand = 0; + Log_Println((char *)FPSTR(cmndDoesNotExist), LOGLEVEL_NOTICE); + System_IndicateError(); + continue; + } + + if (gPlayProperties.playUntilTrackNumber == gPlayProperties.currentTrackNumber && gPlayProperties.playUntilTrackNumber > 0) + { + if (gPlayProperties.saveLastPlayPosition) + { + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), 0, gPlayProperties.playMode, 0, gPlayProperties.numberOfTracks); + } + gPlayProperties.playlistFinished = true; + gPlayProperties.playMode = NO_PLAYLIST; + System_RequestSleep(); + continue; + } + + if (gPlayProperties.currentTrackNumber >= gPlayProperties.numberOfTracks) + { // Check if last element of playlist is already reached + Log_Println((char *)FPSTR(endOfPlaylistReached), LOGLEVEL_NOTICE); + if (!gPlayProperties.repeatPlaylist) + { + if (gPlayProperties.saveLastPlayPosition) + { + // Set back to first track + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + 0), 0, gPlayProperties.playMode, 0, gPlayProperties.numberOfTracks); + } +#ifdef MQTT_ENABLE +#if (LANGUAGE == 1) + publishMqtt((char *)FPSTR(topicTrackState), "", false); +#else + publishMqtt((char *)FPSTR(topicTrackState), "", false); +#endif +#endif + gPlayProperties.playlistFinished = true; + gPlayProperties.playMode = NO_PLAYLIST; +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); +#endif + gPlayProperties.currentTrackNumber = 0; + gPlayProperties.numberOfTracks = 0; + if (gPlayProperties.sleepAfterPlaylist) + { + System_RequestSleep(); + } + continue; + } + else + { // Check if sleep after current track/playlist was requested + if (gPlayProperties.sleepAfterPlaylist || gPlayProperties.sleepAfterCurrentTrack) + { + gPlayProperties.playlistFinished = true; + gPlayProperties.playMode = NO_PLAYLIST; + System_RequestSleep(); + continue; + } // Repeat playlist; set current track number back to 0 + Log_Println((char *)FPSTR(repeatPlaylistDueToPlaymode), LOGLEVEL_NOTICE); + gPlayProperties.currentTrackNumber = 0; + if (gPlayProperties.saveLastPlayPosition) + { + AudioPlayer_NvsRfidWriteWrapper(gPlayProperties.playRfidTag, *(gPlayProperties.playlist + 0), 0, gPlayProperties.playMode, gPlayProperties.currentTrackNumber, gPlayProperties.numberOfTracks); + } + } + } + + if (gPlayProperties.playMode == WEBSTREAM) + { // Webstream + audio.connecttohost(*(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); + gPlayProperties.playlistFinished = false; + } + else + { + // Files from SD + if (!gFSystem.exists(*(gPlayProperties.playlist + gPlayProperties.currentTrackNumber))) + { // Check first if file/folder exists + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(dirOrFileDoesNotExist), *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + gPlayProperties.trackFinished = true; + continue; + } + else + { + audioReturnCode = audio.connecttoFS(gFSystem, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); + // consider track as finished, when audio lib call was not successful + if (!audioReturnCode) + { + System_IndicateError(); + gPlayProperties.trackFinished = true; + continue; + } + Led_Indicate(LedIndicatorType::PlaylistProgress); + if (gPlayProperties.startAtFilePos > 0) + { + audio.setFilePos(gPlayProperties.startAtFilePos); + snprintf(Log_Buffer, Log_BufferLength, "%s %u", (char *)FPSTR(trackStartatPos), audio.getFilePos()); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + } + char buf[255]; + snprintf(buf, sizeof(buf) / sizeof(buf[0]), "(%d/%d) %s", (gPlayProperties.currentTrackNumber + 1), gPlayProperties.numberOfTracks, (const char *)*(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicTrackState), buf, false); +#endif +#if (LANGUAGE == 1) + snprintf(Log_Buffer, Log_BufferLength, "'%s' wird abgespielt (%d von %d)", *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), (gPlayProperties.currentTrackNumber + 1), gPlayProperties.numberOfTracks); +#else + snprintf(Log_Buffer, Log_BufferLength, "'%s' is being played (%d of %d)", *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber), (gPlayProperties.currentTrackNumber + 1), gPlayProperties.numberOfTracks); +#endif + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + gPlayProperties.playlistFinished = false; + } + } + } + + // Handle seekmodes + if (gPlayProperties.seekmode != SEEK_NORMAL) + { + if (gPlayProperties.seekmode == SEEK_FORWARDS) + { + if (audio.setTimeOffset(jumpOffset)) + { +#if (LANGUAGE == 1) + Serial.printf("%d Sekunden nach vorne gesprungen\n", jumpOffset); +#else + Serial.printf("Jumped %d seconds forwards\n", jumpOffset); +#endif + } + else + { + System_IndicateError(); + } + } + else if (gPlayProperties.seekmode == SEEK_BACKWARDS) + { + if (audio.setTimeOffset(-(jumpOffset))) + { +#if (LANGUAGE == 1) + Serial.printf("%d Sekunden zurueck gesprungen\n", jumpOffset); +#else + Serial.printf("Jumped %d seconds backwards\n", jumpOffset); +#endif + } + else + { + System_IndicateError(); + } + } + gPlayProperties.seekmode = SEEK_NORMAL; + } + + // Handle if mono/stereo should be changed (e.g. if plugging headphones) + if (gPlayProperties.newPlayMono != gPlayProperties.currentPlayMono) + { + gPlayProperties.currentPlayMono = gPlayProperties.newPlayMono; + audio.forceMono(gPlayProperties.currentPlayMono); + if (gPlayProperties.currentPlayMono) + { + Log_Println(newPlayModeMono, LOGLEVEL_NOTICE); + audio.setTone(3, 0, 0); + } + else + { + Log_Println(newPlayModeStereo, LOGLEVEL_NOTICE); + audio.setTone(0, 0, 0); + } + } + + // Calculate relative position in file (for neopixel) for SD-card-mode + if (!gPlayProperties.playlistFinished && gPlayProperties.playMode != WEBSTREAM) + { + double fp = (double)audio.getFilePos() / (double)audio.getFileSize(); + if (millis() % 100 == 0) + { + gPlayProperties.currentRelPos = fp * 100; + } + } + else + { + gPlayProperties.currentRelPos = 0; + } + + audio.loop(); + if (gPlayProperties.playlistFinished || gPlayProperties.pausePlay) + { + vTaskDelay(portTICK_PERIOD_MS * 10); // Waste some time if playlist is not active + } + else + { + System_UpdateActivityTimer(); // Refresh if playlist is active so uC will not fall asleep due to reaching inactivity-time + } + + if (audio.isRunning()) + { + settleCount = 0; + } + + // If error occured: remove playlist from ESPuino + if (gPlayProperties.playMode != NO_PLAYLIST && gPlayProperties.playMode != BUSY && !audio.isRunning() && !gPlayProperties.pausePlay) + { + if (settleCount++ == 50) + { // Hack to give audio some time to settle down after playlist was generated + gPlayProperties.playlistFinished = true; + gPlayProperties.playMode = NO_PLAYLIST; + settleCount = 0; + } + } + + esp_task_wdt_reset(); // Don't forget to feed the dog! + } + vTaskDelete(NULL); +} + +// Returns current repeat-mode (mix of repeat current track and current playlist) +uint8_t AudioPlayer_GetRepeatMode(void) +{ + if (gPlayProperties.repeatPlaylist && gPlayProperties.repeatCurrentTrack) + { + return TRACK_N_PLAYLIST; + } + else if (gPlayProperties.repeatPlaylist && !gPlayProperties.repeatCurrentTrack) + { + return PLAYLIST; + } + else if (!gPlayProperties.repeatPlaylist && gPlayProperties.repeatCurrentTrack) + { + return TRACK; + } + else + { + return NO_REPEAT; + } +} + +// Adds new volume-entry to volume-queue +// If volume is changed via webgui or MQTT, it's necessary to re-adjust current value of rotary-encoder. +void AudioPlayer_VolumeToQueueSender(const int32_t _newVolume, bool reAdjustRotary) +{ + uint32_t _volume; + if (_newVolume < AudioPlayer_GetMinVolume()) + { + Log_Println((char *)FPSTR(minLoudnessReached), LOGLEVEL_INFO); + return; + } + else if (_newVolume > AudioPlayer_GetMaxVolume()) + { + Log_Println((char *)FPSTR(maxLoudnessReached), LOGLEVEL_INFO); + return; + } + else + { + _volume = _newVolume; + AudioPlayer_SetCurrentVolume(_volume); + if (reAdjustRotary) + { + RotaryEncoder_Readjust(); + } + } + xQueueSend(gVolumeQueue, &_volume, 0); +} + +// Receives de-serialized RFID-data (from NVS) and dispatches playlists for the given +// playmode to the track-queue. +void AudioPlayer_TrackQueueDispatcher(const char *_itemToPlay, const uint32_t _lastPlayPos, const uint32_t _playMode, const uint16_t _trackLastPlayed) +{ + char *filename; + filename = (char *)x_malloc(sizeof(char) * 255); + + strncpy(filename, _itemToPlay, 255); + gPlayProperties.startAtFilePos = _lastPlayPos; + gPlayProperties.currentTrackNumber = _trackLastPlayed; + char **musicFiles; + gPlayProperties.playMode = BUSY; // Show @Neopixel, if uC is busy with creating playlist + +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicLedBrightnessState), 0, false); + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); +#endif + if (_playMode != WEBSTREAM) + { + musicFiles = SdCard_ReturnPlaylist(filename); + } + else + { + musicFiles = AudioPlayer_ReturnPlaylistFromWebstream(filename); + } +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); +#endif + + if (musicFiles == NULL) + { + Log_Println((char *)FPSTR(errorOccured), LOGLEVEL_ERROR); + System_IndicateError(); + gPlayProperties.playMode = NO_PLAYLIST; + return; + } + else if (!strcmp(*(musicFiles - 1), "0")) + { + Log_Println((char *)FPSTR(noMp3FilesInDir), LOGLEVEL_NOTICE); + System_IndicateError(); + gPlayProperties.playMode = NO_PLAYLIST; + free(filename); + return; + } + + gPlayProperties.playMode = _playMode; + gPlayProperties.numberOfTracks = strtoul(*(musicFiles - 1), NULL, 10); + // Set some default-values + gPlayProperties.repeatCurrentTrack = false; + gPlayProperties.repeatPlaylist = false; + gPlayProperties.sleepAfterCurrentTrack = false; + gPlayProperties.sleepAfterPlaylist = false; + gPlayProperties.saveLastPlayPosition = false; + gPlayProperties.playUntilTrackNumber = 0; + +#ifdef PLAY_LAST_RFID_AFTER_REBOOT + // Store last RFID-tag to NVS + gPrefsSettings.putString("lastRfid", gCurrentRfidTagId); +#endif + + switch (gPlayProperties.playMode) + { + case SINGLE_TRACK: + { + Log_Println((char *)FPSTR(modeSingleTrack), LOGLEVEL_NOTICE); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), NO_REPEAT, false); +#endif + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case SINGLE_TRACK_LOOP: + { + gPlayProperties.repeatCurrentTrack = true; + Log_Println((char *)FPSTR(modeSingleTrackLoop), LOGLEVEL_NOTICE); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), TRACK, false); +#endif + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case AUDIOBOOK: + { // Tracks need to be alph. sorted! + gPlayProperties.saveLastPlayPosition = true; + Log_Println((char *)FPSTR(modeSingleAudiobook), LOGLEVEL_NOTICE); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), NO_REPEAT, false); +#endif + AudioPlayer_SortPlaylist((const char **)musicFiles, strtoul(*(musicFiles - 1), NULL, 10)); + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case AUDIOBOOK_LOOP: + { // Tracks need to be alph. sorted! + gPlayProperties.repeatPlaylist = true; + gPlayProperties.saveLastPlayPosition = true; + Log_Println((char *)FPSTR(modeSingleAudiobookLoop), LOGLEVEL_NOTICE); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), PLAYLIST, false); +#endif + AudioPlayer_SortPlaylist((const char **)musicFiles, strtoul(*(musicFiles - 1), NULL, 10)); + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case ALL_TRACKS_OF_DIR_SORTED: + { + snprintf(Log_Buffer, Log_BufferLength, "%s '%s' ", (char *)FPSTR(modeAllTrackAlphSorted), filename); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + AudioPlayer_SortPlaylist((const char **)musicFiles, strtoul(*(musicFiles - 1), NULL, 10)); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), NO_REPEAT, false); +#endif + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case ALL_TRACKS_OF_DIR_RANDOM: + { + Log_Println((char *)FPSTR(modeAllTrackRandom), LOGLEVEL_NOTICE); + AudioPlayer_SortPlaylist(musicFiles, strtoul(*(musicFiles - 1), NULL, 10)); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), NO_REPEAT, false); +#endif + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case ALL_TRACKS_OF_DIR_SORTED_LOOP: + { + gPlayProperties.repeatPlaylist = true; + Log_Println((char *)FPSTR(modeAllTrackAlphSortedLoop), LOGLEVEL_NOTICE); + AudioPlayer_SortPlaylist((const char **)musicFiles, strtoul(*(musicFiles - 1), NULL, 10)); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), PLAYLIST, false); +#endif + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case ALL_TRACKS_OF_DIR_RANDOM_LOOP: + { + gPlayProperties.repeatPlaylist = true; + Log_Println((char *)FPSTR(modeAllTrackRandomLoop), LOGLEVEL_NOTICE); + AudioPlayer_SortPlaylist(musicFiles, strtoul(*(musicFiles - 1), NULL, 10)); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), PLAYLIST, false); +#endif + xQueueSend(gTrackQueue, &(musicFiles), 0); + break; + } + + case WEBSTREAM: + { // This is always just one "track" + Log_Println((char *)FPSTR(modeWebstream), LOGLEVEL_NOTICE); + if (Wlan_IsConnected()) + { + xQueueSend(gTrackQueue, &(musicFiles), 0); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicRepeatModeState), NO_REPEAT, false); +#endif + } + else + { + Log_Println((char *)FPSTR(webstreamNotAvailable), LOGLEVEL_ERROR); + System_IndicateError(); + gPlayProperties.playMode = NO_PLAYLIST; + } + break; + } + + default: + Log_Println((char *)FPSTR(modeDoesNotExist), LOGLEVEL_ERROR); + gPlayProperties.playMode = NO_PLAYLIST; + System_IndicateError(); + } + free(filename); +} + +/* Wraps putString for writing settings into NVS for RFID-cards. + Returns number of characters written. */ +size_t AudioPlayer_NvsRfidWriteWrapper(const char *_rfidCardId, const char *_track, const uint32_t _playPosition, const uint8_t _playMode, const uint16_t _trackLastPlayed, const uint16_t _numberOfTracks) +{ + Led_SetPause(true); // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write + char prefBuf[275]; + char trackBuf[255]; + snprintf(trackBuf, sizeof(trackBuf) / sizeof(trackBuf[0]), _track); + + // If it's a directory we just want to play/save basename(path) + if (_numberOfTracks > 1) + { + const char s = '/'; + char *last = strrchr(_track, s); + char *first = strchr(_track, s); + unsigned long substr = last - first + 1; + if (substr <= sizeof(trackBuf) / sizeof(trackBuf[0])) + { + snprintf(trackBuf, substr, _track); // save substring basename(_track) + } + else + { + return 0; // Filename too long! + } + } + + snprintf(prefBuf, sizeof(prefBuf) / sizeof(prefBuf[0]), "%s%s%s%u%s%d%s%u", stringDelimiter, trackBuf, stringDelimiter, _playPosition, stringDelimiter, _playMode, stringDelimiter, _trackLastPlayed); +#if (LANGUAGE == 1) + snprintf(Log_Buffer, Log_BufferLength, "Schreibe '%s' in NVS für RFID-Card-ID %s mit playmode %d und letzter Track %u\n", prefBuf, _rfidCardId, _playMode, _trackLastPlayed); +#else + snprintf(Log_Buffer, Log_BufferLength, "Write '%s' to NVS for RFID-Card-ID %s with playmode %d and last track %u\n", prefBuf, _rfidCardId, _playMode, _trackLastPlayed); +#endif + Log_Print(Log_Buffer, LOGLEVEL_INFO); + Log_Println(prefBuf, LOGLEVEL_INFO); + Led_SetPause(false); + return gPrefsRfid.putString(_rfidCardId, prefBuf); +} + +// Adds webstream to playlist; same like SdCard_ReturnPlaylist() but always only one entry +char **AudioPlayer_ReturnPlaylistFromWebstream(const char *_webUrl) +{ + char *webUrl = x_strdup(_webUrl); + static char **url; + + if (url != NULL) + { + --url; + freeMultiCharArray(url, strtoul(*url, NULL, 10)); + } + + url = (char **)x_malloc(sizeof(char *) * 2); + + url[0] = x_strdup("1"); // Number of files is always 1 in url-mode + url[1] = x_strdup(webUrl); + + free(webUrl); + return ++url; +} + +// Adds new control-command to control-queue +void AudioPlayer_TrackControlToQueueSender(const uint8_t trackCommand) +{ + xQueueSend(gTrackControlQueue, &trackCommand, 0); +} + +// Knuth-Fisher-Yates-algorithm to randomize playlist +void AudioPlayer_SortPlaylist(char *str[], const uint32_t count) +{ + if (count < 1) + { + return; + } + + uint32_t i, r; + char *swap = NULL; + uint32_t max = count - 1; + + for (i = 0; i < count; i++) + { + if (max > 0) + { + r = rand() % max; + } + else + { + r = 0; + } + swap = *(str + max); + *(str + max) = *(str + r); + *(str + r) = swap; + max--; + } +} + +// Helper to sort playlist alphabetically +static int AudioPlayer_ArrSortHelper(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + +// Sort playlist alphabetically +void AudioPlayer_SortPlaylist(const char **arr, int n) +{ + qsort(arr, n, sizeof(const char *), AudioPlayer_ArrSortHelper); +} + +// Some mp3-lib-stuff (slightly changed from default) +void audio_info(const char *info) +{ + snprintf(Log_Buffer, Log_BufferLength, "info : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); +} +void audio_id3data(const char *info) +{ //id3 metadata + snprintf(Log_Buffer, Log_BufferLength, "id3data : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); +} +void audio_eof_mp3(const char *info) +{ //end of file + snprintf(Log_Buffer, Log_BufferLength, "eof_mp3 : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + gPlayProperties.trackFinished = true; +} +void audio_showstation(const char *info) +{ + snprintf(Log_Buffer, Log_BufferLength, "station : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + char buf[255]; + snprintf(buf, sizeof(buf) / sizeof(buf[0]), "Webradio: %s", info); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicTrackState), buf, false); +#endif +} +void audio_showstreamtitle(const char *info) +{ + snprintf(Log_Buffer, Log_BufferLength, "streamtitle : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); +} +void audio_bitrate(const char *info) +{ + snprintf(Log_Buffer, Log_BufferLength, "bitrate : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); +} +void audio_commercial(const char *info) +{ //duration in sec + snprintf(Log_Buffer, Log_BufferLength, "commercial : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); +} +void audio_icyurl(const char *info) +{ //homepage + snprintf(Log_Buffer, Log_BufferLength, "icyurl : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); +} +void audio_lasthost(const char *info) +{ //stream URL played + snprintf(Log_Buffer, Log_BufferLength, "lasthost : %s", info); + Log_Println(Log_Buffer, LOGLEVEL_INFO); +} \ No newline at end of file diff --git a/src/AudioPlayer.h b/src/AudioPlayer.h new file mode 100644 index 0000000..f50b950 --- /dev/null +++ b/src/AudioPlayer.h @@ -0,0 +1,44 @@ +#pragma once + +typedef struct { // Bit field + uint8_t playMode: 4; // playMode + char **playlist; // playlist + bool repeatCurrentTrack: 1; // If current track should be looped + bool repeatPlaylist: 1; // If whole playlist should be looped + uint16_t currentTrackNumber: 9; // Current tracknumber + uint16_t numberOfTracks: 9; // Number of tracks in playlist + unsigned long startAtFilePos; // Offset to start play (in bytes) + uint8_t currentRelPos: 7; // Current relative playPosition (in %) + bool sleepAfterCurrentTrack: 1; // If uC should go to sleep after current track + bool sleepAfterPlaylist: 1; // If uC should go to sleep after whole playlist + bool saveLastPlayPosition: 1; // If playposition/current track should be saved (for AUDIOBOOK) + char playRfidTag[13]; // ID of RFID-tag that started playlist + bool pausePlay: 1; // If pause is active + bool trackFinished: 1; // If current track is finished + bool playlistFinished: 1; // If whole playlist is finished + uint8_t playUntilTrackNumber: 6; // Number of tracks to play after which uC goes to sleep + uint8_t seekmode: 2; // If seekmode is active and if yes: forward or backwards? + bool newPlayMono: 1; // true if mono; false if stereo (helper) + bool currentPlayMono: 1; // true if mono; false if stereo +} playProps; + +extern playProps gPlayProperties; + +void AudioPlayer_Init(void); +void AudioPlayer_Cyclic(void); +uint8_t AudioPlayer_GetRepeatMode(void); +void AudioPlayer_VolumeToQueueSender(const int32_t _newVolume, bool reAdjustRotary); +void AudioPlayer_TrackQueueDispatcher(const char *_itemToPlay, const uint32_t _lastPlayPos, const uint32_t _playMode, const uint16_t _trackLastPlayed); +void AudioPlayer_TrackControlToQueueSender(const uint8_t trackCommand); + +uint8_t AudioPlayer_GetCurrentVolume(void); +void AudioPlayer_SetCurrentVolume(uint8_t value); +uint8_t AudioPlayer_GetMaxVolume(void); +void AudioPlayer_SetMaxVolume(uint8_t value); +uint8_t AudioPlayer_GetMaxVolumeSpeaker(void); +void AudioPlayer_SetMaxVolumeSpeaker(uint8_t value); +uint8_t AudioPlayer_GetMinVolume(void); +void AudioPlayer_SetMinVolume(uint8_t value); +uint8_t AudioPlayer_GetInitVolume(void); +void AudioPlayer_SetInitVolume(uint8_t value); +void AudioPlayer_SetupVolume(void); diff --git a/src/Battery.cpp b/src/Battery.cpp new file mode 100644 index 0000000..46bf440 --- /dev/null +++ b/src/Battery.cpp @@ -0,0 +1,113 @@ +#include +#include "settings.h" +#include "Log.h" +#include "Battery.h" +#include "Mqtt.h" +#include "Led.h" +#include "System.h" + +constexpr uint16_t maxAnalogValue = 4095u; // Highest value given by analogRead(); don't change! + +float warningLowVoltage = s_warningLowVoltage; +uint8_t voltageCheckInterval = s_voltageCheckInterval; +float voltageIndicatorLow = s_voltageIndicatorLow; +float voltageIndicatorHigh = s_voltageIndicatorHigh; + +void Battery_Init() +{ +#ifdef MEASURE_BATTERY_VOLTAGE + // Get voltages from NVS for Neopixel + float vLowIndicator = gPrefsSettings.getFloat("vIndicatorLow", 999.99); + if (vLowIndicator <= 999) + { + voltageIndicatorLow = vLowIndicator; + snprintf(Log_Buffer, Log_BufferLength, "%s: %.2f V", (char *)FPSTR(voltageIndicatorLowFromNVS), vLowIndicator); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { // preseed if not set + gPrefsSettings.putFloat("vIndicatorLow", voltageIndicatorLow); + } + + float vHighIndicator = gPrefsSettings.getFloat("vIndicatorHigh", 999.99); + if (vHighIndicator <= 999) + { + voltageIndicatorHigh = vHighIndicator; + snprintf(Log_Buffer, Log_BufferLength, "%s: %.2f V", (char *)FPSTR(voltageIndicatorHighFromNVS), vHighIndicator); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putFloat("vIndicatorHigh", voltageIndicatorHigh); + } + + float vLowWarning = gPrefsSettings.getFloat("wLowVoltage", 999.99); + if (vLowWarning <= 999) + { + warningLowVoltage = vLowWarning; + snprintf(Log_Buffer, Log_BufferLength, "%s: %.2f V", (char *)FPSTR(warningLowVoltageFromNVS), vLowWarning); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putFloat("wLowVoltage", warningLowVoltage); + } + + uint32_t vInterval = gPrefsSettings.getUInt("vCheckIntv", 17777); + if (vInterval != 17777) + { + voltageCheckInterval = vInterval; + snprintf(Log_Buffer, Log_BufferLength, "%s: %u Minuten", (char *)FPSTR(voltageCheckIntervalFromNVS), vInterval); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putUInt("vCheckIntv", voltageCheckInterval); + } +#endif +} + +// The average of several analog reads will be taken to reduce the noise (Note: One analog read takes ~10µs) +float Battery_GetVoltage(void) +{ +#ifdef MEASURE_BATTERY_VOLTAGE + float factor = 1 / ((float)rdiv2 / (rdiv2 + rdiv1)); + float averagedAnalogValue = 0; + uint8_t i; + for (i = 0; i <= 19; i++) + { + averagedAnalogValue += (float)analogRead(VOLTAGE_READ_PIN); + } + averagedAnalogValue /= 20.0; + return (averagedAnalogValue / maxAnalogValue) * referenceVoltage * factor + offsetVoltage; +#endif +} + +// Measures voltage of a battery as per interval or after bootup (after allowing a few seconds to settle down) +void Battery_Cyclic(void) +{ +#ifdef MEASURE_BATTERY_VOLTAGE + static uint32_t lastVoltageCheckTimestamp = 0; + + if ((millis() - lastVoltageCheckTimestamp >= voltageCheckInterval * 60000) || (!lastVoltageCheckTimestamp && millis() >= 10000)) + { + float voltage = Battery_GetVoltage(); + + if (voltage <= warningLowVoltage) + { + snprintf(Log_Buffer, Log_BufferLength, "%s: (%.2f V)", (char *)FPSTR(voltageTooLow), voltage); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + Led_Indicate(LedIndicatorType::VoltageWarning); + } + +#ifdef MQTT_ENABLE + char vstr[6]; + snprintf(vstr, 6, "%.2f", voltage); + publishMqtt((char *)FPSTR(topicBatteryVoltage), vstr, false); +#endif + snprintf(Log_Buffer, Log_BufferLength, "%s: %.2f V", (char *)FPSTR(currentVoltageMsg), voltage); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + lastVoltageCheckTimestamp = millis(); + } +#endif +} \ No newline at end of file diff --git a/src/Battery.h b/src/Battery.h new file mode 100644 index 0000000..58ca8db --- /dev/null +++ b/src/Battery.h @@ -0,0 +1,10 @@ +#pragma once + +extern float warningLowVoltage; +extern uint8_t voltageCheckInterval; +extern float voltageIndicatorLow; +extern float voltageIndicatorHigh; + +void Battery_Init(void); +void Battery_Cyclic(void); +float Battery_GetVoltage(void); diff --git a/src/Bluetooth.cpp b/src/Bluetooth.cpp new file mode 100644 index 0000000..075a065 --- /dev/null +++ b/src/Bluetooth.cpp @@ -0,0 +1,46 @@ +#include +#include "settings.h" +#include "Bluetooth.h" +#include "System.h" + +#ifdef BLUETOOTH_ENABLE +#include "esp_bt.h" +#include "BluetoothA2DPSink.h" +#endif + +#ifdef BLUETOOTH_ENABLE +BluetoothA2DPSink *a2dp_sink; +#endif + +void Bluetooth_Init(void) +{ +#ifdef BLUETOOTH_ENABLE + if (System_GetOperationMode() == OPMODE_BLUETOOTH) + { + a2dp_sink = new BluetoothA2DPSink(); + i2s_pin_config_t pin_config = { + .bck_io_num = I2S_BCLK, + .ws_io_num = I2S_LRC, + .data_out_num = I2S_DOUT, + .data_in_num = I2S_PIN_NO_CHANGE}; + a2dp_sink->set_pin_config(pin_config); + a2dp_sink->start((char *)FPSTR(nameBluetoothDevice)); + } + else + { + esp_bt_mem_release(ESP_BT_MODE_BTDM); + } +#endif +} + +void Bluetooth_Cyclic(void) +{ +#ifdef BLUETOOTH_ENABLE + esp_a2d_audio_state_t state = a2dp_sink->get_audio_state(); + // Reset Sleep Timer when audio is playing + if (state == ESP_A2D_AUDIO_STATE_STARTED) + { + System_UpdateActivityTimer(); + } +#endif +} diff --git a/src/Bluetooth.h b/src/Bluetooth.h new file mode 100644 index 0000000..d1d20ed --- /dev/null +++ b/src/Bluetooth.h @@ -0,0 +1,4 @@ +#pragma once + +void Bluetooth_Init(void); +void Bluetooth_Cyclic(void); \ No newline at end of file diff --git a/src/Button.cpp b/src/Button.cpp new file mode 100644 index 0000000..461b6fa --- /dev/null +++ b/src/Button.cpp @@ -0,0 +1,360 @@ +#include +#include "settings.h" +#include "Log.h" +#include "Button.h" +#include "Cmd.h" +#include "Port.h" +#include "System.h" + +// Only enable those buttons that are not disabled (99 or >115) +// 0 -> 39: GPIOs +// 100 -> 115: Port-expander +#if (NEXT_BUTTON >= 0 && NEXT_BUTTON <= 39) + #define BUTTON_0_ENABLE +#elif (NEXT_BUTTON >= 100 && NEXT_BUTTON <= 115) + #define EXPANDER_0_ENABLE +#endif +#if (PREVIOUS_BUTTON >= 0 && PREVIOUS_BUTTON <= 39) + #define BUTTON_1_ENABLE +#elif (PREVIOUS_BUTTON >= 100 && PREVIOUS_BUTTON <= 115) + #define EXPANDER_1_ENABLE +#endif +#if (PAUSEPLAY_BUTTON >= 0 && PAUSEPLAY_BUTTON <= 39) + #define BUTTON_2_ENABLE +#elif (PAUSEPLAY_BUTTON >= 100 && PAUSEPLAY_BUTTON <= 115) + #define EXPANDER_2_ENABLE +#endif +#ifdef USEROTARY_ENABLE + #if (DREHENCODER_BUTTON >= 0 && DREHENCODER_BUTTON <= 39) + #define BUTTON_3_ENABLE + #elif (DREHENCODER_BUTTON >= 100 && DREHENCODER_BUTTON <= 115) + #define EXPANDER_3_ENABLE + #endif +#endif +#if (BUTTON_4 >= 0 && BUTTON_4 <= 39) + #define BUTTON_4_ENABLE +#elif (BUTTON_4 >= 100 && BUTTON_4 <= 115) + #define EXPANDER_4_ENABLE +#endif +#if (BUTTON_5 >= 0 && BUTTON_5 <= 39) + #define BUTTON_5_ENABLE +#elif (BUTTON_5 >= 100 && BUTTON_5 <= 115) + #define EXPANDER_5_ENABLE +#endif + +t_button gButtons[7]; // next + prev + pplay + rotEnc + button4 + button5 + dummy-button +uint8_t gShutdownButton = 99; // Helper used for Neopixel: stores button-number of shutdown-button + +static volatile SemaphoreHandle_t Button_TimerSemaphore; + +#ifndef IR_CONTROL_ENABLE +hw_timer_t *Button_Timer = NULL; +#endif + +static void IRAM_ATTR onTimer(); + +static void Button_DoButtonActions(void); + +void Button_Init() +{ +#if (WAKEUP_BUTTON <= 39) + esp_sleep_enable_ext0_wakeup((gpio_num_t)WAKEUP_BUTTON, 0); +#endif + +#ifdef NEOPIXEL_ENABLE // Try to find button that is used for shutdown via longpress-action (only necessary for Neopixel) +#if defined(BUTTON_0_ENABLE) || defined(EXPANDER_0_ENABLE) +#if (BUTTON_0_LONG == CMD_SLEEPMODE) + gShutdownButton = 0; +#endif +#endif +#if defined(BUTTON_1_ENABLE) || defined(EXPANDER_1_ENABLE) +#if (BUTTON_1_LONG == CMD_SLEEPMODE) + gShutdownButton = 1; +#endif +#endif +#if defined(BUTTON_2_ENABLE) || defined(EXPANDER_2_ENABLE) +#if (BUTTON_2_LONG == CMD_SLEEPMODE) + gShutdownButton = 2; +#endif +#endif +#if defined(BUTTON_3_ENABLE) || defined(EXPANDER_3_ENABLE) +#if (BUTTON_3_LONG == CMD_SLEEPMODE) + gShutdownButton = 3; +#endif +#endif +#if defined(BUTTON_4_ENABLE) || defined(EXPANDER_4_ENABLE) +#if (BUTTON_4_LONG == CMD_SLEEPMODE) + gShutdownButton = 4; +#endif +#endif +#if defined(BUTTON_5_ENABLE) || defined(EXPANDER_5_ENABLE) +#if (BUTTON_5_LONG == CMD_SLEEPMODE) + gShutdownButton = 5; +#endif +#endif +#endif + +// Activate internal pullups for all enabled buttons +#ifdef BUTTON_0_ENABLE + pinMode(NEXT_BUTTON, INPUT_PULLUP); +#endif +#ifdef BUTTON_1_ENABLE + pinMode(PREVIOUS_BUTTON, INPUT_PULLUP); +#endif +#ifdef BUTTON_2_ENABLE + pinMode(PAUSEPLAY_BUTTON, INPUT_PULLUP); +#endif +#ifdef BUTTON_3_ENABLE + pinMode(DREHENCODER_BUTTON, INPUT_PULLUP); +#endif +#ifdef BUTTON_4_ENABLE + pinMode(BUTTON_4, INPUT_PULLUP); +#endif +#ifdef BUTTON_5_ENABLE + pinMode(BUTTON_5, INPUT_PULLUP); +#endif + + // Create 1000Hz-HW-Timer (currently only used for buttons) + Button_TimerSemaphore = xSemaphoreCreateBinary(); + Button_Timer = timerBegin(0, 240, true); // Prescaler: CPU-clock in MHz + timerAttachInterrupt(Button_Timer, &onTimer, true); + timerAlarmWrite(Button_Timer, 10000, true); // 100 Hz + timerAlarmEnable(Button_Timer); +} + +// If timer-semaphore is set, read buttons (unless controls are locked) +void Button_Cyclic() +{ + if (xSemaphoreTake(Button_TimerSemaphore, 0) == pdTRUE) + { + if (System_AreControlsLocked()) + { + return; + } + + unsigned long currentTimestamp = millis(); + +// Buttons can be mixed between GPIO and port-expander. +// But at the same time only one of them can be for example NEXT_BUTTON +#if defined(BUTTON_0_ENABLE) || defined(EXPANDER_0_ENABLE) + gButtons[0].currentState = Port_Read(NEXT_BUTTON); +#endif +#if defined(BUTTON_1_ENABLE) || defined(EXPANDER_1_ENABLE) + gButtons[1].currentState = Port_Read(PREVIOUS_BUTTON); +#endif +#if defined(BUTTON_2_ENABLE) || defined(EXPANDER_2_ENABLE) + gButtons[2].currentState = Port_Read(PAUSEPLAY_BUTTON); +#endif +#if defined(BUTTON_3_ENABLE) || defined(EXPANDER_3_ENABLE) + gButtons[3].currentState = Port_Read(DREHENCODER_BUTTON); +#endif +#if defined(BUTTON_4_ENABLE) || defined(EXPANDER_4_ENABLE) + gButtons[4].currentState = Port_Read(BUTTON_4); +#endif +#if defined(BUTTON_5_ENABLE) || defined(EXPANDER_5_ENABLE) + gButtons[5].currentState = Port_Read(BUTTON_5); +#endif + + // Iterate over all buttons in struct-array + for (uint8_t i = 0; i < sizeof(gButtons) / sizeof(gButtons[0]); i++) + { + if (gButtons[i].currentState != gButtons[i].lastState && currentTimestamp - gButtons[i].lastPressedTimestamp > buttonDebounceInterval) + { + if (!gButtons[i].currentState) + { + gButtons[i].isPressed = true; + gButtons[i].lastPressedTimestamp = currentTimestamp; + } + else + { + gButtons[i].isReleased = true; + gButtons[i].lastReleasedTimestamp = currentTimestamp; + } + } + gButtons[i].lastState = gButtons[i].currentState; + } + } + Button_DoButtonActions(); +} + +// Do corresponding actions for all buttons +void Button_DoButtonActions(void) +{ + if (gButtons[0].isPressed && gButtons[1].isPressed) + { + gButtons[0].isPressed = false; + gButtons[1].isPressed = false; + Cmd_Action(BUTTON_MULTI_01); + } + else if (gButtons[0].isPressed && gButtons[2].isPressed) + { + gButtons[0].isPressed = false; + gButtons[2].isPressed = false; + Cmd_Action(BUTTON_MULTI_02); + } + else if (gButtons[0].isPressed && gButtons[3].isPressed) + { + gButtons[0].isPressed = false; + gButtons[3].isPressed = false; + Cmd_Action(BUTTON_MULTI_03); + } + else if (gButtons[0].isPressed && gButtons[4].isPressed) + { + gButtons[0].isPressed = false; + gButtons[4].isPressed = false; + Cmd_Action(BUTTON_MULTI_04); + } + else if (gButtons[0].isPressed && gButtons[5].isPressed) + { + gButtons[0].isPressed = false; + gButtons[5].isPressed = false; + Cmd_Action(BUTTON_MULTI_05); + } + else if (gButtons[1].isPressed && gButtons[2].isPressed) + { + gButtons[1].isPressed = false; + gButtons[2].isPressed = false; + Cmd_Action(BUTTON_MULTI_12); + } + else if (gButtons[1].isPressed && gButtons[3].isPressed) + { + gButtons[1].isPressed = false; + gButtons[3].isPressed = false; + Cmd_Action(BUTTON_MULTI_13); + } + else if (gButtons[1].isPressed && gButtons[4].isPressed) + { + gButtons[1].isPressed = false; + gButtons[4].isPressed = false; + Cmd_Action(BUTTON_MULTI_14); + } + else if (gButtons[1].isPressed && gButtons[5].isPressed) + { + gButtons[1].isPressed = false; + gButtons[5].isPressed = false; + Cmd_Action(BUTTON_MULTI_15); + } + else if (gButtons[2].isPressed && gButtons[3].isPressed) + { + gButtons[2].isPressed = false; + gButtons[3].isPressed = false; + Cmd_Action(BUTTON_MULTI_23); + } + else if (gButtons[2].isPressed && gButtons[4].isPressed) + { + gButtons[2].isPressed = false; + gButtons[4].isPressed = false; + Cmd_Action(BUTTON_MULTI_24); + } + else if (gButtons[2].isPressed && gButtons[5].isPressed) + { + gButtons[2].isPressed = false; + gButtons[5].isPressed = false; + Cmd_Action(BUTTON_MULTI_25); + } + else if (gButtons[3].isPressed && gButtons[4].isPressed) + { + gButtons[3].isPressed = false; + gButtons[4].isPressed = false; + Cmd_Action(BUTTON_MULTI_34); + } + else if (gButtons[3].isPressed && gButtons[5].isPressed) + { + gButtons[3].isPressed = false; + gButtons[5].isPressed = false; + Cmd_Action(BUTTON_MULTI_35); + } + else if (gButtons[4].isPressed && gButtons[5].isPressed) + { + gButtons[4].isPressed = false; + gButtons[5].isPressed = false; + Cmd_Action(BUTTON_MULTI_45); + } + else + { + for (uint8_t i = 0; i < sizeof(gButtons) / sizeof(gButtons[0]); i++) + { + if (gButtons[i].isPressed) + { + if (gButtons[i].lastReleasedTimestamp > gButtons[i].lastPressedTimestamp) + { + if (gButtons[i].lastReleasedTimestamp - gButtons[i].lastPressedTimestamp >= intervalToLongPress) + { + switch (i) // Long-press-actions + { + case 0: + Cmd_Action(BUTTON_0_LONG); + gButtons[i].isPressed = false; + break; + + case 1: + Cmd_Action(BUTTON_1_LONG); + gButtons[i].isPressed = false; + break; + + case 2: + Cmd_Action(BUTTON_2_LONG); + gButtons[i].isPressed = false; + break; + + case 3: + Cmd_Action(BUTTON_3_LONG); + gButtons[i].isPressed = false; + break; + + case 4: + Cmd_Action(BUTTON_4_LONG); + gButtons[i].isPressed = false; + break; + + case 5: + Cmd_Action(BUTTON_5_LONG); + gButtons[i].isPressed = false; + break; + } + } + else + { + switch (i) // Short-press-actions + { + case 0: + Cmd_Action(BUTTON_0_SHORT); + gButtons[i].isPressed = false; + break; + + case 1: + Cmd_Action(BUTTON_1_SHORT); + gButtons[i].isPressed = false; + break; + + case 2: + Cmd_Action(BUTTON_2_SHORT); + gButtons[i].isPressed = false; + break; + + case 3: + Cmd_Action(BUTTON_3_SHORT); + gButtons[i].isPressed = false; + break; + + case 4: + Cmd_Action(BUTTON_4_SHORT); + gButtons[i].isPressed = false; + break; + + case 5: + Cmd_Action(BUTTON_5_SHORT); + gButtons[i].isPressed = false; + break; + } + } + } + } + } + } +} + +void IRAM_ATTR onTimer() +{ + xSemaphoreGiveFromISR(Button_TimerSemaphore, NULL); +} diff --git a/src/Button.h b/src/Button.h new file mode 100644 index 0000000..b7dbb26 --- /dev/null +++ b/src/Button.h @@ -0,0 +1,15 @@ +#pragma once + +typedef struct { + bool lastState : 1; + bool currentState : 1; + bool isPressed : 1; + bool isReleased : 1; + unsigned long lastPressedTimestamp; + unsigned long lastReleasedTimestamp; +} t_button; + +extern uint8_t gShutdownButton; + +void Button_Init(void); +void Button_Cyclic(void); diff --git a/src/Cmd.cpp b/src/Cmd.cpp new file mode 100644 index 0000000..034dca6 --- /dev/null +++ b/src/Cmd.cpp @@ -0,0 +1,381 @@ +#include +#include "settings.h" +#include "Cmd.h" +#include "AudioPlayer.h" +#include "Battery.h" +#include "Ftp.h" +#include "Led.h" +#include "Log.h" +#include "Mqtt.h" +#include "System.h" +#include "Wlan.h" + +void Cmd_Action(const uint16_t mod) +{ + switch (mod) + { + case LOCK_BUTTONS_MOD: + { // Locks/unlocks all buttons + System_ToggleLockControls(); + break; + } + + case SLEEP_TIMER_MOD_15: + { // Enables/disables sleep after 15 minutes + System_SetSleepTimer(15u); + + gPlayProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active + gPlayProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active + gPlayProperties.playUntilTrackNumber = 0; + System_IndicateOk(); + break; + } + + case SLEEP_TIMER_MOD_30: + { // Enables/disables sleep after 30 minutes + System_SetSleepTimer(30u); + + gPlayProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active + gPlayProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active + gPlayProperties.playUntilTrackNumber = 0; + System_IndicateOk(); + break; + } + + case SLEEP_TIMER_MOD_60: + { // Enables/disables sleep after 60 minutes + System_SetSleepTimer(60u); + + gPlayProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active + gPlayProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active + gPlayProperties.playUntilTrackNumber = 0; + System_IndicateOk(); + break; + } + + case SLEEP_TIMER_MOD_120: + { // Enables/disables sleep after 2 hrs + System_SetSleepTimer(120u); + + gPlayProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active + gPlayProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active + gPlayProperties.playUntilTrackNumber = 0; + System_IndicateOk(); + break; + } + + case SLEEP_AFTER_END_OF_TRACK: + { // Puts uC to sleep after end of current track + if (gPlayProperties.playMode == NO_PLAYLIST) + { + Log_Println((char *)FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); + System_IndicateError(); + return; + } + if (gPlayProperties.sleepAfterCurrentTrack) + { + Log_Println((char *)FPSTR(modificatorSleepAtEOTd), LOGLEVEL_NOTICE); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicSleepTimerState), "0", false); +#endif +#ifdef NEOPIXEL_ENABLE + Led_ResetToInitialBrightness(); +#endif + } + else + { + Log_Println((char *)FPSTR(modificatorSleepAtEOT), LOGLEVEL_NOTICE); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicSleepTimerState), "EOT", false); +#endif +#ifdef NEOPIXEL_ENABLE + Led_ResetToNightBrightness(); + Log_Println((char *)FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); +#endif + } + gPlayProperties.sleepAfterCurrentTrack = !gPlayProperties.sleepAfterCurrentTrack; + gPlayProperties.sleepAfterPlaylist = false; + System_DisableSleepTimer(); + gPlayProperties.playUntilTrackNumber = 0; + +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); +#endif + System_IndicateOk(); + break; + } + + case SLEEP_AFTER_END_OF_PLAYLIST: + { // Puts uC to sleep after end of whole playlist (can take a while :->) + if (gPlayProperties.playMode == NO_PLAYLIST) + { + Log_Println((char *)FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); + System_IndicateError(); + return; + } + if (gPlayProperties.sleepAfterCurrentTrack) + { +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicSleepTimerState), "0", false); +#endif +#ifdef NEOPIXEL_ENABLE + Led_ResetToInitialBrightness(); +#endif + Log_Println((char *)FPSTR(modificatorSleepAtEOPd), LOGLEVEL_NOTICE); + } + else + { +#ifdef NEOPIXEL_ENABLE + Led_ResetToNightBrightness(); + Log_Println((char *)FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); +#endif + Log_Println((char *)FPSTR(modificatorSleepAtEOP), LOGLEVEL_NOTICE); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicSleepTimerState), "EOP", false); +#endif + } + + gPlayProperties.sleepAfterCurrentTrack = false; + gPlayProperties.sleepAfterPlaylist = !gPlayProperties.sleepAfterPlaylist; + System_DisableSleepTimer(); + gPlayProperties.playUntilTrackNumber = 0; +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); +#endif + System_IndicateOk(); + break; + } + + case SLEEP_AFTER_5_TRACKS: + { + if (gPlayProperties.playMode == NO_PLAYLIST) + { + Log_Println((char *)FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); + System_IndicateError(); + return; + } + + gPlayProperties.sleepAfterCurrentTrack = false; + gPlayProperties.sleepAfterPlaylist = false; + System_DisableSleepTimer(); + + if (gPlayProperties.playUntilTrackNumber > 0) + { + gPlayProperties.playUntilTrackNumber = 0; +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicSleepTimerState), "0", false); +#endif +#ifdef NEOPIXEL_ENABLE + Led_ResetToInitialBrightness(); +#endif + Log_Println((char *)FPSTR(modificatorSleepd), LOGLEVEL_NOTICE); + } + else + { + if (gPlayProperties.currentTrackNumber + 5 > gPlayProperties.numberOfTracks) + { // If currentTrack + 5 exceeds number of tracks in playlist, sleep after end of playlist + gPlayProperties.sleepAfterPlaylist = true; +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicSleepTimerState), "EOP", false); +#endif + } + else + { + gPlayProperties.playUntilTrackNumber = gPlayProperties.currentTrackNumber + 5; +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicSleepTimerState), "EO5T", false); +#endif + } +#ifdef NEOPIXEL_ENABLE + Led_ResetToNightBrightness(); +#endif + Log_Println((char *)FPSTR(sleepTimerEO5), LOGLEVEL_NOTICE); + } + +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); +#endif + System_IndicateOk(); + break; + } + + case REPEAT_PLAYLIST: + { + if (gPlayProperties.playMode == NO_PLAYLIST) + { + Log_Println((char *)FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); + System_IndicateError(); + } + else + { + if (gPlayProperties.repeatPlaylist) + { + Log_Println((char *)FPSTR(modificatorPlaylistLoopDeactive), LOGLEVEL_NOTICE); + } + else + { + Log_Println((char *)FPSTR(modificatorPlaylistLoopActive), LOGLEVEL_NOTICE); + } + gPlayProperties.repeatPlaylist = !gPlayProperties.repeatPlaylist; + char rBuf[2]; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); +#endif + System_IndicateOk(); + } + break; + } + + case REPEAT_TRACK: + { // Introduces looping for track-mode + if (gPlayProperties.playMode == NO_PLAYLIST) + { + Log_Println((char *)FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); + System_IndicateError(); + } + else + { + if (gPlayProperties.repeatCurrentTrack) + { + Log_Println((char *)FPSTR(modificatorTrackDeactive), LOGLEVEL_NOTICE); + } + else + { + Log_Println((char *)FPSTR(modificatorTrackActive), LOGLEVEL_NOTICE); + } + gPlayProperties.repeatCurrentTrack = !gPlayProperties.repeatCurrentTrack; + char rBuf[2]; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); +#endif + System_IndicateOk(); + } + break; + } + + case DIMM_LEDS_NIGHTMODE: + { +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); +#endif + Log_Println((char *)FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); +#ifdef NEOPIXEL_ENABLE + Led_ResetToNightBrightness(); +#endif + System_IndicateOk(); + break; + } + + case TOGGLE_WIFI_STATUS: + { + Wlan_ToggleEnable(); + System_IndicateOk(); + break; + } +#ifdef BLUETOOTH_ENABLE + case TOGGLE_BLUETOOTH_MODE: + { + if (System_GetOperationModeFromNvs() == OPMODE_NORMAL) + { + System_IndicateOk(); + System_SetOperationMode(OPMODE_BLUETOOTH); + } + else if (System_GetOperationModeFromNvs() == OPMODE_BLUETOOTH) + { + System_IndicateOk(); + System_SetOperationMode(OPMODE_NORMAL); + } + else + { + System_IndicateError(); + } + break; + } +#endif +#ifdef FTP_ENABLE + case ENABLE_FTP_SERVER: + { + Ftp_EnableServer(); + break; + } +#endif + case CMD_PLAYPAUSE: + { + AudioPlayer_TrackControlToQueueSender(PAUSEPLAY); + break; + } + case CMD_PREVTRACK: + { + AudioPlayer_TrackControlToQueueSender(PREVIOUSTRACK); + break; + } + case CMD_NEXTTRACK: + { + AudioPlayer_TrackControlToQueueSender(NEXTTRACK); + break; + } + case CMD_FIRSTTRACK: + { + AudioPlayer_TrackControlToQueueSender(FIRSTTRACK); + break; + } + case CMD_LASTTRACK: + { + AudioPlayer_TrackControlToQueueSender(LASTTRACK); + break; + } + case CMD_VOLUMEINIT: + { + AudioPlayer_VolumeToQueueSender(AudioPlayer_GetInitVolume(), true); + break; + } + case CMD_VOLUMEUP: + { + AudioPlayer_VolumeToQueueSender(AudioPlayer_GetCurrentVolume() + 1, true); + break; + } + case CMD_VOLUMEDOWN: + { + AudioPlayer_VolumeToQueueSender(AudioPlayer_GetCurrentVolume() - 1, true); + break; + } + case CMD_MEASUREBATTERY: + { +#ifdef MEASURE_BATTERY_VOLTAGE + float voltage = Battery_GetVoltage(); + snprintf(Log_Buffer, Log_BufferLength, "%s: %.2f V", (char *)FPSTR(currentVoltageMsg), voltage); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + Led_Indicate(LedIndicatorType::Voltage); +#ifdef MQTT_ENABLE + char vstr[6]; + snprintf(vstr, 6, "%.2f", voltage); + publishMqtt((char *)FPSTR(topicBatteryVoltage), vstr, false); +#endif +#endif + break; + } + case CMD_SLEEPMODE: + { + System_RequestSleep(); + break; + } + case CMD_SEEK_FORWARDS: + { + gPlayProperties.seekmode = SEEK_FORWARDS; + break; + } + case CMD_SEEK_BACKWARDS: + { + gPlayProperties.seekmode = SEEK_BACKWARDS; + break; + } + default: + { + snprintf(Log_Buffer, Log_BufferLength, "%s %d !", (char *)FPSTR(modificatorDoesNotExist), mod); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + System_IndicateError(); + } + } +} diff --git a/src/Cmd.h b/src/Cmd.h new file mode 100644 index 0000000..84db533 --- /dev/null +++ b/src/Cmd.h @@ -0,0 +1,3 @@ +#pragma once + +void Cmd_Action(const uint16_t mod); \ No newline at end of file diff --git a/src/Common.h b/src/Common.h new file mode 100644 index 0000000..76f7d3e --- /dev/null +++ b/src/Common.h @@ -0,0 +1,171 @@ +#pragma once + +// FilePathLength +#define MAX_FILEPATH_LENTGH 256 + +constexpr char stringDelimiter[] = "#"; // Character used to encapsulate data in linear NVS-strings (don't change) +constexpr char stringOuterDelimiter[] = "^"; // Character used to encapsulate encapsulated data along with RFID-ID in backup-file + +inline bool isNumber(const char *str) +{ + byte i = 0; + + while (*(str + i) != '\0') + { + if (!isdigit(*(str + i++))) + { + return false; + } + } + + if (i > 0) + { + return true; + } + else + { + return false; + } +} + +// Checks if string starts with prefix +// Returns true if so +inline bool startsWith(const char *str, const char *pre) +{ + if (strlen(pre) < 1) + { + return false; + } + + return !strncmp(str, pre, strlen(pre)); +} + +// Checks if string ends with suffix +// Returns true if so +inline bool endsWith(const char *str, const char *suf) +{ + const char *a = str + strlen(str); + const char *b = suf + strlen(suf); + + while (a != str && b != suf) + { + if (*--a != *--b) + break; + } + + return b == suf && *a == *b; +} + +inline void convertUtf8ToAscii(String utf8String, char *asciiString) +{ + + int k = 0; + bool f_C3_seen = false; + + for (int i = 0; i < utf8String.length() && k < MAX_FILEPATH_LENTGH - 1; i++) + { + + if (utf8String[i] == 195) + { // C3 + f_C3_seen = true; + continue; + } + else + { + if (f_C3_seen == true) + { + f_C3_seen = false; + switch (utf8String[i]) + { + case 0x84: + asciiString[k++] = 0x8e; + break; // Ä + case 0xa4: + asciiString[k++] = 0x84; + break; // ä + case 0x9c: + asciiString[k++] = 0x9a; + break; // Ü + case 0xbc: + asciiString[k++] = 0x81; + break; // ü + case 0x96: + asciiString[k++] = 0x99; + break; // Ö + case 0xb6: + asciiString[k++] = 0x94; + break; // ö + case 0x9f: + asciiString[k++] = 0xe1; + break; // ß + default: + asciiString[k++] = 0xdb; // Unknow... + } + } + else + { + asciiString[k++] = utf8String[i]; + } + } + } + + asciiString[k] = 0; +} + +inline void convertAsciiToUtf8(String asciiString, char *utf8String) +{ + + int k = 0; + + for (int i = 0; i < asciiString.length() && k < MAX_FILEPATH_LENTGH - 2; i++) + { + + switch (asciiString[i]) + { + case 0x8e: + utf8String[k++] = 0xc3; + utf8String[k++] = 0x84; + break; // Ä + case 0x84: + utf8String[k++] = 0xc3; + utf8String[k++] = 0xa4; + break; // ä + case 0x9a: + utf8String[k++] = 0xc3; + utf8String[k++] = 0x9c; + break; // Ü + case 0x81: + utf8String[k++] = 0xc3; + utf8String[k++] = 0xbc; + break; // ü + case 0x99: + utf8String[k++] = 0xc3; + utf8String[k++] = 0x96; + break; // Ö + case 0x94: + utf8String[k++] = 0xc3; + utf8String[k++] = 0xb6; + break; // ö + case 0xe1: + utf8String[k++] = 0xc3; + utf8String[k++] = 0x9f; + break; // ß + default: + utf8String[k++] = asciiString[i]; + } + } + + utf8String[k] = 0; +} + +// Release previously allocated memory +inline void freeMultiCharArray(char **arr, const uint32_t cnt) +{ + for (uint32_t i = 0; i <= cnt; i++) + { + /*snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(freePtr), *(arr+i)); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG);*/ + free(*(arr + i)); + } + *arr = NULL; +} diff --git a/src/Ftp.cpp b/src/Ftp.cpp new file mode 100644 index 0000000..a95e48e --- /dev/null +++ b/src/Ftp.cpp @@ -0,0 +1,116 @@ +#include +#include +#include "settings.h" +#include "Ftp.h" +#include "Log.h" +#include "MemX.h" +#include "SdCard.h" +#include "System.h" +#include "Wlan.h" + +#ifdef FTP_ENABLE +#include "ESP32FtpServer.h" +#endif + +// FTP +char *Ftp_User = x_strndup((char *)"esp32", ftpUserLength); // FTP-user (default; can be changed later via GUI) +char *Ftp_Password = x_strndup((char *)"esp32", ftpPasswordLength); // FTP-password (default; can be changed later via GUI) + +// FTP +#ifdef FTP_ENABLE +FtpServer *ftpSrv; // Heap-alloction takes place later (when needed) +bool ftpEnableLastStatus = false; +bool ftpEnableCurrentStatus = false; +#endif + +void ftpManager(void); + +void Ftp_Init(void) +{ + // Get FTP-user from NVS + String nvsFtpUser = gPrefsSettings.getString("ftpuser", "-1"); + if (!nvsFtpUser.compareTo("-1")) + { + gPrefsSettings.putString("ftpuser", (String)Ftp_User); + Log_Println((char *)FPSTR(wroteFtpUserToNvs), LOGLEVEL_ERROR); + } + else + { + strncpy(Ftp_User, nvsFtpUser.c_str(), ftpUserLength); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(restoredFtpUserFromNvs), nvsFtpUser.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + + // Get FTP-password from NVS + String nvsFtpPassword = gPrefsSettings.getString("ftppassword", "-1"); + if (!nvsFtpPassword.compareTo("-1")) + { + gPrefsSettings.putString("ftppassword", (String)Ftp_Password); + Log_Println((char *)FPSTR(wroteFtpPwdToNvs), LOGLEVEL_ERROR); + } + else + { + strncpy(Ftp_Password, nvsFtpPassword.c_str(), ftpPasswordLength); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(restoredFtpPwdFromNvs), nvsFtpPassword.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } +} + +void Ftp_Cyclic(void) +{ +#ifdef FTP_ENABLE + ftpManager(); + + if (WL_CONNECTED == WiFi.status()) + { + if (ftpEnableLastStatus && ftpEnableCurrentStatus) + { + ftpSrv->handleFTP(); + } + } + + if (ftpEnableLastStatus && ftpEnableCurrentStatus) + { + if (ftpSrv->isConnected()) + { + System_UpdateActivityTimer(); // Re-adjust timer while client is connected to avoid ESP falling asleep + } + } +#endif +} + +void Ftp_EnableServer(void) +{ + if (Wlan_IsConnected() && !ftpEnableLastStatus && !ftpEnableCurrentStatus) + { + ftpEnableLastStatus = true; + System_IndicateOk(); + } + else + { + Log_Println((char *)FPSTR(unableToStartFtpServer), LOGLEVEL_ERROR); + System_IndicateError(); + } +} + +// Creates FTP-instance only when requested +void ftpManager(void) +{ +#ifdef FTP_ENABLE + if (ftpEnableLastStatus && !ftpEnableCurrentStatus) + { + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(freeHeapWithoutFtp), ESP.getFreeHeap()); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG); + ftpEnableCurrentStatus = true; + ftpSrv = new FtpServer(); + ftpSrv->begin(gFSystem, Ftp_User, Ftp_Password); + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(freeHeapWithFtp), ESP.getFreeHeap()); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG); +#if (LANGUAGE == 1) + Serial.println(F("FTP-Server gestartet")); +#else + Serial.println(F("FTP-server started")); +#endif + } +#endif +} diff --git a/src/Ftp.h b/src/Ftp.h new file mode 100644 index 0000000..fc43124 --- /dev/null +++ b/src/Ftp.h @@ -0,0 +1,8 @@ +#pragma once + +constexpr uint8_t ftpUserLength = 10u; // Length will be published n-1 as maxlength to GUI +constexpr uint8_t ftpPasswordLength = 15u; // Length will be published n-1 as maxlength to GUI + +void Ftp_Init(void); +void Ftp_Cyclic(void); +void Ftp_EnableServer(void); diff --git a/src/HTMLaccesspoint.h b/src/HTMLaccesspoint_DE.h similarity index 98% rename from src/HTMLaccesspoint.h rename to src/HTMLaccesspoint_DE.h index 4405e10..ae64193 100644 --- a/src/HTMLaccesspoint.h +++ b/src/HTMLaccesspoint_DE.h @@ -1,3 +1,5 @@ +#if (LANGUAGE == 1) + static const char accesspoint_HTML[] PROGMEM = "\ \ \ @@ -61,4 +63,6 @@ static const char accesspoint_HTML[] PROGMEM = "\ \ \ \ -"; \ No newline at end of file +"; + +#endif diff --git a/src/HTMLaccesspoint_EN.h b/src/HTMLaccesspoint_EN.h index 3b7ceba..a5187eb 100644 --- a/src/HTMLaccesspoint_EN.h +++ b/src/HTMLaccesspoint_EN.h @@ -1,3 +1,5 @@ +#if (LANGUAGE == 2) + static const char accesspoint_HTML[] PROGMEM = "\ \ \ @@ -61,4 +63,6 @@ static const char accesspoint_HTML[] PROGMEM = "\ \ \ \ -"; \ No newline at end of file +"; + +#endif diff --git a/src/HTMLmanagement.h b/src/HTMLmanagement_DE.h similarity index 99% rename from src/HTMLmanagement.h rename to src/HTMLmanagement_DE.h index 2322da5..ac4b7f4 100644 --- a/src/HTMLmanagement.h +++ b/src/HTMLmanagement_DE.h @@ -1,3 +1,5 @@ +#if (LANGUAGE == 1) + static const char management_HTML[] PROGMEM = "\ \ \ @@ -1108,4 +1110,6 @@ static const char management_HTML[] PROGMEM = "\ });\ \ \ -"; \ No newline at end of file +"; + +#endif diff --git a/src/HTMLmanagement_EN.h b/src/HTMLmanagement_EN.h index c3febf0..a920c5e 100644 --- a/src/HTMLmanagement_EN.h +++ b/src/HTMLmanagement_EN.h @@ -1,3 +1,5 @@ +#if (LANGUAGE == 2) + static const char management_HTML[] PROGMEM = "\ \ \ @@ -1108,4 +1110,6 @@ static const char management_HTML[] PROGMEM = "\ });\ \ \ -"; \ No newline at end of file +"; + +#endif diff --git a/src/IrReceiver.cpp b/src/IrReceiver.cpp new file mode 100644 index 0000000..6465496 --- /dev/null +++ b/src/IrReceiver.cpp @@ -0,0 +1,169 @@ +#include +#include "settings.h" +#include "IrReceiver.h" +#include "AudioPlayer.h" +#include "Cmd.h" +#include "Queues.h" +#include "System.h" + +#ifdef IR_CONTROL_ENABLE +#include +#endif + +// HW-Timer +#ifdef IR_CONTROL_ENABLE +uint32_t IrReceiver_LastRcCmdTimestamp = 0u; +#endif + +void IrReceiver_Init() +{ +#ifdef IR_CONTROL_ENABLE + IrReceiver.begin(IRLED_PIN); +#endif +} + +void IrReceiver_Cyclic() +{ +#ifdef IR_CONTROL_ENABLE + static uint8_t lastVolume = 0; + + if (IrReceiver.decode()) + { + + // Print a short summary of received data + IrReceiver.printIRResultShort(&Serial); + Serial.println(); + IrReceiver.resume(); // Enable receiving of the next value + bool rcActionOk = false; + if (millis() - IrReceiver_LastRcCmdTimestamp >= IR_DEBOUNCE) + { + rcActionOk = true; // not used for volume up/down + IrReceiver_LastRcCmdTimestamp = millis(); + } + + switch (IrReceiver.decodedIRData.command) + { + case RC_PLAY: + { + if (rcActionOk) + { + Cmd_Action(CMD_PLAYPAUSE); + Serial.println(F("RC: Play")); + } + break; + } + case RC_PAUSE: + { + if (rcActionOk) + { + Cmd_Action(CMD_PLAYPAUSE); + Serial.println(F("RC: Pause")); + } + break; + } + case RC_NEXT: + { + if (rcActionOk) + { + Cmd_Action(CMD_NEXTTRACK); + Serial.println(F("RC: Next")); + } + break; + } + case RC_PREVIOUS: + { + if (rcActionOk) + { + Cmd_Action(CMD_PREVTRACK); + Serial.println(F("RC: Previous")); + } + break; + } + case RC_FIRST: + { + if (rcActionOk) + { + Cmd_Action(CMD_FIRSTTRACK); + Serial.println(F("RC: First")); + } + break; + } + case RC_LAST: + { + if (rcActionOk) + { + Cmd_Action(CMD_LASTTRACK); + Serial.println(F("RC: Last")); + } + break; + } + case RC_MUTE: + { + if (rcActionOk) + { + if (AudioPlayer_GetCurrentVolume() > 0) + { + lastVolume = AudioPlayer_GetCurrentVolume(); + AudioPlayer_SetCurrentVolume(0u); + } + else + { + AudioPlayer_SetCurrentVolume(lastVolume); // Remember last volume if mute is pressed again + } + + uint8_t currentVolume = AudioPlayer_GetCurrentVolume(); + xQueueSend(gVolumeQueue, ¤tVolume, 0); + Serial.println(F("RC: Mute")); + } + break; + } + case RC_BLUETOOTH: + { + if (rcActionOk) + { + Cmd_Action(TOGGLE_BLUETOOTH_MODE); + Serial.println(F("RC: Bluetooth")); + } + break; + } + case RC_FTP: + { + if (rcActionOk) + { + Cmd_Action(ENABLE_FTP_SERVER); + Serial.println(F("RC: FTP")); + } + break; + } + case RC_SHUTDOWN: + { + if (rcActionOk) + { + System_RequestSleep(); + Serial.println(F("RC: Shutdown")); + } + break; + } + case RC_VOL_DOWN: + { + Cmd_Action(CMD_VOLUMEDOWN); + Serial.println(F("RC: Volume down")); + break; + } + case RC_VOL_UP: + { + Cmd_Action(CMD_VOLUMEUP); + Serial.println(F("RC: Volume up")); + break; + } + default: + { + if (rcActionOk) + { + Serial.println(F("RC: unknown")); + } + } + } + } +#endif +} \ No newline at end of file diff --git a/src/IrReceiver.h b/src/IrReceiver.h new file mode 100644 index 0000000..9efabec --- /dev/null +++ b/src/IrReceiver.h @@ -0,0 +1,4 @@ +#pragma once + +void IrReceiver_Init(); +void IrReceiver_Cyclic(); diff --git a/src/Led.cpp b/src/Led.cpp new file mode 100644 index 0000000..2e3adc3 --- /dev/null +++ b/src/Led.cpp @@ -0,0 +1,654 @@ +#include +#include +#include +#include "settings.h" +#include "AudioPlayer.h" +#include "Battery.h" +#include "Button.h" +#include "Led.h" +#include "Log.h" +#include "System.h" +#include "Wlan.h" + +#ifdef NEOPIXEL_ENABLE +#include + +#define LED_INITIAL_BRIGHTNESS 16u +#define LED_INITIAL_NIGHT_BRIGHTNESS 2u + +#define LED_INDICATOR_SET(indicator) ((Led_Indicators) |= (1u << ((uint8_t)indicator))) +#define LED_INDICATOR_IS_SET(indicator) (((Led_Indicators) & (1u << ((uint8_t)indicator))) > 0u) +#define LED_INDICATOR_CLEAR(indicator) ((Led_Indicators) &= ~(1u << ((uint8_t)indicator))) + +extern t_button gButtons[7]; // next + prev + pplay + rotEnc + button4 + button5 + dummy-button +extern uint8_t gShutdownButton; + +static uint32_t Led_Indicators = 0u; + +static bool Led_Pause = false; // Used to pause Neopixel-signalisation (while NVS-writes as this leads to exceptions; don't know why) + +static uint8_t Led_InitialBrightness = LED_INITIAL_BRIGHTNESS; +static uint8_t Led_Brightness = LED_INITIAL_BRIGHTNESS; +static uint8_t Led_NightBrightness = LED_INITIAL_NIGHT_BRIGHTNESS; + +static void Led_Task(void *parameter); +static uint8_t Led_Address(uint8_t number); + +#endif + +void Led_Init(void) +{ +#ifdef NEOPIXEL_ENABLE + // Get some stuff from NVS... + // Get initial LED-brightness from NVS + uint8_t nvsILedBrightness = gPrefsSettings.getUChar("iLedBrightness", 0); + if (nvsILedBrightness) + { + Led_InitialBrightness = nvsILedBrightness; + Led_Brightness = nvsILedBrightness; + snprintf(Log_Buffer, Log_BufferLength, "%s: %d", (char *)FPSTR(initialBrightnessfromNvs), nvsILedBrightness); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putUChar("iLedBrightness", Led_InitialBrightness); + Log_Println((char *)FPSTR(wroteInitialBrightnessToNvs), LOGLEVEL_ERROR); + } + + // Get night LED-brightness from NVS + uint8_t nvsNLedBrightness = gPrefsSettings.getUChar("nLedBrightness", 0); + if (nvsNLedBrightness) + { + Led_NightBrightness = nvsNLedBrightness; + snprintf(Log_Buffer, Log_BufferLength, "%s: %d", (char *)FPSTR(restoredInitialBrightnessForNmFromNvs), nvsNLedBrightness); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putUChar("nLedBrightness", Led_NightBrightness); + Log_Println((char *)FPSTR(wroteNmBrightnessToNvs), LOGLEVEL_ERROR); + } + + xTaskCreatePinnedToCore( + Led_Task, /* Function to implement the task */ + "Led_Task", /* Name of the task */ + 2000, /* Stack size in words */ + NULL, /* Task input parameter */ + 1, /* Priority of the task */ + NULL, /* Task handle. */ + 0 /* Core where the task should run */ + ); +#endif +} + +void Led_Exit(void) +{ +#ifdef NEOPIXEL_ENABLE + FastLED.clear(); + FastLED.show(); +#endif +} + +void Led_Indicate(LedIndicatorType value) +{ +#ifdef NEOPIXEL_ENABLE + LED_INDICATOR_SET(value); +#endif +} + +void Led_SetPause(boolean value) +{ +#ifdef NEOPIXEL_ENABLE + Led_Pause = value; +#endif +} + +void Led_ResetToInitialBrightness(void) +{ +#ifdef NEOPIXEL_ENABLE + Led_Brightness = Led_InitialBrightness; +#endif +} + +void Led_ResetToNightBrightness(void) +{ +#ifdef NEOPIXEL_ENABLE + Led_Brightness = Led_NightBrightness; + Log_Println((char *)FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); +#endif +} + +uint8_t Led_GetBrightness(void) +{ +#ifdef NEOPIXEL_ENABLE + return Led_Brightness; +#else + return 0u; +#endif +} + +void Led_SetBrightness(uint8_t value) +{ +#ifdef NEOPIXEL_ENABLE + Led_Brightness = value; +#endif +} + +// Switches Neopixel-addressing from clockwise to counter clockwise (and vice versa) +uint8_t Led_Address(uint8_t number) +{ +#ifdef NEOPIXEL_REVERSE_ROTATION + return NUM_LEDS - 1 - number; +#else + return number; +#endif +} + +static void Led_Task(void *parameter) +{ +#ifdef NEOPIXEL_ENABLE + static uint8_t hlastVolume = AudioPlayer_GetCurrentVolume(); + static uint8_t lastPos = gPlayProperties.currentRelPos; + static bool lastPlayState = false; + static bool lastLockState = false; + static bool ledBusyShown = false; + static bool notificationShown = false; + static bool volumeChangeShown = false; + static bool showEvenError = false; + static bool turnedOffLeds = false; + static uint8_t ledPosWebstream = 0; + static uint8_t ledSwitchInterval = 5; // time in secs (webstream-only) + static uint8_t webstreamColor = 0; + static unsigned long lastSwitchTimestamp = 0; + static bool redrawProgress = false; + static uint8_t lastLedBrightness = Led_Brightness; + static CRGB::HTMLColorCode idleColor; + static CRGB leds[NUM_LEDS]; + FastLED.addLeds(leds, NUM_LEDS).setCorrection(TypicalSMD5050); + FastLED.setBrightness(Led_Brightness); + + for (;;) + { + if (Led_Pause) + { // Workaround to prevent exceptions while NVS-writes take place + vTaskDelay(portTICK_RATE_MS * 10); + continue; + } + if (System_IsSleepRequested()) + { // If deepsleep is planned, turn off LEDs first in order to avoid LEDs still glowing when ESP32 is in deepsleep + if (!turnedOffLeds) + { + FastLED.clear(true); + turnedOffLeds = true; + } + + vTaskDelay(portTICK_RATE_MS * 10); + continue; + } + if (!LED_INDICATOR_IS_SET(LedIndicatorType::BootComplete)) + { // Rotates orange unless boot isn't complete + FastLED.clear(); + for (uint8_t led = 0; led < NUM_LEDS; led++) + { + if (showEvenError) + { + if (Led_Address(led) % 2 == 0) + { + if (millis() <= 10000) + { + leds[Led_Address(led)] = CRGB::Orange; + } + else + { + leds[Led_Address(led)] = CRGB::Red; + } + } + } + else + { + if (millis() >= 10000) + { // Flashes red after 10s (will remain forever if SD cannot be mounted) + leds[Led_Address(led)] = CRGB::Red; + } + else + { + if (Led_Address(led) % 2 == 1) + { + leds[Led_Address(led)] = CRGB::Orange; + } + } + } + } + FastLED.show(); + showEvenError = !showEvenError; + vTaskDelay(portTICK_RATE_MS * 500); + esp_task_wdt_reset(); + continue; + } + + if (lastLedBrightness != Led_Brightness) + { + FastLED.setBrightness(Led_Brightness); + lastLedBrightness = Led_Brightness; + } + + // LEDs growing red as long button for sleepmode is pressed. + if (gShutdownButton < (sizeof(gButtons) / sizeof(gButtons[0])) - 1) + { // Only show animation, if CMD_SLEEPMODE was assigned to BUTTON_n_LONG + button is pressed + if (!gButtons[gShutdownButton].currentState) + { + FastLED.clear(); + for (uint8_t led = 0; led < NUM_LEDS; led++) + { + leds[Led_Address(led)] = CRGB::Red; + if (gButtons[gShutdownButton].currentState) + { + FastLED.show(); + delay(5); + break; + } + FastLED.show(); + vTaskDelay(intervalToLongPress / NUM_LEDS * portTICK_RATE_MS); + } + } + } + else + { + gShutdownButton = (sizeof(gButtons) / sizeof(gButtons[0])) - 1; // If CMD_SLEEPMODE was not assigned to an enabled button, dummy-button is used + if (!gButtons[gShutdownButton].currentState) + { + gButtons[gShutdownButton].currentState = true; + } + } + + if (LED_INDICATOR_IS_SET(LedIndicatorType::Error)) + { // If error occured (e.g. RFID-modification not accepted) + LED_INDICATOR_CLEAR(LedIndicatorType::Error); + notificationShown = true; + FastLED.clear(); + + for (uint8_t led = 0; led < NUM_LEDS; led++) + { + leds[Led_Address(led)] = CRGB::Red; + } + FastLED.show(); + vTaskDelay(portTICK_RATE_MS * 200); + } + + if (LED_INDICATOR_IS_SET(LedIndicatorType::Ok)) + { // If action was accepted + LED_INDICATOR_CLEAR(LedIndicatorType::Ok); + notificationShown = true; + FastLED.clear(); + + for (uint8_t led = 0; led < NUM_LEDS; led++) + { + leds[Led_Address(led)] = CRGB::Green; + } + FastLED.show(); + vTaskDelay(portTICK_RATE_MS * 400); + } + +#ifdef MEASURE_BATTERY_VOLTAGE + if (LED_INDICATOR_IS_SET(LedIndicatorType::VoltageWarning)) + { // Flashes red three times if battery-voltage is low + LED_INDICATOR_CLEAR(LedIndicatorType::VoltageWarning); + notificationShown = true; + for (uint8_t i = 0; i < 3; i++) + { + FastLED.clear(); + + for (uint8_t led = 0; led < NUM_LEDS; led++) + { + leds[Led_Address(led)] = CRGB::Red; + } + FastLED.show(); + vTaskDelay(portTICK_RATE_MS * 200); + FastLED.clear(); + + for (uint8_t led = 0; led < NUM_LEDS; led++) + { + leds[Led_Address(led)] = CRGB::Black; + } + FastLED.show(); + vTaskDelay(portTICK_RATE_MS * 200); + } + } + + if (LED_INDICATOR_IS_SET(LedIndicatorType::Voltage)) + { + LED_INDICATOR_CLEAR(LedIndicatorType::Voltage); + float currentVoltage = Battery_GetVoltage(); + float vDiffIndicatorRange = voltageIndicatorHigh - voltageIndicatorLow; + float vDiffCurrent = currentVoltage - voltageIndicatorLow; + + if (vDiffCurrent < 0) + { // If voltage is too low or no battery is connected + LED_INDICATOR_SET(LedIndicatorType::Error); + break; + } + else + { + uint8_t numLedsToLight = ((float)vDiffCurrent / vDiffIndicatorRange) * NUM_LEDS; + FastLED.clear(); + for (uint8_t led = 0; led < numLedsToLight; led++) + { + if (((float)numLedsToLight / NUM_LEDS) >= 0.6) + { + leds[Led_Address(led)] = CRGB::Green; + } + else if (((float)numLedsToLight / NUM_LEDS) <= 0.6 && ((float)numLedsToLight / NUM_LEDS) >= 0.3) + { + leds[Led_Address(led)] = CRGB::Orange; + } + else + { + leds[Led_Address(led)] = CRGB::Red; + } + FastLED.show(); + vTaskDelay(portTICK_RATE_MS * 20); + } + + for (uint8_t i = 0; i <= 100; i++) + { + if (hlastVolume != AudioPlayer_GetCurrentVolume() || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { + break; + } + + vTaskDelay(portTICK_RATE_MS * 20); + } + } + } +#endif + + if (hlastVolume != AudioPlayer_GetCurrentVolume()) + { // If volume has been changed + uint8_t numLedsToLight = map(AudioPlayer_GetCurrentVolume(), 0, AudioPlayer_GetMaxVolume(), 0, NUM_LEDS); + hlastVolume = AudioPlayer_GetCurrentVolume(); + volumeChangeShown = true; + FastLED.clear(); + + for (int led = 0; led < numLedsToLight; led++) + { // (Inverse) color-gradient from green (85) back to (still) red (245) using unsigned-cast + leds[Led_Address(led)].setHue((uint8_t)(85 - ((double)95 / NUM_LEDS) * led)); + } + FastLED.show(); + + for (uint8_t i = 0; i <= 50; i++) + { + if (hlastVolume != AudioPlayer_GetCurrentVolume() || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { + if (hlastVolume != AudioPlayer_GetCurrentVolume()) + { + volumeChangeShown = false; + } + break; + } + + vTaskDelay(portTICK_RATE_MS * 20); + } + } + + if (LED_INDICATOR_IS_SET(LedIndicatorType::Rewind)) + { + LED_INDICATOR_CLEAR(LedIndicatorType::Rewind); + for (uint8_t i = NUM_LEDS - 1; i > 0; i--) + { + leds[Led_Address(i)] = CRGB::Black; + FastLED.show(); + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { + break; + } + else + { + vTaskDelay(portTICK_RATE_MS * 30); + } + } + } + + if (LED_INDICATOR_IS_SET(LedIndicatorType::PlaylistProgress)) + { + LED_INDICATOR_CLEAR(LedIndicatorType::PlaylistProgress); + if (gPlayProperties.numberOfTracks > 1 && gPlayProperties.currentTrackNumber < gPlayProperties.numberOfTracks) + { + uint8_t numLedsToLight = map(gPlayProperties.currentTrackNumber, 0, gPlayProperties.numberOfTracks - 1, 0, NUM_LEDS); + FastLED.clear(); + for (uint8_t i = 0; i < numLedsToLight; i++) + { + leds[Led_Address(i)] = CRGB::Blue; + FastLED.show(); +#ifdef MEASURE_BATTERY_VOLTAGE + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || LED_INDICATOR_IS_SET(LedIndicatorType::VoltageWarning) || LED_INDICATOR_IS_SET(LedIndicatorType::Voltage) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#else + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#endif + break; + } + else + { + vTaskDelay(portTICK_RATE_MS * 30); + } + } + + for (uint8_t i = 0; i <= 100; i++) + { +#ifdef MEASURE_BATTERY_VOLTAGE + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || LED_INDICATOR_IS_SET(LedIndicatorType::VoltageWarning) || LED_INDICATOR_IS_SET(LedIndicatorType::Voltage) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#else + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#endif + break; + } + else + { + vTaskDelay(portTICK_RATE_MS * 15); + } + } + + for (uint8_t i = numLedsToLight; i > 0; i--) + { + leds[Led_Address(i) - 1] = CRGB::Black; + FastLED.show(); +#ifdef MEASURE_BATTERY_VOLTAGE + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || LED_INDICATOR_IS_SET(LedIndicatorType::VoltageWarning) || LED_INDICATOR_IS_SET(LedIndicatorType::Voltage) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#else + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#endif + break; + } + else + { + vTaskDelay(portTICK_RATE_MS * 30); + } + } + } + } + + switch (gPlayProperties.playMode) + { + case NO_PLAYLIST: // If no playlist is active (idle) + if (System_GetOperationMode() == OPMODE_BLUETOOTH) + { + idleColor = CRGB::Blue; + } + else + { + if (Wlan_IsConnected()) + { + idleColor = CRGB::White; + } + else + { + idleColor = CRGB::Green; + } + } + if (hlastVolume == AudioPlayer_GetCurrentVolume() && lastLedBrightness == Led_Brightness) + { + for (uint8_t i = 0; i < NUM_LEDS; i++) + { + FastLED.clear(); + if (Led_Address(i) == 0) + { // White if Wifi is enabled and blue if not + leds[0] = idleColor; + leds[NUM_LEDS / 4] = idleColor; + leds[NUM_LEDS / 2] = idleColor; + leds[NUM_LEDS / 4 * 3] = idleColor; + } + else + { + leds[Led_Address(i) % NUM_LEDS] = idleColor; + leds[(Led_Address(i) + NUM_LEDS / 4) % NUM_LEDS] = idleColor; + leds[(Led_Address(i) + NUM_LEDS / 2) % NUM_LEDS] = idleColor; + leds[(Led_Address(i) + NUM_LEDS / 4 * 3) % NUM_LEDS] = idleColor; + } + FastLED.show(); + for (uint8_t i = 0; i <= 50; i++) + { +#ifdef MEASURE_BATTERY_VOLTAGE + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || LED_INDICATOR_IS_SET(LedIndicatorType::VoltageWarning) || LED_INDICATOR_IS_SET(LedIndicatorType::Voltage) || gPlayProperties.playMode != NO_PLAYLIST || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#else + if (hlastVolume != AudioPlayer_GetCurrentVolume() || lastLedBrightness != Led_Brightness || LED_INDICATOR_IS_SET(LedIndicatorType::Error) || LED_INDICATOR_IS_SET(LedIndicatorType::Ok) || gPlayProperties.playMode != NO_PLAYLIST || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#endif + break; + } + else + { + vTaskDelay(portTICK_RATE_MS * 10); + } + } + } + } + break; + + case BUSY: // If uC is busy (parsing SD-card) + ledBusyShown = true; + for (uint8_t i = 0; i < NUM_LEDS; i++) + { + FastLED.clear(); + if (Led_Address(i) == 0) + { + leds[0] = CRGB::BlueViolet; + leds[NUM_LEDS / 4] = CRGB::BlueViolet; + leds[NUM_LEDS / 2] = CRGB::BlueViolet; + leds[NUM_LEDS / 4 * 3] = CRGB::BlueViolet; + } + else + { + leds[Led_Address(i) % NUM_LEDS] = CRGB::BlueViolet; + leds[(Led_Address(i) + NUM_LEDS / 4) % NUM_LEDS] = CRGB::BlueViolet; + leds[(Led_Address(i) + NUM_LEDS / 2) % NUM_LEDS] = CRGB::BlueViolet; + leds[(Led_Address(i) + NUM_LEDS / 4 * 3) % NUM_LEDS] = CRGB::BlueViolet; + } + FastLED.show(); + if (gPlayProperties.playMode != BUSY) + { + break; + } + vTaskDelay(portTICK_RATE_MS * 50); + } + break; + + default: // If playlist is active (doesn't matter which type) + if (!gPlayProperties.playlistFinished) + { +#ifdef MEASURE_BATTERY_VOLTAGE + if (gPlayProperties.pausePlay != lastPlayState || System_AreControlsLocked() != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || LED_INDICATOR_IS_SET(LedIndicatorType::VoltageWarning) || LED_INDICATOR_IS_SET(LedIndicatorType::Voltage) || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#else + if (gPlayProperties.pausePlay != lastPlayState || System_AreControlsLocked() != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || !gButtons[gShutdownButton].currentState || System_IsSleepRequested()) + { +#endif + lastPlayState = gPlayProperties.pausePlay; + lastLockState = System_AreControlsLocked(); + notificationShown = false; + volumeChangeShown = false; + if (ledBusyShown) + { + ledBusyShown = false; + FastLED.clear(); + FastLED.show(); + } + redrawProgress = true; + } + + if (gPlayProperties.playMode != WEBSTREAM) + { + if (gPlayProperties.currentRelPos != lastPos || redrawProgress) + { + redrawProgress = false; + lastPos = gPlayProperties.currentRelPos; + uint8_t numLedsToLight = map(gPlayProperties.currentRelPos, 0, 98, 0, NUM_LEDS); + FastLED.clear(); + for (uint8_t led = 0; led < numLedsToLight; led++) + { + if (System_AreControlsLocked()) + { + leds[Led_Address(led)] = CRGB::Red; + } + else if (!gPlayProperties.pausePlay) + { // Hue-rainbow + leds[Led_Address(led)].setHue((uint8_t)(85 - ((double)95 / NUM_LEDS) * led)); + } + } + if (gPlayProperties.pausePlay) + { + leds[Led_Address(0)] = CRGB::Orange; + leds[(Led_Address(NUM_LEDS / 4)) % NUM_LEDS] = CRGB::Orange; + leds[(Led_Address(NUM_LEDS / 2)) % NUM_LEDS] = CRGB::Orange; + leds[(Led_Address(NUM_LEDS / 4 * 3)) % NUM_LEDS] = CRGB::Orange; + break; + } + } + } + else + { // ... but do things a little bit different for Webstream as there's no progress available + if (lastSwitchTimestamp == 0 || (millis() - lastSwitchTimestamp >= ledSwitchInterval * 1000) || redrawProgress) + { + redrawProgress = false; + lastSwitchTimestamp = millis(); + FastLED.clear(); + if (ledPosWebstream + 1 < NUM_LEDS) + { + ledPosWebstream++; + } + else + { + ledPosWebstream = 0; + } + if (System_AreControlsLocked()) + { + leds[Led_Address(ledPosWebstream)] = CRGB::Red; + leds[(Led_Address(ledPosWebstream) + NUM_LEDS / 2) % NUM_LEDS] = CRGB::Red; + } + else if (!gPlayProperties.pausePlay) + { + leds[Led_Address(ledPosWebstream)].setHue(webstreamColor); + leds[(Led_Address(ledPosWebstream) + NUM_LEDS / 2) % NUM_LEDS].setHue(webstreamColor++); + } + else if (gPlayProperties.pausePlay) + { + leds[Led_Address(ledPosWebstream)] = CRGB::Orange; + leds[(Led_Address(ledPosWebstream) + NUM_LEDS / 2) % NUM_LEDS] = CRGB::Orange; + } + } + } + FastLED.show(); + vTaskDelay(portTICK_RATE_MS * 5); + } + } + //vTaskDelay(portTICK_RATE_MS * 10); + esp_task_wdt_reset(); + } + vTaskDelete(NULL); +#endif +} diff --git a/src/Led.h b/src/Led.h new file mode 100644 index 0000000..2fc338b --- /dev/null +++ b/src/Led.h @@ -0,0 +1,25 @@ +#pragma once + +typedef enum class LedIndicator +{ + BootComplete = 0, + Error, + Ok, + PlaylistProgress, + Rewind, + Voltage, + VoltageWarning +} LedIndicatorType; + +void Led_Init(void); +void Led_Exit(void); +void Led_Indicate(LedIndicatorType value); +void Led_SetPause(boolean value); +void Led_ResetToInitialBrightness(void); +void Led_ResetToNightBrightness(void); +uint8_t Led_GetBrightness(void); +void Led_SetBrightness(uint8_t value); +uint8_t AudioPlayer_GetInitVolume(void); +void AudioPlayer_SetInitVolume(uint8_t value); +uint8_t AudioPlayer_GetInitVolume(void); +void AudioPlayer_SetInitVolume(uint8_t value); \ No newline at end of file diff --git a/src/Log.cpp b/src/Log.cpp new file mode 100644 index 0000000..f7bed28 --- /dev/null +++ b/src/Log.cpp @@ -0,0 +1,46 @@ +#include +#include "settings.h" +#include "Log.h" +#include "MemX.h" +#include "LogRingBuffer.h" + +// Serial-logging buffer +uint8_t Log_BufferLength = 200; +char *Log_Buffer = (char *)calloc(Log_BufferLength, sizeof(char)); // Buffer for all log-messages + +static LogRingBuffer Log_RingBuffer; + +void Log_Init(void) +{ + Serial.begin(115200); + Log_Buffer = (char *)x_calloc(Log_BufferLength, sizeof(char)); // Buffer for all log-messages +} + +/* Wrapper-function for serial-logging (with newline) + _logBuffer: char* to log + _minLogLevel: loglevel configured for this message. + If (SERIAL_LOGLEVEL <= _minLogLevel) message will be logged +*/ +void Log_Println(const char *_logBuffer, const uint8_t _minLogLevel) +{ + if (SERIAL_LOGLEVEL >= _minLogLevel) + { + Serial.println(_logBuffer); + Log_RingBuffer.println(_logBuffer); + } +} + +/* Wrapper-function for serial-logging (without newline) */ +void Log_Print(const char *_logBuffer, const uint8_t _minLogLevel) +{ + if (SERIAL_LOGLEVEL >= _minLogLevel) + { + Serial.print(_logBuffer); + Log_RingBuffer.print(_logBuffer); + } +} + +String Log_GetRingBuffer(void) +{ + return Log_RingBuffer.get(); +} \ No newline at end of file diff --git a/src/Log.h b/src/Log.h new file mode 100644 index 0000000..c9f39ea --- /dev/null +++ b/src/Log.h @@ -0,0 +1,24 @@ +#pragma once +#include "LogMessages.h" + +// Loglevels available (don't change!) +#define LOGLEVEL_ERROR 1 // only errors +#define LOGLEVEL_NOTICE 2 // errors + important messages +#define LOGLEVEL_INFO 3 // infos + errors + important messages +#define LOGLEVEL_DEBUG 4 // almost everything + +extern uint8_t Log_BufferLength; +extern char *Log_Buffer; // Buffer for all log-messages + +/* Wrapper-function for serial-logging (with newline) + _logBuffer: char* to log + _minLogLevel: loglevel configured for this message. + If (_currentLogLevel <= _minLogLevel) message will be logged +*/ +void Log_Println(const char *_logBuffer, const uint8_t _minLogLevel); + +/* Wrapper-function for serial-logging (without newline) */ +void Log_Print(const char *_logBuffer, const uint8_t _minLogLevel); + +void Log_Init(void); +String Log_GetRingBuffer(void); diff --git a/src/LogMessages_DE.cpp b/src/LogMessages_DE.cpp new file mode 100644 index 0000000..fa01162 --- /dev/null +++ b/src/LogMessages_DE.cpp @@ -0,0 +1,189 @@ + +#include "settings.h" + +#if (LANGUAGE == 1) +#include "Log.h" + +const char stillOnlineMqtt[] PROGMEM = "MQTT: Bin noch online."; +const char tryConnectMqttS[] PROGMEM = "Versuche Verbindung zu MQTT-Broker aufzubauen"; +const char mqttOk[] PROGMEM = "MQTT-Session aufgebaut."; +const char sleepTimerEOP[] PROGMEM = "Sleep-Timer: Nach dem letzten Track der Playlist."; +const char sleepTimerEOT[] PROGMEM = "Sleep-Timer: Nach dem Ende des laufenden Tracks."; +const char sleepTimerStop[] PROGMEM = "Sleep-Timer wurde deaktiviert."; +const char sleepTimerEO5[] PROGMEM = "Sleep Timer: Nach Ende des Titels oder, wenn früher, Ende der Playlist"; +const char sleepTimerAlreadyStopped[] PROGMEM = "Sleep-Timer ist bereits deaktiviert."; +const char sleepTimerSetTo[] PROGMEM = "Sleep-Timer gesetzt auf"; +const char allowButtons[] PROGMEM = "Alle Tasten werden freigegeben."; +const char lockButtons[] PROGMEM = "Alle Tasten werden gesperrt."; +const char noPlaylistNotAllowedMqtt[] PROGMEM = "Playmode kann nicht auf 'Keine Playlist' gesetzt werden via MQTT."; +const char playmodeChangedMQtt[] PROGMEM = "Playmode per MQTT angepasst."; +const char noPlaymodeChangeIfIdle[] PROGMEM = "Playmode kann nicht verändert werden, wenn keine Playlist aktiv ist."; +const char noValidTopic[] PROGMEM = "Kein gültiges Topic"; +const char freePtr[] PROGMEM = "Ptr-Freigabe"; +const char freeMemory[] PROGMEM = "Freier Speicher"; +const char writeEntryToNvs[] PROGMEM = "Schreibe Eintrag in NVS"; +const char freeMemoryAfterFree[] PROGMEM = "Freier Speicher nach Aufräumen"; +const char releaseMemoryOfOldPlaylist[] PROGMEM = "Gebe Speicher der alten Playlist frei."; +const char dirOrFileDoesNotExist[] PROGMEM = "Datei oder Verzeichnis existiert nicht "; +const char unableToAllocateMemForPlaylist[] PROGMEM = "Speicher für Playlist konnte nicht allokiert werden!"; +const char unableToAllocateMem[] PROGMEM = "Speicher konnte nicht allokiert werden!"; +const char fileModeDetected[] PROGMEM = "Dateimodus erkannt."; +const char nameOfFileFound[] PROGMEM = "Gefundenes File"; +const char reallocCalled[] PROGMEM = "Speicher reallokiert."; +const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Speicher für lineare Playlist konnte nicht allokiert werden!"; +const char numberOfValidFiles[] PROGMEM = "Anzahl gültiger Files"; +const char newLoudnessReceivedQueue[] PROGMEM = "Neue Lautstärke empfangen via Queue"; +const char newCntrlReceivedQueue[] PROGMEM = "Kontroll-Kommando empfangen via Queue"; +const char newPlaylistReceived[] PROGMEM = "Neue Playlist empfangen"; +const char repeatTrackDueToPlaymode[] PROGMEM = "Wiederhole Titel aufgrund von Playmode."; +const char repeatPlaylistDueToPlaymode[] PROGMEM = "Wiederhole Playlist aufgrund von Playmode."; +const char cmndStop[] PROGMEM = "Kommando: Stop"; +const char cmndPause[] PROGMEM = "Kommando: Pause"; +const char cmndNextTrack[] PROGMEM = "Kommando: Nächster Titel"; +const char cmndPrevTrack[] PROGMEM = "Kommando: Vorheriger Titel"; +const char cmndFirstTrack[] PROGMEM = "Kommando: Erster Titel von Playlist"; +const char cmndLastTrack[] PROGMEM = "Kommando: Letzter Titel von Playlist"; +const char cmndDoesNotExist[] PROGMEM = "Dieses Kommando existiert nicht."; +const char lastTrackAlreadyActive[] PROGMEM = "Es wird bereits der letzte Track gespielt."; +const char firstTrackAlreadyActive[] PROGMEM = "Es wird bereits der erste Track gespielt."; +const char trackStartAudiobook[] PROGMEM = "Titel wird im Hörspielmodus von vorne gespielt."; +const char trackStart[] PROGMEM = "Titel wird von vorne gespielt."; +const char trackChangeWebstream[] PROGMEM = "Im Webradio-Modus kann nicht an den Anfang gesprungen werden."; +const char endOfPlaylistReached[] PROGMEM = "Ende der Playlist erreicht."; +const char trackStartatPos[] PROGMEM = "Titel wird abgespielt ab Position"; +const char rfidScannerReady[] PROGMEM = "RFID-Tags koennen jetzt gescannt werden..."; +const char rfidTagDetected[] PROGMEM = "RFID-Karte erkannt: "; +const char rfid15693TagDetected[] PROGMEM = "RFID-Karte (ISO-15693) erkannt: "; +const char rfidTagReceived[] PROGMEM = "RFID-Karte empfangen"; +const char rfidTagUnknownInNvs[] PROGMEM = "RFID-Karte ist im NVS nicht hinterlegt."; +const char goToSleepDueToIdle[] PROGMEM = "Gehe in Deep Sleep wegen Inaktivität..."; +const char goToSleepDueToTimer[] PROGMEM = "Gehe in Deep Sleep wegen Sleep Timer..."; +const char goToSleepNow[] PROGMEM = "Gehe jetzt in Deep Sleep!"; +const char maxLoudnessReached[] PROGMEM = "Maximale Lautstärke bereits erreicht!"; +const char minLoudnessReached[] PROGMEM = "Minimale Lautstärke bereits erreicht!"; +const char errorOccured[] PROGMEM = "Fehler aufgetreten!"; +const char noMp3FilesInDir[] PROGMEM = "Verzeichnis beinhaltet keine mp3-Files."; +const char modeSingleTrack[] PROGMEM = "Modus: Einzelner Track"; +const char modeSingleTrackLoop[] PROGMEM = "Modus: Einzelner Track in Endlosschleife"; +const char modeSingleAudiobook[] PROGMEM = "Modus: Hoerspiel"; +const char modeSingleAudiobookLoop[] PROGMEM = "Modus: Hoerspiel in Endlosschleife"; +const char modeAllTrackAlphSorted[] PROGMEM = "Modus: Spiele alle Tracks (alphabetisch sortiert) des Ordners"; +const char modeAllTrackRandom[] PROGMEM = "Modus: Alle Tracks eines Ordners zufällig"; +const char modeAllTrackAlphSortedLoop[] PROGMEM = "Modus: Alle Tracks eines Ordners sortiert (alphabetisch) in Endlosschleife"; +const char modeAllTrackRandomLoop[] PROGMEM = "Modus: Alle Tracks eines Ordners zufällig in Endlosschleife"; +const char modeWebstream[] PROGMEM = "Modus: Webstream"; +const char webstreamNotAvailable[] PROGMEM = "Aktuell kein Webstream möglich, da keine WLAN-Verbindung vorhanden!"; +const char modeDoesNotExist[] PROGMEM = "Abspielmodus existiert nicht!"; +const char modeRepeatNone[] PROGMEM = "Repeatmodus: Kein Repeat"; +const char modeRepeatTrack[] PROGMEM = "Repeatmodus: Aktueller Titel"; +const char modeRepeatPlaylist[] PROGMEM = "Repeatmodus: Gesamte Playlist"; +const char modeRepeatTracknPlaylist[] PROGMEM = "Repeatmodus: Track und Playlist"; +const char modificatorAllButtonsLocked[] PROGMEM = "Modifikator: Alle Tasten werden per RFID gesperrt."; +const char modificatorAllButtonsUnlocked[] PROGMEM = "Modifikator: Alle Tasten werden per RFID freigegeben."; +const char modificatorSleepd[] PROGMEM = "Modifikator: Sleep-Timer wieder deaktiviert."; +const char modificatorSleepTimer15[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (15 Minuten)."; +const char modificatorSleepTimer30[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (30 Minuten)."; +const char modificatorSleepTimer60[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (60 Minuten)."; +const char modificatorSleepTimer120[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (2 Stunden)."; +const char ledsDimmedToNightmode[] PROGMEM = "LEDs wurden auf Nachtmodus gedimmt."; +const char modificatorNotallowedWhenIdle[] PROGMEM = "Modifikator kann bei nicht aktivierter Playlist nicht angewendet werden."; +const char modificatorSleepAtEOT[] PROGMEM = "Modifikator: Sleep-Timer am Ende des Titels aktiviert."; +const char modificatorSleepAtEOTd[] PROGMEM = "Modifikator: Sleep-Timer am Ende des Titels deaktiviert."; +const char modificatorSleepAtEOP[] PROGMEM = "Modifikator: Sleep-Timer am Ende der Playlist aktiviert."; +const char modificatorSleepAtEOPd[] PROGMEM = "Modifikator: Sleep-Timer am Ende der Playlist deaktiviert."; +const char modificatorAllTrackAlphSortedLoop[] PROGMEM = "Modifikator: Alle Titel (alphabetisch sortiert) in Endlosschleife."; +const char modificatorAllTrackRandomLoop[] PROGMEM = "Modifikator: Alle Titel (zufällige Reihenfolge) in Endlosschleife."; +const char modificatorCurTrackLoop[] PROGMEM = "Modifikator: Aktueller Titel in Endlosschleife."; +const char modificatorCurAudiobookLoop[] PROGMEM = "Modifikator: Aktuelles Hörspiel in Endlosschleife."; +const char modificatorPlaylistLoopActive[] PROGMEM = "Modifikator: Alle Titel in Endlosschleife aktiviert."; +const char modificatorPlaylistLoopDeactive[] PROGMEM = "Modifikator: Alle Titel in Endlosschleife deaktiviert."; +const char modificatorTrackActive[] PROGMEM = "Modifikator: Titel in Endlosschleife aktiviert."; +const char modificatorTrackDeactive[] PROGMEM = "Modifikator: Titel in Endlosschleife deaktiviert."; +const char modificatorNotAllowed[] PROGMEM = "Modifikator konnte nicht angewendet werden."; +const char modificatorLoopRev[] PROGMEM = "Modifikator: Endlosschleife beendet."; +const char modificatorDoesNotExist[] PROGMEM = "Ein Karten-Modifikator existiert nicht vom Typ"; +const char errorOccuredNvs[] PROGMEM = "Es ist ein Fehler aufgetreten beim Lesen aus dem NVS!"; +const char statementsReceivedByServer[] PROGMEM = "Vom Server wurde Folgendes empfangen"; +const char savedSsidInNvs[] PROGMEM = "Speichere SSID in NVS"; +const char savedWifiPwdInNvs[] PROGMEM = "Speichere WLAN-Password in NVS"; +const char apReady[] PROGMEM = "Access-Point geöffnet"; +const char httpReady[] PROGMEM = "HTTP-Server gestartet."; +const char unableToMountSd[] PROGMEM = "SD-Karte konnte nicht gemountet werden."; +const char unableToCreateVolQ[] PROGMEM = "Konnte Volume-Queue nicht anlegen."; +const char unableToCreateRfidQ[] PROGMEM = "Konnte RFID-Queue nicht anlegen."; +const char unableToCreateMgmtQ[] PROGMEM = "Konnte Play-Management-Queue nicht anlegen."; +const char unableToCreatePlayQ[] PROGMEM = "Konnte Track-Queue nicht anlegen.."; +const char initialBrightnessfromNvs[] PROGMEM = "Initiale LED-Helligkeit wurde aus NVS geladen"; +const char wroteInitialBrightnessToNvs[] PROGMEM = "Initiale LED-Helligkeit wurde ins NVS geschrieben."; +const char restoredInitialBrightnessForNmFromNvs[] PROGMEM = "LED-Helligkeit für Nachtmodus wurde aus NVS geladen"; +const char wroteNmBrightnessToNvs[] PROGMEM = "LED-Helligkeit für Nachtmodus wurde ins NVS geschrieben."; +const char wroteFtpUserToNvs[] PROGMEM = "FTP-User wurde ins NVS geschrieben."; +const char restoredFtpUserFromNvs[] PROGMEM = "FTP-User wurde aus NVS geladen"; +const char wroteFtpPwdToNvs[] PROGMEM = "FTP-Passwort wurde ins NVS geschrieben."; +const char restoredFtpPwdFromNvs[] PROGMEM = "FTP-Passwort wurde aus NVS geladen"; +const char restoredMaxInactivityFromNvs[] PROGMEM = "Maximale Inaktivitätszeit wurde aus NVS geladen"; +const char wroteMaxInactivityToNvs[] PROGMEM = "Maximale Inaktivitätszeit wurde ins NVS geschrieben."; +const char restoredInitialLoudnessFromNvs[] PROGMEM = "Initiale Lautstärke wurde aus NVS geladen"; +const char wroteInitialLoudnessToNvs[] PROGMEM = "Initiale Lautstärke wurde ins NVS geschrieben."; +const char restoredMaxLoudnessForSpeakerFromNvs[] PROGMEM = "Maximale Lautstärke für Lautsprecher wurde aus NVS geladen"; +const char restoredMaxLoudnessForHeadphoneFromNvs[] PROGMEM = "Maximale Lautstärke für Kopfhörer wurde aus NVS geladen"; +const char wroteMaxLoudnessForSpeakerToNvs[] PROGMEM = "Maximale Lautstärke für Lautsprecher wurde ins NVS geschrieben."; +const char wroteMaxLoudnessForHeadphoneToNvs[] PROGMEM = "Maximale Lautstärke für Kopfhörer wurde ins NVS geschrieben."; +const char maxVolumeSet[] PROGMEM = "Maximale Lautstärke wurde gesetzt auf"; +const char wroteMqttFlagToNvs[] PROGMEM = "MQTT-Flag wurde ins NVS geschrieben."; +const char restoredMqttActiveFromNvs[] PROGMEM = "MQTT-Flag (aktiviert) wurde aus NVS geladen"; +const char restoredMqttDeactiveFromNvs[] PROGMEM = "MQTT-Flag (deaktiviert) wurde aus NVS geladen"; +const char wroteMqttServerToNvs[] PROGMEM = "MQTT-Server wurde ins NVS geschrieben."; +const char restoredMqttServerFromNvs[] PROGMEM = "MQTT-Server wurde aus NVS geladen"; +const char wroteMqttUserToNvs[] PROGMEM = "MQTT-User wurde ins NVS geschrieben."; +const char restoredMqttUserFromNvs[] PROGMEM = "MQTT-User wurde aus NVS geladen"; +const char wroteMqttPwdToNvs[] PROGMEM = "MQTT-Passwort wurde ins NVS geschrieben."; +const char restoredMqttPwdFromNvs[] PROGMEM = "MQTT-Passwort wurde aus NVS geladen"; +const char restoredMqttPortFromNvs[] PROGMEM = "MQTT-Port wurde aus NVS geladen"; +const char mqttWithPwd[] PROGMEM = "Verbinde zu MQTT-Server mit User und Passwort"; +const char mqttWithoutPwd[] PROGMEM = "Verbinde zu MQTT-Server ohne User und Passwort"; +const char ssidNotFoundInNvs[] PROGMEM = "SSID wurde im NVS nicht gefunden."; +const char wifiPwdNotFoundInNvs[] PROGMEM = "WLAN-Passwort wurde im NVS nicht gefunden."; +const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Statische WLAN-IP-Konfiguration wurde im NVS nicht gefunden."; +const char wifiHostnameNotSet[] PROGMEM = "Keine Hostname-Konfiguration im NVS gefunden."; +const char mqttConnFailed[] PROGMEM = "Verbindung fehlgeschlagen, versuche in Kürze erneut"; +const char restoredHostnameFromNvs[] PROGMEM = "Hostname aus NVS geladen"; +const char currentVoltageMsg[] PROGMEM = "Aktuelle Batteriespannung"; +const char voltageTooLow[] PROGMEM = "Batteriespannung niedrig"; +const char sdBootFailedDeepsleep[] PROGMEM = "Bootgang wegen SD fehlgeschlagen. Gehe in Deepsleep..."; +const char wifiEnabledAfterRestart[] PROGMEM = "WLAN wird aktiviert."; +const char wifiDisabledAfterRestart[] PROGMEM = "WLAN wird deaktiviert."; +const char voltageIndicatorLowFromNVS[] PROGMEM = "Unterer Spannungslevel (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; +const char voltageIndicatorHighFromNVS[] PROGMEM = "Oberer Spannungslevel (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; +const char voltageCheckIntervalFromNVS[] PROGMEM = "Zyklus für Spannungsmessung (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; +const char warningLowVoltageFromNVS[] PROGMEM = "Spannungslevel (Batterie) fuer Warnung via Neopixel aus NVS geladen"; +const char unableToRestoreLastRfidFromNVS[] PROGMEM = "Letzte RFID konnte nicht aus NVS geladen werden"; +const char restoredLastRfidFromNVS[] PROGMEM = "Letzte RFID wurde aus NVS geladen"; +const char failedOpenFileForWrite[] PROGMEM = "Öffnen der Datei für den Schreibvorgang fehlgeschlagen"; +const char fileWritten[] PROGMEM = "Datei geschrieben"; +const char writeFailed[] PROGMEM = "Schreibvorgang fehlgeschlagen"; +const char writingFile[] PROGMEM = "Schreibe Datei"; +const char failedToOpenFileForAppending[] PROGMEM = "Öffnen der Datei zum Schreiben der JSON-Datei fehlgeschlagen"; +const char listingDirectory[] PROGMEM = "Verzeichnisinhalt anzeigen"; +const char failedToOpenDirectory[] PROGMEM = "Öffnen des Verzeichnisses fehlgeschlagen"; +const char notADirectory[] PROGMEM = "Kein Verzeichnis"; +const char sdMountedMmc1BitMode[] PROGMEM = "Versuche SD-Karte wird im SD_MMC-Modus (1 Bit) zu mounten..."; +const char sdMountedSpiMode[] PROGMEM = "Versuche SD-Karte wird im SPI-Modus zu mounten..."; +const char backupRecoveryWebsite[] PROGMEM = "

Das Backup-File wird eingespielt...
Zur letzten Seite zurückkehren.

"; +const char restartWebsite[] PROGMEM = "

Der ESPuino wird neu gestartet...
Zur letzten Seite zurückkehren.

"; +const char shutdownWebsite[] PROGMEM = "

Der ESPuino wird ausgeschaltet...

"; +const char mqttMsgReceived[] PROGMEM = "MQTT-Nachricht empfangen"; +const char trackPausedAtPos[] PROGMEM = "Titel pausiert bei Position"; +const char freeHeapWithoutFtp[] PROGMEM = "Freier Heap-Speicher vor FTP-Instanzierung"; +const char freeHeapWithFtp[] PROGMEM = "Freier Heap-Speicher nach FTP-Instanzierung"; +const char freeHeapAfterSetup[] PROGMEM = "Freier Heap-Speicher nach Setup-Routine"; +const char tryStaticIpConfig[] PROGMEM = "Statische IP-Konfiguration wird durchgeführt..."; +const char staticIPConfigFailed[] PROGMEM = "Statische IP-Konfiguration fehlgeschlagen"; +const char wakeUpRfidNoIso14443[] PROGMEM = "ESP32 wurde vom Kartenleser aus dem Deepsleep aufgeweckt. Allerdings wurde keine ISO-14443-Karte gefunden. Gehe zurück in den Deepsleep..."; +const char lowPowerCardSuccess[] PROGMEM = "Kartenerkennung via 'low power' erfolgreich durchgeführt"; +const char rememberLastVolume[] PROGMEM = "Lautstärke vor dem letzten Shutdown wird wiederhergestellt. Dies überschreibt die Einstellung der initialen Lautstärke aus der GUI."; +const char unableToStartFtpServer[] PROGMEM = "Der FTP-Server konnte nicht gestartet werden. Entweder weil er ist bereits gestartet oder kein WLAN verfügbar ist."; +const char newPlayModeStereo[] PROGMEM = "Neuer Modus: stereo"; +const char newPlayModeMono[] PROGMEM = "Neuer Modus: mono"; + +#endif diff --git a/src/LogMessages_EN.cpp b/src/LogMessages_EN.cpp new file mode 100644 index 0000000..d0ec5bb --- /dev/null +++ b/src/LogMessages_EN.cpp @@ -0,0 +1,189 @@ + +#include "settings.h" + +#if (LANGUAGE == 2) +#include "Log.h" + +const char stillOnlineMqtt[] PROGMEM = "MQTT: still online."; +const char tryConnectMqttS[] PROGMEM = "Trying to connect to MQTT-broker"; +const char mqttOk[] PROGMEM = "MQTT-connection established."; +const char sleepTimerEOP[] PROGMEM = "Sleep-timer: after last track of playlist."; +const char sleepTimerEOT[] PROGMEM = "Sleep-timer: after end of current track."; +const char sleepTimerStop[] PROGMEM = "Sleep-timer has been disabled."; +const char sleepTimerEO5[] PROGMEM = "Sleep-timer: after five track or end of playlist - whatever is reached first"; +const char sleepTimerAlreadyStopped[] PROGMEM = "sleep-timer is already disabled."; +const char sleepTimerSetTo[] PROGMEM = "sleep-timer adjusted to"; +const char allowButtons[] PROGMEM = "Unlocking all keys."; +const char lockButtons[] PROGMEM = "Locking all keys."; +const char noPlaylistNotAllowedMqtt[] PROGMEM = "Playmode cannot be adjusted to 'no playlist' via MQTT."; +const char playmodeChangedMQtt[] PROGMEM = "Playlist adjusted via MQTT."; +const char noPlaymodeChangeIfIdle[] PROGMEM = "Playlist cannot be adjusted while no playlist is active."; +const char noValidTopic[] PROGMEM = "No valid MQTT-topic"; +const char freePtr[] PROGMEM = "Releasing Pointer"; +const char freeMemory[] PROGMEM = "Free memory"; +const char writeEntryToNvs[] PROGMEM = "Storing data to NVS"; +const char freeMemoryAfterFree[] PROGMEM = "Free memory after cleaning"; +const char releaseMemoryOfOldPlaylist[] PROGMEM = "Releasing memory of old playlist."; +const char dirOrFileDoesNotExist[] PROGMEM = "File of directory does not exist"; +const char unableToAllocateMemForPlaylist[] PROGMEM = "Unable to allocate memory for playlist!"; +const char unableToAllocateMem[] PROGMEM = "Unable to allocate memory!"; +const char fileModeDetected[] PROGMEM = "File-mode detected."; +const char nameOfFileFound[] PROGMEM = "File found"; +const char reallocCalled[] PROGMEM = "Reallocated memory."; +const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Unable to allocate memory for linear playlist!"; +const char numberOfValidFiles[] PROGMEM = "Number of valid files"; +const char newLoudnessReceivedQueue[] PROGMEM = "New volume received via queue"; +const char newCntrlReceivedQueue[] PROGMEM = "Control-command received via queue"; +const char newPlaylistReceived[] PROGMEM = "New playlist received"; +const char repeatTrackDueToPlaymode[] PROGMEM = "Repeating track due to playmode configured."; +const char repeatPlaylistDueToPlaymode[] PROGMEM = "Repeating playlist due to playmode configured."; +const char cmndStop[] PROGMEM = "Command: stop"; +const char cmndPause[] PROGMEM = "Command: pause"; +const char cmndNextTrack[] PROGMEM = "Command: next track"; +const char cmndPrevTrack[] PROGMEM = "Command: previous track"; +const char cmndFirstTrack[] PROGMEM = "Command: first track of playlist"; +const char cmndLastTrack[] PROGMEM = "Command: last track of playlist"; +const char cmndDoesNotExist[] PROGMEM = "Command requested does not exist."; +const char lastTrackAlreadyActive[] PROGMEM = "Already playing last track."; +const char firstTrackAlreadyActive[] PROGMEM = "Already playing first track."; +const char trackStartAudiobook[] PROGMEM = "Starting track in playmode from the very beginning."; +const char trackStart[] PROGMEM = "Starting track from the very beginning."; +const char trackChangeWebstream[] PROGMEM = "Playing from the very beginning is not possible while webradio-mode is active."; +const char endOfPlaylistReached[] PROGMEM = "Reached end of playlist."; +const char trackStartatPos[] PROGMEM = "Starting track at position"; +const char rfidScannerReady[] PROGMEM = "RFID-tags can now be applied..."; +const char rfidTagDetected[] PROGMEM = "RFID-tag detected: "; +const char rfid15693TagDetected[] PROGMEM = "RFID-ta (ISO-15693) detected: "; +const char rfidTagReceived[] PROGMEM = "RFID-tag received"; +const char rfidTagUnknownInNvs[] PROGMEM = "RFID-tag is unkown to NVS."; +const char goToSleepDueToIdle[] PROGMEM = "Going to deepsleep due to inactivity-timer..."; +const char goToSleepDueToTimer[] PROGMEM = "Going to deepsleep due to sleep timer..."; +const char goToSleepNow[] PROGMEM = "Going to deepsleep now!"; +const char maxLoudnessReached[] PROGMEM = "Already reached max volume!"; +const char minLoudnessReached[] PROGMEM = "Already reached min volume!"; +const char errorOccured[] PROGMEM = "Error occured!"; +const char noMp3FilesInDir[] PROGMEM = "Directory does not contain mp3-files."; +const char modeSingleTrack[] PROGMEM = "Mode: Single track"; +const char modeSingleTrackLoop[] PROGMEM = "Mode: single track as infinite loop"; +const char modeSingleAudiobook[] PROGMEM = "Mode: audiobook"; +const char modeSingleAudiobookLoop[] PROGMEM = "Mode: audiobook as infinite loop"; +const char modeAllTrackAlphSorted[] PROGMEM = "Mode: all tracks (in alph. order) of directory"; +const char modeAllTrackRandom[] PROGMEM = "Mode: all tracks (in random. order) of directory"; +const char modeAllTrackAlphSortedLoop[] PROGMEM = "Mode: all tracks (in alph. order) of directory as infinite loop"; +const char modeAllTrackRandomLoop[] PROGMEM = "Mode: all tracks (in random order) of directory as infinite loop"; +const char modeWebstream[] PROGMEM = "Mode: webstream"; +const char webstreamNotAvailable[] PROGMEM = "Unable to access webstream as no wifi-connection is available!"; +const char modeDoesNotExist[] PROGMEM = "Playmode does not exist!"; +const char modeRepeatNone[] PROGMEM = "Repeatmode: no repeat"; +const char modeRepeatTrack[] PROGMEM = "Repeatmode: current track"; +const char modeRepeatPlaylist[] PROGMEM = "Repeatmode: whole playlist"; +const char modeRepeatTracknPlaylist[] PROGMEM = "Repeatmode: track and playlist"; +const char modificatorAllButtonsLocked[] PROGMEM = "Modificator: locking all keys via RFID-tag."; +const char modificatorAllButtonsUnlocked[] PROGMEM = "Modificator: unlocking all keys via RFID-tag."; +const char modificatorSleepd[] PROGMEM = "Modificator: sleep-Timer deactivated."; +const char modificatorSleepTimer15[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (15 minutes)."; +const char modificatorSleepTimer30[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (30 minutes)."; +const char modificatorSleepTimer60[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (60 minutes)."; +const char modificatorSleepTimer120[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (2 hours)."; +const char ledsDimmedToNightmode[] PROGMEM = "Dimmed LEDs to nightmode."; +const char modificatorNotallowedWhenIdle[] PROGMEM = "Modificator cannot be applied while playlist is inactive."; +const char modificatorSleepAtEOT[] PROGMEM = "Modificator: adjusted sleep-timer to after end of current track."; +const char modificatorSleepAtEOTd[] PROGMEM = "Modificator: disabled sleep-timer after end of current track."; +const char modificatorSleepAtEOP[] PROGMEM = "Modificator: adjusted sleep-timer to after end of playlist."; +const char modificatorSleepAtEOPd[] PROGMEM = "Modificator: disabled sleep-timer after end of playlist."; +const char modificatorAllTrackAlphSortedLoop[] PROGMEM = "Modificator: adjusted to all tracks (in alph. order) as infinite loop."; +const char modificatorAllTrackRandomLoop[] PROGMEM = "Modificator: adjusted to all tracks (in random order) as infinite loop."; +const char modificatorCurTrackLoop[] PROGMEM = "Modificator: adjusted to current track as infinite loop."; +const char modificatorCurAudiobookLoop[] PROGMEM = "Modificator: adjusted to current audiobook as infinite loop."; +const char modificatorPlaylistLoopActive[] PROGMEM = "Modificator: adjusted to all tracks as infinite loop."; +const char modificatorPlaylistLoopDeactive[] PROGMEM = "Modificator: disabled all tracks as infinite loop."; +const char modificatorTrackActive[] PROGMEM = "Modificator: adjusted to current track as infinite loop."; +const char modificatorTrackDeactive[] PROGMEM = "Modificator: disabled current track as infinite loop."; +const char modificatorNotAllowed[] PROGMEM = "Unable to apply modificator."; +const char modificatorLoopRev[] PROGMEM = "Modificator: infinite loop ended."; +const char modificatorDoesNotExist[] PROGMEM = "This type of card-modificator does not exist"; +const char errorOccuredNvs[] PROGMEM = "Error occured while reading from NVS!"; +const char statementsReceivedByServer[] PROGMEM = "Data received from server"; +const char savedSsidInNvs[] PROGMEM = "Storing SSID to NVS"; +const char savedWifiPwdInNvs[] PROGMEM = "Storing wifi-password to NVS"; +const char apReady[] PROGMEM = "Started wifi-access-point"; +const char httpReady[] PROGMEM = "Started HTTP-server."; +const char unableToMountSd[] PROGMEM = "Unable to mount sd-card."; +const char unableToCreateVolQ[] PROGMEM = "Unable to create volume-queue."; +const char unableToCreateRfidQ[] PROGMEM = "Unable to create RFID-queue."; +const char unableToCreateMgmtQ[] PROGMEM = "Unable to play-management-queue."; +const char unableToCreatePlayQ[] PROGMEM = "Unable to create track-queue.."; +const char initialBrightnessfromNvs[] PROGMEM = "Restoring initial LED-brightness from NVS"; +const char wroteInitialBrightnessToNvs[] PROGMEM = "Storing initial LED-brightness to NVS."; +const char restoredInitialBrightnessForNmFromNvs[] PROGMEM = "Restored LED-brightness for nightmode from NVS"; +const char wroteNmBrightnessToNvs[] PROGMEM = "Stored LED-brightness for nightmode to NVS."; +const char wroteFtpUserToNvs[] PROGMEM = "Stored FTP-user to NVS."; +const char restoredFtpUserFromNvs[] PROGMEM = "Restored FTP-user from NVS"; +const char wroteFtpPwdToNvs[] PROGMEM = "Stored FTP-password to NVS."; +const char restoredFtpPwdFromNvs[] PROGMEM = "Restored FTP-password from NVS"; +const char restoredMaxInactivityFromNvs[] PROGMEM = "Restored maximum inactivity-time from NVS."; +const char wroteMaxInactivityToNvs[] PROGMEM = "Stored maximum inactivity-time to NVS."; +const char restoredInitialLoudnessFromNvs[] PROGMEM = "Restored initial volume from NVS"; +const char wroteInitialLoudnessToNvs[] PROGMEM = "Stored initial volume to NVS."; +const char restoredMaxLoudnessForSpeakerFromNvs[] PROGMEM = "Restored maximum volume for speaker from NVS"; +const char restoredMaxLoudnessForHeadphoneFromNvs[] PROGMEM = "Restored maximum volume for headphone from NVS"; +const char wroteMaxLoudnessForSpeakerToNvs[] PROGMEM = "Wrote maximum volume for speaker to NVS."; +const char wroteMaxLoudnessForHeadphoneToNvs[] PROGMEM = "Wrote maximum volume for headphone to NVS."; +const char maxVolumeSet[] PROGMEM = "Maximum volume set to"; +const char wroteMqttFlagToNvs[] PROGMEM = "Stored MQTT-flag to NVS."; +const char restoredMqttActiveFromNvs[] PROGMEM = "Restored MQTT-flag (enabled) from NVS"; +const char restoredMqttDeactiveFromNvs[] PROGMEM = "Restored MQTT-flag (disabled) from NVS"; +const char wroteMqttServerToNvs[] PROGMEM = "Stored MQTT-server to NVS."; +const char restoredMqttServerFromNvs[] PROGMEM = "Restored MQTT-Server from NVS"; +const char wroteMqttUserToNvs[] PROGMEM = "Stored MQTT-user to NVS."; +const char restoredMqttUserFromNvs[] PROGMEM = "Restored MQTT-user from NVS"; +const char wroteMqttPwdToNvs[] PROGMEM = "Stored MQTT-password to NVS."; +const char restoredMqttPwdFromNvs[] PROGMEM = "Restored MQTT-password from NVS"; +const char restoredMqttPortFromNvs[] PROGMEM = "Restored MQTT-port from NVS"; +const char mqttWithPwd[] PROGMEM = "Try to connect to MQTT-server with user und password"; +const char mqttWithoutPwd[] PROGMEM = "Try to connect to MQTT-server without user und password"; +const char ssidNotFoundInNvs[] PROGMEM = "Unable to find SSID to NVS."; +const char wifiPwdNotFoundInNvs[] PROGMEM = "Unable to find wifi-password to NVS."; +const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Unable to find wifi-ip-configuration to NVS."; +const char wifiHostnameNotSet[] PROGMEM = "Unable to find hostname-configuration to NVS."; +const char mqttConnFailed[] PROGMEM = "Unable to establish mqtt-connection, trying again..."; +const char restoredHostnameFromNvs[] PROGMEM = "Restored hostname from NVS"; +const char currentVoltageMsg[] PROGMEM = "Current battery-voltage"; +const char voltageTooLow[] PROGMEM = "Low battery-voltage"; +const char sdBootFailedDeepsleep[] PROGMEM = "Failed to boot due to SD. Will go to deepsleep..."; +const char wifiEnabledAfterRestart[] PROGMEM = "WiFi will be enabled."; +const char wifiDisabledAfterRestart[] PROGMEM = "WiFi will be disabled ."; +const char voltageIndicatorLowFromNVS[] PROGMEM = "Restored lower voltage-level for Neopixel-display from NVS"; +const char voltageIndicatorHighFromNVS[] PROGMEM = "Restored upper voltage-level for Neopixel-display from NVS"; +const char voltageCheckIntervalFromNVS[] PROGMEM = "Restored interval of battery-measurement or Neopixel-display from NVS"; +const char warningLowVoltageFromNVS[] PROGMEM = "Restored battery-voltage-level for warning via Neopixel from NVS"; +const char unableToRestoreLastRfidFromNVS[] PROGMEM = "Unable to restore last RFID from NVS"; +const char restoredLastRfidFromNVS[] PROGMEM = "Restored last RFID from NVS"; +const char failedOpenFileForWrite[] PROGMEM = "Failed to open file for writing"; +const char fileWritten[] PROGMEM = "File written"; +const char writeFailed[] PROGMEM = "Write failed"; +const char writingFile[] PROGMEM = "Writing file"; +const char failedToOpenFileForAppending[] PROGMEM = "Failed to open file for appending"; +const char listingDirectory[] PROGMEM = "Listing directory"; +const char failedToOpenDirectory[] PROGMEM = "Failed to open directory"; +const char notADirectory[] PROGMEM = "Not a directory"; +const char sdMountedMmc1BitMode[] PROGMEM = "SD card mounted in SPI-mode configured..."; +const char sdMountedSpiMode[] PROGMEM = "Mounting SD card in SPI-mode..."; +const char backupRecoveryWebsite[] PROGMEM = "

Backup-file is being applied...
Back to last page.

"; +const char restartWebsite[] PROGMEM = "

ESPuino is being restarted...
Back to last page.

"; +const char shutdownWebsite[] PROGMEM = "

Der ESPuino is being shutdown...

"; +const char mqttMsgReceived[] PROGMEM = "MQTT-message received"; +const char trackPausedAtPos[] PROGMEM = "Track paused at position"; +const char freeHeapWithoutFtp[] PROGMEM = "Free heap before FTP-allocation"; +const char freeHeapWithFtp[] PROGMEM = "Free heap after FTP-allocation"; +const char freeHeapAfterSetup[] PROGMEM = "Free heap after setup"; +const char tryStaticIpConfig[] PROGMEM = "Performing IP-configuration..."; +const char staticIPConfigFailed[] PROGMEM = "IP-configuration failed"; +const char wakeUpRfidNoIso14443[] PROGMEM = "Wakeup caused by low power card-detection. RF-field changed but no ISO-14443 card on reader was found. So I'll return back to sleep now..."; +const char lowPowerCardSuccess[] PROGMEM = "Switch to low power card-detection: success"; +const char rememberLastVolume[] PROGMEM = "Restored volume used before last shutdown. This overwrites the initial volume configured via webgui."; +const char unableToStartFtpServer[] PROGMEM = "FTP-server cannot be started. This is because FTP-service is already active of because WiFi is unavailable."; +const char newPlayModeStereo[] PROGMEM = "New mode: stereo"; +const char newPlayModeMono[] PROGMEM = "New mode: mono"; + +#endif diff --git a/src/MemX.cpp b/src/MemX.cpp new file mode 100644 index 0000000..97411e9 --- /dev/null +++ b/src/MemX.cpp @@ -0,0 +1,54 @@ +#include +#include "MemX.h" + +// Wraps strdup(). Without PSRAM, strdup is called => so heap is used. +// With PSRAM being available, the same is done what strdup() does, but with allocation on PSRAM. +char * x_strdup(const char *_str) { + if (!psramInit()) { + return strdup(_str); + } else { + char *dst = (char *) ps_malloc(strlen (_str) + 1); + if (dst == NULL) { + return NULL; + } + strcpy(dst, _str); + return dst; + } +} + + +// Wraps strndup(). Without PSRAM, strdup is called => so heap is used. +// With PSRAM being available, the same is done what strndup() does, but with allocation on PSRAM. +char * x_strndup(const char *_str, uint32_t _len) { + if (!psramInit()) { + return strndup(_str, _len); + } else { + char *dst = (char *) ps_malloc(_len + 1); + if (dst == NULL) { + return NULL; + } + strncpy(dst, _str, _len); + dst[_len] = '\0'; + return dst; + } +} + + +// Wraps ps_malloc() and malloc(). Selection depends on whether PSRAM is available or not. +char * x_malloc(uint32_t _allocSize) { + if (psramInit()) { + return (char *) ps_malloc(_allocSize); + } else { + return (char *) malloc(_allocSize); + } +} + + +// Wraps ps_calloc() and calloc(). Selection depends on whether PSRAM is available or not. +char * x_calloc(uint32_t _allocSize, uint32_t _unitSize) { + if (psramInit()) { + return (char *) ps_calloc(_allocSize, _unitSize); + } else { + return (char *) calloc(_allocSize, _unitSize); + } +} \ No newline at end of file diff --git a/src/MemX.h b/src/MemX.h new file mode 100644 index 0000000..9f976eb --- /dev/null +++ b/src/MemX.h @@ -0,0 +1,6 @@ +#pragma once + +char *x_calloc(uint32_t _allocSize, uint32_t _unitSize); +char *x_malloc(uint32_t _allocSize); +char *x_strdup(const char *_str); +char *x_strndup(const char *_str, uint32_t _len); \ No newline at end of file diff --git a/src/Mqtt.cpp b/src/Mqtt.cpp new file mode 100644 index 0000000..3a3eabe --- /dev/null +++ b/src/Mqtt.cpp @@ -0,0 +1,516 @@ +#include +#include +#include "settings.h" +#include "Mqtt.h" +#include "AudioPlayer.h" +#include "Led.h" +#include "Log.h" +#include "MemX.h" +#include "System.h" +#include "Queues.h" +#include "Wlan.h" + +#ifdef MQTT_ENABLE +#define MQTT_SOCKET_TIMEOUT 1 // https://github.com/knolleary/pubsubclient/issues/403 +#include +#endif + +constexpr uint8_t stillOnlineInterval = 60u; // Interval 'I'm still alive' is sent via MQTT (in seconds) + +// MQTT-helper +#ifdef MQTT_ENABLE +static WiFiClient Mqtt_WifiClient; +static PubSubClient Mqtt_PubSubClient(Mqtt_WifiClient); +#endif + +// Please note: all of them are defaults that can be changed later via GUI +char *gMqttServer = x_strndup((char *)"192.168.2.43", mqttServerLength); // IP-address of MQTT-server (if not found in NVS this one will be taken) +char *gMqttUser = x_strndup((char *)"mqtt-user", mqttUserLength); // MQTT-user +char *gMqttPassword = x_strndup((char *)"mqtt-password", mqttPasswordLength); // MQTT-password*/ +uint16_t gMqttPort = 1883; // MQTT-Port + +// MQTT +static bool Mqtt_Enabled = true; + +static void Mqtt_ClientCallback(const char *topic, const byte *payload, uint32_t length); +static bool Mqtt_Reconnect(void); +static void Mqtt_PostHeartbeatViaMqtt(void); + +void Mqtt_Init() +{ +#ifdef MQTT_ENABLE + // Get MQTT-enable from NVS + uint8_t nvsEnableMqtt = gPrefsSettings.getUChar("enableMQTT", 99); + switch (nvsEnableMqtt) + { + case 99: + gPrefsSettings.putUChar("enableMQTT", Mqtt_Enabled); + Log_Println((char *)FPSTR(wroteMqttFlagToNvs), LOGLEVEL_ERROR); + break; + case 1: + Mqtt_Enabled = nvsEnableMqtt; + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(restoredMqttActiveFromNvs), nvsEnableMqtt); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + break; + case 0: + Mqtt_Enabled = nvsEnableMqtt; + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(restoredMqttDeactiveFromNvs), nvsEnableMqtt); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + break; + } + + // Get MQTT-server from NVS + String nvsMqttServer = gPrefsSettings.getString("mqttServer", "-1"); + if (!nvsMqttServer.compareTo("-1")) + { + gPrefsSettings.putString("mqttServer", (String)gMqttServer); + Log_Println((char *)FPSTR(wroteMqttServerToNvs), LOGLEVEL_ERROR); + } + else + { + strncpy(gMqttServer, nvsMqttServer.c_str(), mqttServerLength); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(restoredMqttServerFromNvs), nvsMqttServer.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + + // Get MQTT-user from NVS + String nvsMqttUser = gPrefsSettings.getString("mqttUser", "-1"); + if (!nvsMqttUser.compareTo("-1")) + { + gPrefsSettings.putString("mqttUser", (String)gMqttUser); + Log_Println((char *)FPSTR(wroteMqttUserToNvs), LOGLEVEL_ERROR); + } + else + { + strncpy(gMqttUser, nvsMqttUser.c_str(), mqttUserLength); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(restoredMqttUserFromNvs), nvsMqttUser.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + + // Get MQTT-password from NVS + String nvsMqttPassword = gPrefsSettings.getString("mqttPassword", "-1"); + if (!nvsMqttPassword.compareTo("-1")) + { + gPrefsSettings.putString("mqttPassword", (String)gMqttPassword); + Log_Println((char *)FPSTR(wroteMqttPwdToNvs), LOGLEVEL_ERROR); + } + else + { + strncpy(gMqttPassword, nvsMqttPassword.c_str(), mqttPasswordLength); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(restoredMqttPwdFromNvs), nvsMqttPassword.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + + // Get MQTT-password from NVS + uint32_t nvsMqttPort = gPrefsSettings.getUInt("mqttPort", 99999); + if (nvsMqttPort == 99999) + { + gPrefsSettings.putUInt("mqttPort", gMqttPort); + } + else + { + gMqttPort = nvsMqttPort; + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(restoredMqttPortFromNvs), gMqttPort); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + + // Only enable MQTT if requested + if (Mqtt_Enabled) + { + Mqtt_PubSubClient.setServer(gMqttServer, gMqttPort); + Mqtt_PubSubClient.setCallback(Mqtt_ClientCallback); + } +#endif +} + +void Mqtt_Cyclic(void) +{ +#ifdef MQTT_ENABLE + if (Mqtt_Enabled && Wlan_IsConnected()) + { + Mqtt_Reconnect(); + Mqtt_PubSubClient.loop(); + Mqtt_PostHeartbeatViaMqtt(); + } +#endif +} + +void Mqtt_Exit(void) +{ +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicState), "Offline", false); + publishMqtt((char *)FPSTR(topicTrackState), "---", false); + Mqtt_PubSubClient.disconnect(); +#endif +} + +bool Mqtt_IsEnabled(void) +{ + return Mqtt_Enabled; +} + +/* Wrapper-functions for MQTT-publish */ +bool publishMqtt(const char *topic, const char *payload, bool retained) +{ +#ifdef MQTT_ENABLE + if (strcmp(topic, "") != 0) + { + if (Mqtt_PubSubClient.connected()) + { + Mqtt_PubSubClient.publish(topic, payload, retained); + delay(100); + return true; + } + } +#endif + return false; +} + +bool publishMqtt(const char *topic, int32_t payload, bool retained) +{ +#ifdef MQTT_ENABLE + char buf[11]; + snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%d", payload); + return publishMqtt(topic, buf, retained); +#else + return false; +#endif +} + +bool publishMqtt(const char *topic, unsigned long payload, bool retained) +{ +#ifdef MQTT_ENABLE + char buf[11]; + snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%lu", payload); + return publishMqtt(topic, buf, retained); +#else + return false; +#endif +} + +bool publishMqtt(const char *topic, uint32_t payload, bool retained) +{ +#ifdef MQTT_ENABLE + char buf[11]; + snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%u", payload); + return publishMqtt(topic, buf, retained); +#else + return false; +#endif +} + +/* Cyclic posting via MQTT that ESP is still alive. Use case: when ESPuino is switched off, it will post via + MQTT it's gonna be offline now. But when unplugging ESPuino e.g. openHAB doesn't know ESPuino is offline. + One way to recognize this is to determine, when a topic has been updated for the last time. So by + telling openHAB connection is timed out after 2mins for instance, this is the right topic to check for. */ +void Mqtt_PostHeartbeatViaMqtt(void) +{ +#ifdef MQTT_ENABLE + static unsigned long lastOnlineTimestamp = 0u; + + if (millis() - lastOnlineTimestamp >= stillOnlineInterval * 1000) + { + lastOnlineTimestamp = millis(); + if (publishMqtt((char *)FPSTR(topicState), "Online", false)) + { + Log_Println((char *)FPSTR(stillOnlineMqtt), LOGLEVEL_DEBUG); + } + } +#endif +} + +/* Connects/reconnects to MQTT-Broker unless connection is not already available. + Manages MQTT-subscriptions. +*/ +bool Mqtt_Reconnect() +{ +#ifdef MQTT_ENABLE + static uint32_t mqttLastRetryTimestamp = 0u; + uint8_t connect = false; + uint8_t i = 0; + + if (!mqttLastRetryTimestamp || millis() - mqttLastRetryTimestamp >= mqttRetryInterval * 1000) + { + mqttLastRetryTimestamp = millis(); + } + else + { + return false; + } + + while (!Mqtt_PubSubClient.connected() && i < mqttMaxRetriesPerInterval) + { + i++; + snprintf(Log_Buffer, Log_BufferLength, "%s %s", (char *)FPSTR(tryConnectMqttS), gMqttServer); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + + // Try to connect to MQTT-server. If username AND password are set, they'll be used + if (strlen(gMqttUser) < 1 || strlen(gMqttPassword) < 1) + { + Log_Println((char *)FPSTR(mqttWithoutPwd), LOGLEVEL_NOTICE); + if (Mqtt_PubSubClient.connect(DEVICE_HOSTNAME)) + { + connect = true; + } + } + else + { + Log_Println((char *)FPSTR(mqttWithPwd), LOGLEVEL_NOTICE); + if (Mqtt_PubSubClient.connect(DEVICE_HOSTNAME, gMqttUser, gMqttPassword)) + { + connect = true; + } + } + if (connect) + { + Log_Println((char *)FPSTR(mqttOk), LOGLEVEL_NOTICE); + + // Deepsleep-subscription + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicSleepCmnd)); + + // RFID-Tag-ID-subscription + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicRfidCmnd)); + + // Loudness-subscription + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicLoudnessCmnd)); + + // Sleep-Timer-subscription + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicSleepTimerCmnd)); + + // Next/previous/stop/play-track-subscription + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicTrackControlCmnd)); + + // Lock controls + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicLockControlsCmnd)); + + // Current repeat-Mode + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicRepeatModeCmnd)); + + // LED-brightness + Mqtt_PubSubClient.subscribe((char *)FPSTR(topicLedBrightnessCmnd)); + + // Publish some stuff + publishMqtt((char *)FPSTR(topicState), "Online", false); + publishMqtt((char *)FPSTR(topicTrackState), "---", false); + publishMqtt((char *)FPSTR(topicLoudnessState), AudioPlayer_GetCurrentVolume(), false); + publishMqtt((char *)FPSTR(topicSleepTimerState), System_GetSleepTimerTimeStamp(), false); + publishMqtt((char *)FPSTR(topicLockControlsState), "OFF", false); + publishMqtt((char *)FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); + publishMqtt((char *)FPSTR(topicRepeatModeState), 0, false); + publishMqtt((char *)FPSTR(topicCurrentIPv4IP), Wlan_GetIpAddress().c_str(), false); + + return Mqtt_PubSubClient.connected(); + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "%s: rc=%i (%d / %d)", (char *)FPSTR(mqttConnFailed), Mqtt_PubSubClient.state(), i, mqttMaxRetriesPerInterval); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } + } + return false; +#endif +} + +// Is called if there's a new MQTT-message for us +void Mqtt_ClientCallback(const char *topic, const byte *payload, uint32_t length) +{ +#ifdef MQTT_ENABLE + char *receivedString = x_strndup((char *)payload, length); + char *mqttTopic = x_strdup(topic); + + snprintf(Log_Buffer, Log_BufferLength, "%s: [Topic: %s] [Command: %s]", (char *)FPSTR(mqttMsgReceived), mqttTopic, receivedString); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + + // Go to sleep? + if (strcmp_P(topic, topicSleepCmnd) == 0) + { + if ((strcmp(receivedString, "OFF") == 0) || (strcmp(receivedString, "0") == 0)) + { + System_RequestSleep(); + } + } + + // New track to play? Take RFID-ID as input + else if (strcmp_P(topic, topicRfidCmnd) == 0) + { + char *_rfidId = x_strdup(receivedString); + xQueueSend(gRfidCardQueue, &_rfidId, 0); + //free(_rfidId); + } + // Loudness to change? + else if (strcmp_P(topic, topicLoudnessCmnd) == 0) + { + unsigned long vol = strtoul(receivedString, NULL, 10); + AudioPlayer_VolumeToQueueSender(vol, true); + } + // Modify sleep-timer? + else if (strcmp_P(topic, topicSleepTimerCmnd) == 0) + { + if (gPlayProperties.playMode == NO_PLAYLIST) + { // Don't allow sleep-modications if no playlist is active + Log_Println((char *)FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_INFO); + publishMqtt((char *)FPSTR(topicSleepState), 0, false); + System_IndicateError(); + return; + } + if (strcmp(receivedString, "EOP") == 0) + { + gPlayProperties.sleepAfterPlaylist = true; + Log_Println((char *)FPSTR(sleepTimerEOP), LOGLEVEL_NOTICE); + System_IndicateOk(); + return; + } + else if (strcmp(receivedString, "EOT") == 0) + { + gPlayProperties.sleepAfterCurrentTrack = true; + Log_Println((char *)FPSTR(sleepTimerEOT), LOGLEVEL_NOTICE); + System_IndicateOk(); + return; + } + else if (strcmp(receivedString, "EO5T") == 0) + { + if ((gPlayProperties.numberOfTracks - 1) >= (gPlayProperties.currentTrackNumber + 5)) + { + gPlayProperties.playUntilTrackNumber = gPlayProperties.currentTrackNumber + 5; + } + else + { + gPlayProperties.sleepAfterPlaylist = true; + } + Log_Println((char *)FPSTR(sleepTimerEO5), LOGLEVEL_NOTICE); + System_IndicateOk(); + return; + } + else if (strcmp(receivedString, "0") == 0) + { + if (System_IsSleepTimerEnabled()) + { + System_DisableSleepTimer(); + Log_Println((char *)FPSTR(sleepTimerStop), LOGLEVEL_NOTICE); + System_IndicateOk(); + publishMqtt((char *)FPSTR(topicSleepState), 0, false); + return; + } + else + { + Log_Println((char *)FPSTR(sleepTimerAlreadyStopped), LOGLEVEL_INFO); + System_IndicateError(); + return; + } + } + System_SetSleepTimer((uint8_t)strtoul(receivedString, NULL, 10)); + snprintf(Log_Buffer, Log_BufferLength, "%s: %u Minute(n)", (char *)FPSTR(sleepTimerSetTo), System_GetSleepTimer()); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + System_IndicateOk(); + + gPlayProperties.sleepAfterPlaylist = false; + gPlayProperties.sleepAfterCurrentTrack = false; + } + // Track-control (pause/play, stop, first, last, next, previous) + else if (strcmp_P(topic, topicTrackControlCmnd) == 0) + { + uint8_t controlCommand = strtoul(receivedString, NULL, 10); + AudioPlayer_TrackControlToQueueSender(controlCommand); + } + + // Check if controls should be locked + else if (strcmp_P(topic, topicLockControlsCmnd) == 0) + { + if (strcmp(receivedString, "OFF") == 0) + { + System_SetLockControls(false); + Log_Println((char *)FPSTR(allowButtons), LOGLEVEL_NOTICE); + System_IndicateOk(); + } + else if (strcmp(receivedString, "ON") == 0) + { + System_SetLockControls(true); + Log_Println((char *)FPSTR(lockButtons), LOGLEVEL_NOTICE); + System_IndicateOk(); + } + } + + // Check if playmode should be adjusted + else if (strcmp_P(topic, topicRepeatModeCmnd) == 0) + { + char rBuf[2]; + uint8_t repeatMode = strtoul(receivedString, NULL, 10); + Serial.printf("Repeat: %d", repeatMode); + if (gPlayProperties.playMode != NO_PLAYLIST) + { + if (gPlayProperties.playMode == NO_PLAYLIST) + { + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); + Log_Println((char *)FPSTR(noPlaylistNotAllowedMqtt), LOGLEVEL_ERROR); + System_IndicateError(); + } + else + { + switch (repeatMode) + { + case NO_REPEAT: + gPlayProperties.repeatCurrentTrack = false; + gPlayProperties.repeatPlaylist = false; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); + Log_Println((char *)FPSTR(modeRepeatNone), LOGLEVEL_INFO); + System_IndicateOk(); + break; + + case TRACK: + gPlayProperties.repeatCurrentTrack = true; + gPlayProperties.repeatPlaylist = false; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); + Log_Println((char *)FPSTR(modeRepeatTrack), LOGLEVEL_INFO); + System_IndicateOk(); + break; + + case PLAYLIST: + gPlayProperties.repeatCurrentTrack = false; + gPlayProperties.repeatPlaylist = true; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); + Log_Println((char *)FPSTR(modeRepeatPlaylist), LOGLEVEL_INFO); + System_IndicateOk(); + break; + + case TRACK_N_PLAYLIST: + gPlayProperties.repeatCurrentTrack = true; + gPlayProperties.repeatPlaylist = true; + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); + Log_Println((char *)FPSTR(modeRepeatTracknPlaylist), LOGLEVEL_INFO); + System_IndicateOk(); + break; + + default: + System_IndicateError(); + snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode()); + publishMqtt((char *)FPSTR(topicRepeatModeState), rBuf, false); + break; + } + } + } + } + + // Check if LEDs should be dimmed + else if (strcmp_P(topic, topicLedBrightnessCmnd) == 0) + { + Led_SetBrightness(strtoul(receivedString, NULL, 10)); + } + + // Requested something that isn't specified? + else + { + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(noValidTopic), topic); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + System_IndicateError(); + } + + free(receivedString); + free(mqttTopic); +#endif +} diff --git a/src/Mqtt.h b/src/Mqtt.h new file mode 100644 index 0000000..dbd2f86 --- /dev/null +++ b/src/Mqtt.h @@ -0,0 +1,25 @@ +#pragma once + +#ifdef MQTT_ENABLE + #define MQTT_SOCKET_TIMEOUT 1 // https://github.com/knolleary/pubsubclient/issues/403 + #include +#endif +// MQTT-configuration +// Please note: all lengths will be published n-1 as maxlength to GUI +constexpr uint8_t mqttServerLength = 32u; +constexpr uint8_t mqttUserLength = 16u; +constexpr uint8_t mqttPasswordLength = 16u; + +extern char *gMqttUser; +extern char *gMqttPassword; +extern uint16_t gMqttPort; + +void Mqtt_Init(void); +void Mqtt_Cyclic(void); +void Mqtt_Exit(void); +bool Mqtt_IsEnabled(void); + +bool publishMqtt(const char *topic, const char *payload, bool retained); +bool publishMqtt(const char *topic, int32_t payload, bool retained); +bool publishMqtt(const char *topic, unsigned long payload, bool retained); +bool publishMqtt(const char *topic, uint32_t payload, bool retained); diff --git a/src/Port.cpp b/src/Port.cpp new file mode 100644 index 0000000..969208e --- /dev/null +++ b/src/Port.cpp @@ -0,0 +1,77 @@ +#include +#include +#include "settings.h" +#include "Port.h" + +#ifdef PORT_EXPANDER_ENABLE +extern TwoWire i2cBusTwo; + +uint8_t Port_ExpanderPorts[portsToRead]; +bool Port_ExpanderHandler(void); +#endif + +void Port_Init(void) +{ +} + +void Port_Cyclic(void) +{ +#ifdef PORT_EXPANDER_ENABLE + Port_ExpanderHandler(); +#endif +} + +// Wrapper: reads from GPIOs (via digitalRead()) or from port-expander (if enabled) +// Behaviour like digitalRead(): returns true if not pressed and false if pressed +bool Port_Read(const uint8_t _channel) +{ + switch (_channel) + { + case 0 ... 39: // GPIO + return digitalRead(_channel); + +#ifdef PORT_EXPANDER_ENABLE + case 100 ... 107: // Port-expander (port 0) + return (Port_ExpanderPorts[0] & (1 << (_channel - 100))); // Remove offset 100 (return false if pressed) + + case 108 ... 115: // Port-expander (port 1) + if (portsToRead == 2) + { // Make sure portsToRead != 1 when channel > 107 + return (Port_ExpanderPorts[1] & (1 << (_channel - 108))); // Remove offset 100 + 8 (return false if pressed) + } + else + { + return true; + } + +#endif + + default: // Everything else (doesn't make sense at all) isn't supposed to be pressed + return true; + } +} + +#ifdef PORT_EXPANDER_ENABLE +// Reads input from port-expander and writes output into global array +// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA9555.pdf +bool Port_ExpanderHandler() +{ + i2cBusTwo.beginTransmission(expanderI2cAddress); + for (uint8_t i = 0; i < portsToRead; i++) + { + i2cBusTwo.write(0x00 + i); // Go to input-register... + i2cBusTwo.endTransmission(); + i2cBusTwo.requestFrom(expanderI2cAddress, 1); // ...and read its byte + + if (i2cBusTwo.available()) + { + Port_ExpanderPorts[i] = i2cBusTwo.read(); + } + else + { + return false; + } + } + return false; +} +#endif \ No newline at end of file diff --git a/src/Port.h b/src/Port.h new file mode 100644 index 0000000..00c8c9d --- /dev/null +++ b/src/Port.h @@ -0,0 +1,5 @@ +#pragma once + +void Port_Init(void); +void Port_Cyclic(void); +bool Port_Read(const uint8_t _channel); \ No newline at end of file diff --git a/src/Queues.cpp b/src/Queues.cpp new file mode 100644 index 0000000..e8b4262 --- /dev/null +++ b/src/Queues.cpp @@ -0,0 +1,38 @@ +#include +#include "settings.h" +#include "Log.h" +#include "Rfid.h" + +QueueHandle_t gVolumeQueue; +QueueHandle_t gTrackQueue; +QueueHandle_t gTrackControlQueue; +QueueHandle_t gRfidCardQueue; + +void Queues_Init(void) +{ + // Create queues + gVolumeQueue = xQueueCreate(1, sizeof(int)); + if (gVolumeQueue == NULL) + { + Log_Println((char *)FPSTR(unableToCreateVolQ), LOGLEVEL_ERROR); + } + + gRfidCardQueue = xQueueCreate(1, cardIdStringSize); + if (gRfidCardQueue == NULL) + { + Log_Println((char *)FPSTR(unableToCreateRfidQ), LOGLEVEL_ERROR); + } + + gTrackControlQueue = xQueueCreate(1, sizeof(uint8_t)); + if (gTrackControlQueue == NULL) + { + Log_Println((char *)FPSTR(unableToCreateMgmtQ), LOGLEVEL_ERROR); + } + + char **playlistArray; + gTrackQueue = xQueueCreate(1, sizeof(playlistArray)); + if (gTrackQueue == NULL) + { + Log_Println((char *)FPSTR(unableToCreatePlayQ), LOGLEVEL_ERROR); + } +} diff --git a/src/Queues.h b/src/Queues.h new file mode 100644 index 0000000..ac6f9d0 --- /dev/null +++ b/src/Queues.h @@ -0,0 +1,8 @@ +#pragma once + +extern QueueHandle_t gVolumeQueue; +extern QueueHandle_t gTrackQueue; +extern QueueHandle_t gTrackControlQueue; +extern QueueHandle_t gRfidCardQueue; + +void Queues_Init(void); \ No newline at end of file diff --git a/src/Rfid.h b/src/Rfid.h new file mode 100644 index 0000000..bf67322 --- /dev/null +++ b/src/Rfid.h @@ -0,0 +1,12 @@ +#pragma once + +constexpr uint8_t cardIdSize = 4u; +constexpr uint8_t cardIdStringSize = (cardIdSize * 3u) + 1u; + +extern char *gCurrentRfidTagId; + +void Rfid_Init(void); +void Rfid_Cyclic(void); +void Rfid_Exit(void); +void Rfid_WakeupCheck(void); +void Rfid_PreferenceLookupHandler(void); diff --git a/src/RfidCommon.cpp b/src/RfidCommon.cpp new file mode 100644 index 0000000..307520f --- /dev/null +++ b/src/RfidCommon.cpp @@ -0,0 +1,101 @@ +#include +#include "settings.h" +#include "Rfid.h" +#include "AudioPlayer.h" +#include "Cmd.h" +#include "Common.h" +#include "Log.h" +#include "MemX.h" +#include "Mqtt.h" +#include "Queues.h" +#include "System.h" +#include "Web.h" + +unsigned long Rfid_LastRfidCheckTimestamp = 0; +char *gCurrentRfidTagId = NULL; + +// Tries to lookup RFID-tag-string in NVS and extracts parameter from it if found +void Rfid_PreferenceLookupHandler(void) +{ + BaseType_t rfidStatus; + char rfidTagId[cardIdStringSize]; + char _file[255]; + uint32_t _lastPlayPos = 0; + uint16_t _trackLastPlayed = 0; + uint32_t _playMode = 1; + + rfidStatus = xQueueReceive(gRfidCardQueue, &rfidTagId, 0); + if (rfidStatus == pdPASS) + { + System_UpdateActivityTimer(); + free(gCurrentRfidTagId); + gCurrentRfidTagId = x_strdup(rfidTagId); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(rfidTagReceived), gCurrentRfidTagId); + Web_SendWebsocketData(0, 10); // Push new rfidTagId to all websocket-clients + Log_Println(Log_Buffer, LOGLEVEL_INFO); + + String s = gPrefsRfid.getString(gCurrentRfidTagId, "-1"); // Try to lookup rfidId in NVS + if (!s.compareTo("-1")) + { + Log_Println((char *)FPSTR(rfidTagUnknownInNvs), LOGLEVEL_ERROR); + System_IndicateError(); + return; + } + + char *token; + uint8_t i = 1; + token = strtok((char *)s.c_str(), stringDelimiter); + while (token != NULL) + { // Try to extract data from string after lookup + if (i == 1) + { + strncpy(_file, token, sizeof(_file) / sizeof(_file[0])); + } + else if (i == 2) + { + _lastPlayPos = strtoul(token, NULL, 10); + } + else if (i == 3) + { + _playMode = strtoul(token, NULL, 10); + } + else if (i == 4) + { + _trackLastPlayed = strtoul(token, NULL, 10); + } + i++; + token = strtok(NULL, stringDelimiter); + } + + if (i != 5) + { + Log_Println((char *)FPSTR(errorOccuredNvs), LOGLEVEL_ERROR); + System_IndicateError(); + } + else + { + // Only pass file to queue if strtok revealed 3 items + if (_playMode >= 100) + { + // Modification-cards can change some settings (e.g. introducing track-looping or sleep after track/playlist). + Cmd_Action(_playMode); + } + else + { +#ifdef MQTT_ENABLE + publishMqtt((char *)FPSTR(topicRfidState), gCurrentRfidTagId, false); +#endif + +#ifdef BLUETOOTH_ENABLE + // if music rfid was read, go back to normal mode + if (System_GetOperationMode() == OPMODE_BLUETOOTH) + { + System_SetOperationMode(OPMODE_NORMAL); + } +#endif + + AudioPlayer_TrackQueueDispatcher(_file, _lastPlayPos, _playMode, _trackLastPlayed); + } + } + } +} \ No newline at end of file diff --git a/src/RfidMfrc522.cpp b/src/RfidMfrc522.cpp new file mode 100644 index 0000000..9b2f2ed --- /dev/null +++ b/src/RfidMfrc522.cpp @@ -0,0 +1,101 @@ +#include +#include "settings.h" +#include "Rfid.h" +#include "Log.h" +#include "MemX.h" +#include "Queues.h" +#include "System.h" + +#if defined RFID_READER_TYPE_MFRC522_SPI || defined RFID_READER_TYPE_MFRC522_I2C + +#ifdef RFID_READER_TYPE_MFRC522_SPI +#include +#endif +#if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(PORT_EXPANDER_ENABLE) +#include "Wire.h" +#endif +#ifdef RFID_READER_TYPE_MFRC522_I2C +#include +#endif + +extern unsigned long Rfid_LastRfidCheckTimestamp; + +#ifdef RFID_READER_TYPE_MFRC522_I2C +extern TwoWire i2cBusTwo; + +static MFRC522_I2C mfrc522(MFRC522_ADDR, MFRC522_RST_PIN, &i2cBusTwo); +#endif +#ifdef RFID_READER_TYPE_MFRC522_SPI +static MFRC522 mfrc522(RFID_CS, RST_PIN); +#endif + +void Rfid_Init(void) +{ +#ifdef RFID_READER_TYPE_MFRC522_SPI + SPI.begin(RFID_SCK, RFID_MISO, RFID_MOSI, RFID_CS); + SPI.setFrequency(1000000); +#endif + + // Init RC522 Card-Reader +#if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(RFID_READER_TYPE_MFRC522_SPI) + mfrc522.PCD_Init(); + mfrc522.PCD_SetAntennaGain(rfidGain); + delay(50); + Log_Println((char *)FPSTR(rfidScannerReady), LOGLEVEL_DEBUG); +#endif +} + +void Rfid_Cyclic(void) +{ + byte cardId[cardIdSize]; + String cardIdString; + + if ((millis() - Rfid_LastRfidCheckTimestamp) >= RFID_SCAN_INTERVAL) + { + Rfid_LastRfidCheckTimestamp = millis(); + // Reset the loop if no new card is present on the sensor/reader. This saves the entire process when idle. + + if (!mfrc522.PICC_IsNewCardPresent()) + { + return; + } + + // Select one of the cards + if (!mfrc522.PICC_ReadCardSerial()) + { + return; + } + + //mfrc522.PICC_DumpToSerial(&(mfrc522.uid)); + mfrc522.PICC_HaltA(); + mfrc522.PCD_StopCrypto1(); + + memcpy(cardId, mfrc522.uid.uidByte, cardIdSize); + + Log_Print((char *)FPSTR(rfidTagDetected), LOGLEVEL_NOTICE); + for (uint8_t i = 0u; i < cardIdSize; i++) + { + snprintf(Log_Buffer, Log_BufferLength, "%02x%s", cardId[i], (i < cardIdSize - 1u) ? "-" : "\n"); + Log_Print(Log_Buffer, LOGLEVEL_NOTICE); + } + + for (uint8_t i = 0u; i < cardIdSize; i++) + { + char num[4]; + snprintf(num, sizeof(num), "%03d", cardId[i]); + cardIdString += num; + } + + xQueueSend(gRfidCardQueue, cardIdString.c_str(), 0); + } +} + +void Rfid_Exit(void) +{ +} + +void Rfid_WakeupCheck(void) +{ +} + +#endif \ No newline at end of file diff --git a/src/RfidPn5180.cpp b/src/RfidPn5180.cpp new file mode 100644 index 0000000..ac8426f --- /dev/null +++ b/src/RfidPn5180.cpp @@ -0,0 +1,262 @@ +#include +#include +#include +#include "settings.h" +#include "Rfid.h" +#include "Log.h" +#include "MemX.h" +#include "Queues.h" +#include "System.h" + +#ifdef RFID_READER_TYPE_PN5180 +#include +#include +#include +#endif + +#define RFID_PN5180_STATE_INIT 0u + +#define RFID_PN5180_NFC14443_STATE_RESET 1u +#define RFID_PN5180_NFC14443_STATE_SETUPRF 2u +#define RFID_PN5180_NFC14443_STATE_READCARD 3u + +#define RFID_PN5180_NFC15693_STATE_RESET 4u +#define RFID_PN5180_NFC15693_STATE_SETUPRF 5u +#define RFID_PN5180_NFC15693_STATE_DISABLEPRIVACYMODE 6u +#define RFID_PN5180_NFC15693_STATE_GETINVENTORY 7u + +extern unsigned long Rfid_LastRfidCheckTimestamp; + +#ifdef RFID_READER_TYPE_PN5180 + +static void Rfid_Task(void *parameter); +static void Rfid_Read(void); + +void Rfid_Init(void) +{ +#ifdef PN5180_ENABLE_LPCD + // disable pin hold from deep sleep + gpio_deep_sleep_hold_dis(); + gpio_hold_dis(gpio_num_t(RFID_CS)); // NSS + gpio_hold_dis(gpio_num_t(RFID_RST)); // RST +#endif + + // Create task for rfid + xTaskCreatePinnedToCore( + Rfid_Task, /* Function to implement the task */ + "Rfid_Task", /* Name of the task */ + 1500, /* Stack size in words */ + NULL, /* Task input parameter */ + 1, /* Priority of the task */ + NULL, /* Task handle. */ + 0 /* Core where the task should run */ + ); +} + +void Rfid_Cyclic(void) +{ + // Implemented via task +} + +void Rfid_Task(void *parameter) +{ + for (;;) + { + Rfid_Read(); + vTaskDelay(5u); + } +} + +void Rfid_Read(void) +{ + static PN5180ISO14443 nfc14443(RFID_CS, RFID_BUSY, RFID_RST); + static PN5180ISO15693 nfc15693(RFID_CS, RFID_BUSY, RFID_RST); + static uint8_t stateMachine = RFID_PN5180_STATE_INIT; + byte cardId[cardIdSize], lastCardId[cardIdSize]; + uint8_t uid[10]; + String cardIdString; + bool cardReceived = false; + + if (RFID_PN5180_STATE_INIT == stateMachine) + { + nfc14443.begin(); + nfc14443.reset(); + // show PN5180 reader version + uint8_t firmwareVersion[2]; + nfc14443.readEEprom(FIRMWARE_VERSION, firmwareVersion, sizeof(firmwareVersion)); + Serial.print(F("Firmware version=")); + Serial.print(firmwareVersion[1]); + Serial.print("."); + Serial.println(firmwareVersion[0]); + + // activate RF field + delay(4); + Log_Println((char *)FPSTR(rfidScannerReady), LOGLEVEL_DEBUG); + } + // 1. check for an ISO-14443 card + else if (RFID_PN5180_NFC14443_STATE_RESET == stateMachine) + { + nfc14443.reset(); + } + else if (RFID_PN5180_NFC14443_STATE_SETUPRF == stateMachine) + { + nfc14443.setupRF(); + } + else if (RFID_PN5180_NFC14443_STATE_READCARD == stateMachine) + { + if (nfc14443.readCardSerial(uid) >= 4u) + { + cardReceived = true; + } + } + // 2. check for an ISO-15693 card + else if (RFID_PN5180_NFC15693_STATE_RESET == stateMachine) + { + nfc15693.reset(); + } + else if (RFID_PN5180_NFC15693_STATE_SETUPRF == stateMachine) + { + nfc15693.setupRF(); + } + else if (RFID_PN5180_NFC15693_STATE_DISABLEPRIVACYMODE == stateMachine) + { + // check for ICODE-SLIX2 password protected tag + // put your privacy password here, e.g.: + // https://de.ifixit.com/Antworten/Ansehen/513422/nfc+Chips+f%C3%BCr+tonies+kaufen + uint8_t password[] = {0x01, 0x02, 0x03, 0x04}; + ISO15693ErrorCode myrc = nfc15693.disablePrivacyMode(password); + if (ISO15693_EC_OK == myrc) + { + Serial.println(F("disabling privacy-mode successful")); + } + } + else if (RFID_PN5180_NFC15693_STATE_GETINVENTORY == stateMachine) + { + // try to read ISO15693 inventory + ISO15693ErrorCode rc = nfc15693.getInventory(uid); + if (rc == ISO15693_EC_OK) + { + cardReceived = true; + } + } + + // send card to queue + if (cardReceived) + { + memcpy(cardId, uid, cardIdSize); + + // check for different card id + if (memcmp((const void *)cardId, (const void *)lastCardId, sizeof(cardId)) == 0) + { + // reset state machine + stateMachine = RFID_PN5180_NFC14443_STATE_RESET; + return; + } + + memcpy(lastCardId, cardId, cardIdSize); + + Log_Print((char *)FPSTR(rfidTagDetected), LOGLEVEL_NOTICE); + for (uint8_t i = 0u; i < cardIdSize; i++) + { + snprintf(Log_Buffer, Log_BufferLength, "%02x%s", cardId[i], (i < cardIdSize - 1u) ? "-" : "\n"); + Log_Print(Log_Buffer, LOGLEVEL_NOTICE); + } + + for (uint8_t i = 0u; i < cardIdSize; i++) + { + char num[4]; + snprintf(num, sizeof(num), "%03d", cardId[i]); + cardIdString += num; + } + + xQueueSend(gRfidCardQueue, cardIdString.c_str(), 0); + } + + stateMachine++; + + if (stateMachine > RFID_PN5180_NFC15693_STATE_GETINVENTORY) + { + stateMachine = RFID_PN5180_NFC14443_STATE_RESET; + } +} + +void Rfid_Exit(void) +{ +// goto low power card detection mode +#ifdef PN5180_ENABLE_LPCD + static PN5180 nfc(RFID_CS, RFID_BUSY, RFID_RST); + nfc.begin(); + // show PN5180 reader version + uint8_t firmwareVersion[2]; + nfc.readEEprom(FIRMWARE_VERSION, firmwareVersion, sizeof(firmwareVersion)); + Serial.print(F("Firmware version=")); + Serial.print(firmwareVersion[1]); + Serial.print("."); + Serial.println(firmwareVersion[0]); + // check firmware version: PN5180 firmware < 4.0 has several bugs preventing the LPCD mode + // you can flash latest firmware with this project: https://github.com/abidxraihan/PN5180_Updater_ESP32 + if (firmwareVersion[1] < 4) + { + Serial.println(F("This PN5180 firmware does not work with LPCD! use firmware >= 4.0")); + return; + } + Serial.println(F("prepare low power card detection...")); + nfc.prepareLPCD(); + nfc.clearIRQStatus(0xffffffff); + Serial.print(F("PN5180 IRQ PIN: ")); + Serial.println(Port_Read(RFID_IRQ)); + // turn on LPCD + uint16_t wakeupCounterInMs = 0x3FF; // must be in the range of 0x0 - 0xA82. max wake-up time is 2960 ms. + if (nfc.switchToLPCD(wakeupCounterInMs)) + { + Serial.println(F("switch to low power card detection: success")); + // configure wakeup pin for deep-sleep wake-up, use ext1 + esp_sleep_enable_ext1_wakeup((1ULL << (RFID_IRQ)), ESP_EXT1_WAKEUP_ANY_HIGH); + // freeze pin states in deep sleep + gpio_hold_en(gpio_num_t(RFID_CS)); // CS/NSS + gpio_hold_en(gpio_num_t(RFID_RST)); // RST + gpio_deep_sleep_hold_en(); + } + else + { + Serial.println(F("switchToLPCD failed")); + } +#endif +} + +// wake up from LPCD, check card is present. This works only for ISO-14443 compatible cards +void Rfid_WakeupCheck(void) +{ +#ifdef PN5180_ENABLE_LPCD + static PN5180ISO14443 nfc14443(RFID_CS, RFID_BUSY, RFID_RST); + nfc14443.begin(); + nfc14443.reset(); + nfc14443.setupRF(); + if (!nfc14443.isCardPresent()) + { + nfc14443.clearIRQStatus(0xffffffff); + Serial.print(F("Logic level at PN5180' IRQ-PIN: ")); + Serial.println(Port_Read(RFID_IRQ)); + // turn on LPCD + uint16_t wakeupCounterInMs = 0x3FF; // needs to be in the range of 0x0 - 0xA82. max wake-up time is 2960 ms. + if (nfc14443.switchToLPCD(wakeupCounterInMs)) + { + Log_Println((char *)FPSTR(lowPowerCardSuccess), LOGLEVEL_INFO); + // configure wakeup pin for deep-sleep wake-up, use ext1 + esp_sleep_enable_ext1_wakeup((1ULL << (RFID_IRQ)), ESP_EXT1_WAKEUP_ANY_HIGH); + // freeze pin states in deep sleep + gpio_hold_en(gpio_num_t(RFID_CS)); // CS/NSS + gpio_hold_en(gpio_num_t(RFID_RST)); // RST + gpio_deep_sleep_hold_en(); + Log_Println((char *)FPSTR(wakeUpRfidNoIso14443), LOGLEVEL_ERROR); + esp_deep_sleep_start(); + } + else + { + Serial.println(F("switchToLPCD failed")); + } + } +#endif +} + +#endif diff --git a/src/RotaryEncoder.cpp b/src/RotaryEncoder.cpp new file mode 100644 index 0000000..0346538 --- /dev/null +++ b/src/RotaryEncoder.cpp @@ -0,0 +1,78 @@ +#include +#include "settings.h" +#include "RotaryEncoder.h" +#include "AudioPlayer.h" +#include "Log.h" +#include "System.h" + +#ifdef USEROTARY_ENABLE +#include +#endif + +// Rotary encoder-configuration +#ifdef USEROTARY_ENABLE +ESP32Encoder encoder; +// Rotary encoder-helper +int32_t lastEncoderValue; +int32_t currentEncoderValue; +int32_t lastVolume = -1; // Don't change -1 as initial-value! +#endif + +void RotaryEncoder_Init(void) +{ + // Init rotary encoder +#ifdef USEROTARY_ENABLE + encoder.attachHalfQuad(DREHENCODER_CLK, DREHENCODER_DT); + encoder.clearCount(); + encoder.setCount(AudioPlayer_GetInitVolume() * 2); // Ganzes Raster ist immer +2, daher initiale Lautstärke mit 2 multiplizieren +#endif +} + +void RotaryEncoder_Readjust(void) +{ +#ifdef USEROTARY_ENABLE + encoder.clearCount(); + encoder.setCount(AudioPlayer_GetCurrentVolume() * 2); +#endif +} + +// Handles volume directed by rotary encoder +void RotaryEncoder_Cyclic(void) +{ +#ifdef USEROTARY_ENABLE + if (System_AreControlsLocked()) + { + encoder.clearCount(); + encoder.setCount(AudioPlayer_GetCurrentVolume() * 2); + return; + } + + currentEncoderValue = encoder.getCount(); + // Only if initial run or value has changed. And only after "full step" of rotary encoder + if (((lastEncoderValue != currentEncoderValue) || lastVolume == -1) && (currentEncoderValue % 2 == 0)) + { + System_UpdateActivityTimer(); // Set inactive back if rotary encoder was used + if ((AudioPlayer_GetMaxVolume() * 2) < currentEncoderValue) + { + encoder.clearCount(); + encoder.setCount(AudioPlayer_GetMaxVolume() * 2); + Log_Println((char *)FPSTR(maxLoudnessReached), LOGLEVEL_INFO); + currentEncoderValue = encoder.getCount(); + } + else if (currentEncoderValue < AudioPlayer_GetMinVolume()) + { + encoder.clearCount(); + encoder.setCount(AudioPlayer_GetMinVolume()); + Log_Println((char *)FPSTR(minLoudnessReached), LOGLEVEL_INFO); + currentEncoderValue = encoder.getCount(); + } + lastEncoderValue = currentEncoderValue; + AudioPlayer_SetCurrentVolume(lastEncoderValue / 2u); + if (AudioPlayer_GetCurrentVolume() != lastVolume) + { + lastVolume = AudioPlayer_GetCurrentVolume(); + AudioPlayer_VolumeToQueueSender(AudioPlayer_GetCurrentVolume(), false); + } + } +#endif +} \ No newline at end of file diff --git a/src/RotaryEncoder.h b/src/RotaryEncoder.h new file mode 100644 index 0000000..1fe303a --- /dev/null +++ b/src/RotaryEncoder.h @@ -0,0 +1,5 @@ +#pragma once + +void RotaryEncoder_Init(void); +void RotaryEncoder_Cyclic(void); +void RotaryEncoder_Readjust(void); \ No newline at end of file diff --git a/src/SdCard.cpp b/src/SdCard.cpp new file mode 100644 index 0000000..c5df3fe --- /dev/null +++ b/src/SdCard.cpp @@ -0,0 +1,235 @@ +#include +#include "settings.h" +#include "SdCard.h" +#include "Common.h" +#include "Led.h" +#include "Log.h" +#include "MemX.h" +#include "System.h" + +#ifdef SD_MMC_1BIT_MODE +fs::FS gFSystem = (fs::FS)SD_MMC; +#else +SPIClass spiSD(HSPI); +fs::FS gFSystem = (fs::FS)SD; +#endif + +void SdCard_Init(void) +{ +#ifndef SINGLE_SPI_ENABLE +#ifdef SD_MMC_1BIT_MODE + pinMode(2, INPUT_PULLUP); + while (!SD_MMC.begin("/sdcard", true)) + { +#else + pinMode(SPISD_CS, OUTPUT); + digitalWrite(SPISD_CS, HIGH); + spiSD.begin(SPISD_SCK, SPISD_MISO, SPISD_MOSI, SPISD_CS); + spiSD.setFrequency(1000000); + while (!SD.begin(SPISD_CS, spiSD)) + { +#endif +#else +#ifdef SD_MMC_1BIT_MODE + pinMode(2, INPUT_PULLUP); + while (!SD_MMC.begin("/sdcard", true)) + { +#else + while (!SD.begin(SPISD_CS)) + { +#endif +#endif + Log_Println((char *)FPSTR(unableToMountSd), LOGLEVEL_ERROR); + delay(500); +#ifdef SHUTDOWN_IF_SD_BOOT_FAILS + if (millis() >= deepsleepTimeAfterBootFails * 1000) + { + Log_Println((char *)FPSTR(sdBootFailedDeepsleep), LOGLEVEL_ERROR); + esp_deep_sleep_start(); + } +#endif + } +} + +void SdCard_Exit(void) +{ + // SD card goto idle mode +#ifdef SD_MMC_1BIT_MODE + SD_MMC.end(); +#endif +} + +sdcard_type_t SdCard_GetType(void) +{ + sdcard_type_t cardType; +#ifdef SD_MMC_1BIT_MODE + Log_Println((char *)FPSTR(sdMountedMmc1BitMode), LOGLEVEL_NOTICE); + cardType = SD_MMC.cardType(); +#else + Log_Println((char *)FPSTR(sdMountedSpiMode), LOGLEVEL_NOTICE); + cardType = SD.cardType(); +#endif + return cardType; +} + +// Check if file-type is correct +bool fileValid(const char *_fileItem) +{ + const char ch = '/'; + char *subst; + subst = strrchr(_fileItem, ch); // Don't use files that start with . + + return (!startsWith(subst, (char *)"/.")) && + (endsWith(_fileItem, ".mp3") || endsWith(_fileItem, ".MP3") || + endsWith(_fileItem, ".aac") || endsWith(_fileItem, ".AAC") || + endsWith(_fileItem, ".m3u") || endsWith(_fileItem, ".M3U") || + endsWith(_fileItem, ".m4a") || endsWith(_fileItem, ".M4A") || + endsWith(_fileItem, ".wav") || endsWith(_fileItem, ".WAV") || + endsWith(_fileItem, ".flac") || endsWith(_fileItem, ".FLAC") || + endsWith(_fileItem, ".asx") || endsWith(_fileItem, ".ASX")); +} + +/* Puts SD-file(s) or directory into a playlist + First element of array always contains the number of payload-items. */ +char **SdCard_ReturnPlaylist(const char *fileName) +{ + static char **files; + char fileNameBuf[255]; + + File fileOrDirectory = gFSystem.open(fileName); + + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(freeMemory), ESP.getFreeHeap()); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG); + + if (files != NULL) + { // If **ptr already exists, de-allocate its memory + Log_Println((char *)FPSTR(releaseMemoryOfOldPlaylist), LOGLEVEL_DEBUG); + --files; + freeMultiCharArray(files, strtoul(*files, NULL, 10)); + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(freeMemoryAfterFree), ESP.getFreeHeap()); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG); + } + + if (!fileOrDirectory) + { + Log_Println((char *)FPSTR(dirOrFileDoesNotExist), LOGLEVEL_ERROR); + return NULL; + } + + // File-mode + if (!fileOrDirectory.isDirectory()) + { + files = (char **)x_malloc(sizeof(char *) * 2); + if (files == NULL) + { + Log_Println((char *)FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR); + System_IndicateError(); + return NULL; + } + Log_Println((char *)FPSTR(fileModeDetected), LOGLEVEL_INFO); + strncpy(fileNameBuf, (char *)fileOrDirectory.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0])); + if (fileValid(fileNameBuf)) + { + files = (char **)x_malloc(sizeof(char *) * 2); + files[1] = x_strdup(fileNameBuf); + } + files[0] = x_strdup("1"); // Number of files is always 1 in file-mode + + return ++files; + } + + // Directory-mode + uint16_t allocCount = 1; + uint16_t allocSize = 512; + if (psramInit()) + { + allocSize = 16384; // There's enough PSRAM. So we don't have to care... + } + char *serializedPlaylist; + + serializedPlaylist = (char *)x_calloc(allocSize, sizeof(char)); + + while (true) + { + File fileItem = fileOrDirectory.openNextFile(); + if (!fileItem) + { + break; + } + if (fileItem.isDirectory()) + { + continue; + } + else + { + strncpy(fileNameBuf, (char *)fileItem.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0])); + + // Don't support filenames that start with "." and only allow .mp3 + if (fileValid(fileNameBuf)) + { + /*snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(nameOfFileFound), fileNameBuf); + Log_Println(Log_Buffer, LOGLEVEL_INFO);*/ + if ((strlen(serializedPlaylist) + strlen(fileNameBuf) + 2) >= allocCount * allocSize) + { + serializedPlaylist = (char *)realloc(serializedPlaylist, ++allocCount * allocSize); + Log_Println((char *)FPSTR(reallocCalled), LOGLEVEL_DEBUG); + if (serializedPlaylist == NULL) + { + Log_Println((char *)FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR); + System_IndicateError(); + return files; + } + } + strcat(serializedPlaylist, stringDelimiter); + strcat(serializedPlaylist, fileNameBuf); + } + } + } + + // Get number of elements out of serialized playlist + uint32_t cnt = 0; + for (uint32_t k = 0; k < (strlen(serializedPlaylist)); k++) + { + if (serializedPlaylist[k] == '#') + { + cnt++; + } + } + + // Alloc only necessary number of playlist-pointers + files = (char **)x_malloc(sizeof(char *) * cnt + 1); + + if (files == NULL) + { + Log_Println((char *)FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR); + System_IndicateError(); + free(serializedPlaylist); + return NULL; + } + + // Extract elements out of serialized playlist and copy to playlist + char *token; + token = strtok(serializedPlaylist, stringDelimiter); + uint32_t pos = 1; + while (token != NULL) + { + files[pos++] = x_strdup(token); + token = strtok(NULL, stringDelimiter); + } + + free(serializedPlaylist); + + files[0] = (char *)x_malloc(sizeof(char) * 5); + + if (files[0] == NULL) + { + Log_Println((char *)FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR); + System_IndicateError(); + return NULL; + } + sprintf(files[0], "%u", cnt); + snprintf(Log_Buffer, Log_BufferLength, "%s: %d", (char *)FPSTR(numberOfValidFiles), cnt); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + + return ++files; // return ptr+1 (starting at 1st payload-item); ptr+0 contains number of items +} diff --git a/src/SdCard.h b/src/SdCard.h new file mode 100644 index 0000000..1fdb82e --- /dev/null +++ b/src/SdCard.h @@ -0,0 +1,14 @@ +#pragma once +#include "settings.h" +#ifdef SD_MMC_1BIT_MODE +#include "SD_MMC.h" +#else +#include "SD.h" +#endif + +extern fs::FS gFSystem; + +void SdCard_Init(void); +void SdCard_Exit(void); +sdcard_type_t SdCard_GetType(void); +char **SdCard_ReturnPlaylist(const char *fileName); diff --git a/src/System.cpp b/src/System.cpp new file mode 100644 index 0000000..01cf5ce --- /dev/null +++ b/src/System.cpp @@ -0,0 +1,237 @@ +#include +#include "settings.h" +#include "System.h" +#include "AudioPlayer.h" +#include "Rfid.h" +#include "Led.h" +#include "Log.h" +#include "Mqtt.h" +#include "SdCard.h" + +constexpr const char prefsRfidNamespace[] PROGMEM = "rfidTags"; // Namespace used to save IDs of rfid-tags +constexpr const char prefsSettingsNamespace[] PROGMEM = "settings"; // Namespace used for generic settings + +Preferences gPrefsRfid; +Preferences gPrefsSettings; + +unsigned long System_LastTimeActiveTimestamp = 0u; // Timestamp of last user-interaction +unsigned long System_SleepTimerStartTimestamp = 0u; // Flag if sleep-timer is active +bool System_GoToSleep = false; // Flag for turning uC immediately into deepsleep +bool System_Sleeping = false; // Flag for turning into deepsleep is in progress +bool System_LockControls = false; // Flag if buttons and rotary encoder is locked +uint8_t System_MaxInactivityTime = 10u; // Time in minutes, after uC is put to deep sleep because of inactivity (and modified later via GUI) +uint8_t System_SleepTimer = 30u; // Sleep timer in minutes that can be optionally used (and modified later via MQTT or RFID) + +// Operation Mode +volatile uint8_t System_OperationMode; + +void System_SleepHandler(void); +void System_DeepSleepManager(void); + +void System_Init(void) +{ + srand(esp_random()); + pinMode(POWER, OUTPUT); + digitalWrite(POWER, HIGH); + + gPrefsRfid.begin((char *)FPSTR(prefsRfidNamespace)); + gPrefsSettings.begin((char *)FPSTR(prefsSettingsNamespace)); + + // Get maximum inactivity-time from NVS + uint32_t nvsMInactivityTime = gPrefsSettings.getUInt("mInactiviyT", 0); + if (nvsMInactivityTime) + { + System_MaxInactivityTime = nvsMInactivityTime; + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(restoredMaxInactivityFromNvs), nvsMInactivityTime); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + gPrefsSettings.putUInt("mInactiviyT", System_MaxInactivityTime); + Log_Println((char *)FPSTR(wroteMaxInactivityToNvs), LOGLEVEL_ERROR); + } + + System_OperationMode = gPrefsSettings.getUChar("operationMode", OPMODE_NORMAL); +} + +void System_Cyclic(void) +{ + System_SleepHandler(); + System_DeepSleepManager(); +} + +void System_UpdateActivityTimer(void) +{ + System_LastTimeActiveTimestamp = millis(); +} + +void System_RequestSleep(void) +{ + System_GoToSleep = true; +} + +bool System_IsSleepRequested(void) +{ + return System_GoToSleep; +} + +bool System_SetSleepTimer(uint8_t minutes) +{ + bool sleepTimerEnabled = false; + + if (System_SleepTimerStartTimestamp && (System_SleepTimer == minutes)) + { + System_SleepTimerStartTimestamp = 0u; + Led_ResetToInitialBrightness(); + Log_Println((char *)FPSTR(modificatorSleepd), LOGLEVEL_NOTICE); + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); + } + else + { + System_SleepTimer = minutes; + System_SleepTimerStartTimestamp = millis(); + sleepTimerEnabled = true; + + Led_ResetToNightBrightness(); + Log_Println((char *)FPSTR(modificatorSleepTimer60), LOGLEVEL_NOTICE); + publishMqtt((char *)FPSTR(topicSleepTimerState), System_SleepTimer, false); + publishMqtt((char *)FPSTR(topicLedBrightnessState), Led_GetBrightness(), false); + } + + return sleepTimerEnabled; +} + +void System_DisableSleepTimer(void) +{ + System_SleepTimerStartTimestamp = 0u; +} + +bool System_IsSleepTimerEnabled(void) +{ + return (System_SleepTimerStartTimestamp > 0u); +} + +uint32_t System_GetSleepTimerTimeStamp(void) +{ + return System_SleepTimerStartTimestamp; +} + +bool System_IsSleepPending(void) +{ + return System_Sleeping; +} + +uint8_t System_GetSleepTimer(void) +{ + return System_SleepTimer; +} + +void System_SetLockControls(bool value) +{ + System_LockControls = value; +} + +void System_ToggleLockControls(void) +{ + System_LockControls = !System_LockControls; + + if (System_LockControls) + { + Log_Println((char *)FPSTR(modificatorAllButtonsLocked), LOGLEVEL_NOTICE); + publishMqtt((char *)FPSTR(topicLockControlsState), "ON", false); + System_IndicateOk(); + } + else + { + Log_Println((char *)FPSTR(modificatorAllButtonsUnlocked), LOGLEVEL_NOTICE); + publishMqtt((char *)FPSTR(topicLockControlsState), "OFF", false); + } +} + +bool System_AreControlsLocked(void) +{ + return System_LockControls; +} + +void System_IndicateError(void) +{ + Led_Indicate(LedIndicatorType::Error); +} +void System_IndicateOk(void) +{ + Led_Indicate(LedIndicatorType::Ok); +} + +// Writes to NVS, if bluetooth or "normal" mode is desired +void System_SetOperationMode(uint8_t opMode) +{ + uint8_t currentOperationMode = gPrefsSettings.getUChar("operationMode", OPMODE_NORMAL); + if (currentOperationMode != opMode) + { + if (gPrefsSettings.putUChar("operationMode", opMode)) + { + ESP.restart(); + } + } +} + +uint8_t System_GetOperationMode(void) +{ + return System_OperationMode; +} + +// Reads from NVS, if bluetooth or "normal" mode is desired +uint8_t System_GetOperationModeFromNvs(void) +{ + return gPrefsSettings.getUChar("operationMode", OPMODE_NORMAL); +} + +// Sets deep-sleep-flag if max. inactivity-time is reached +void System_SleepHandler(void) +{ + unsigned long m = millis(); + if (m >= System_LastTimeActiveTimestamp && (m - System_LastTimeActiveTimestamp >= (System_MaxInactivityTime * 1000u * 60u))) + { + Log_Println((char *)FPSTR(goToSleepDueToIdle), LOGLEVEL_INFO); + System_RequestSleep(); + } + else if (System_SleepTimerStartTimestamp > 00) + { + if (m - System_SleepTimerStartTimestamp >= (System_SleepTimer * 1000u * 60u)) + { + Log_Println((char *)FPSTR(goToSleepDueToTimer), LOGLEVEL_INFO); + System_RequestSleep(); + } + } +} + +// Puts uC to deep-sleep if flag is set +void System_DeepSleepManager(void) +{ + if (System_GoToSleep) + { + if (System_Sleeping) + { + return; + } + + System_Sleeping = true; + Log_Println((char *)FPSTR(goToSleepNow), LOGLEVEL_NOTICE); + + Mqtt_Exit(); + Led_Exit(); + +#ifdef USE_LAST_VOLUME_AFTER_REBOOT + gPrefsSettings.putUInt("previousVolume", AudioPlayer_GetCurrentVolume()); +#endif + SdCard_Exit(); + + Serial.flush(); + // switch off power + digitalWrite(POWER, LOW); + delay(200); + Rfid_Exit(); + Serial.println(F("deep-sleep, good night.......")); + esp_deep_sleep_start(); + } +} diff --git a/src/System.h b/src/System.h new file mode 100644 index 0000000..2cf0965 --- /dev/null +++ b/src/System.h @@ -0,0 +1,25 @@ +#pragma once +#include + +extern Preferences gPrefsRfid; +extern Preferences gPrefsSettings; + +void System_Init(void); +void System_Cyclic(void); +void System_UpdateActivityTimer(void); +void System_RequestSleep(void); +bool System_IsSleepRequested(void); +bool System_SetSleepTimer(uint8_t minutes); +void System_DisableSleepTimer(); +bool System_IsSleepTimerEnabled(void); +uint32_t System_GetSleepTimerTimeStamp(void); +bool System_IsSleepPending(void); +uint8_t System_GetSleepTimer(void); +void System_SetLockControls(bool value); +void System_ToggleLockControls(void); +bool System_AreControlsLocked(void); +void System_IndicateError(void); +void System_IndicateOk(void); +void System_SetOperationMode(uint8_t opMode); +uint8_t System_GetOperationMode(void); +uint8_t System_GetOperationModeFromNvs(void); diff --git a/src/Web.cpp b/src/Web.cpp new file mode 100644 index 0000000..13edc7e --- /dev/null +++ b/src/Web.cpp @@ -0,0 +1,1066 @@ +#include +#include +#include +#include +#include "freertos/ringbuf.h" +#include "ESPAsyncWebServer.h" +#include "ArduinoJson.h" +#include "settings.h" +#include "AudioPlayer.h" +#include "Battery.h" +#include "Cmd.h" +#include "Common.h" +#include "Ftp.h" +#include "Led.h" +#include "Log.h" +#include "MemX.h" +#include "Mqtt.h" +#include "Rfid.h" +#include "SdCard.h" +#include "System.h" +#include "Web.h" +#include "Wlan.h" + +#include "HTMLaccesspoint_DE.h" +#include "HTMLaccesspoint_EN.h" +#include "HTMLmanagement_DE.h" +#include "HTMLmanagement_EN.h" + +typedef struct +{ + char nvsKey[13]; + char nvsEntry[275]; +} nvs_t; + +const char mqttTab[] PROGMEM = " MQTT"; +const char ftpTab[] PROGMEM = " FTP"; + +AsyncWebServer wServer(80); +AsyncWebSocket ws("/ws"); +AsyncEventSource events("/events"); + +static bool webserverStarted = false; + +static RingbufHandle_t explorerFileUploadRingBuffer; +static QueueHandle_t explorerFileUploadStatusQueue; +static TaskHandle_t fileStorageTaskHandle; + +static void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +static void explorerHandleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +static void explorerHandleFileStorageTask(void *parameter); +static void explorerHandleListRequest(AsyncWebServerRequest *request); +static void explorerHandleDeleteRequest(AsyncWebServerRequest *request); +static void explorerHandleCreateRequest(AsyncWebServerRequest *request); +static void explorerHandleRenameRequest(AsyncWebServerRequest *request); +static void explorerHandleAudioRequest(AsyncWebServerRequest *request); + +static bool Web_DumpNvsToSd(const char *_namespace, const char *_destFile); + +static void onWebsocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); +static String templateProcessor(const String &templ); +static void webserverStart(void); + +void Web_Init(void) +{ + wServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send_P(200, "text/html", accesspoint_HTML); + }); + + wServer.on("/init", HTTP_POST, [](AsyncWebServerRequest *request) { + if (request->hasParam("ssid", true) && request->hasParam("pwd", true) && request->hasParam("hostname", true)) + { + Serial.println(request->getParam("ssid", true)->value()); + Serial.println(request->getParam("pwd", true)->value()); + Serial.println(request->getParam("hostname", true)->value()); + gPrefsSettings.putString("SSID", request->getParam("ssid", true)->value()); + gPrefsSettings.putString("Password", request->getParam("pwd", true)->value()); + gPrefsSettings.putString("Hostname", request->getParam("hostname", true)->value()); + } + request->send_P(200, "text/html", accesspoint_HTML); + }); + + wServer.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) { +#if (LANGUAGE == 1) + request->send(200, "text/html", "ESPuino wird neu gestartet..."); +#else + request->send(200, "text/html", "ESPuino is being restarted..."); +#endif + Serial.flush(); + ESP.restart(); + }); + + wServer.on("/shutdown", HTTP_GET, [](AsyncWebServerRequest *request) { +#if (LANGUAGE == 1) + request->send(200, "text/html", "ESPuino wird ausgeschaltet..."); +#else + request->send(200, "text/html", "ESPuino is being shutdown..."); +#endif + System_RequestSleep(); + }); + + // allow cors for local debug + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + wServer.begin(); + Log_Println((char *)FPSTR(httpReady), LOGLEVEL_NOTICE); +} + +void Web_Cyclic(void) +{ + webserverStart(); + ws.cleanupClients(); +} + +void notFound(AsyncWebServerRequest *request) +{ + request->send(404, "text/plain", "Not found"); +} + +void webserverStart(void) +{ + if (Wlan_IsConnected() && !webserverStarted) + { + // attach AsyncWebSocket for Mgmt-Interface + ws.onEvent(onWebsocketEvent); + wServer.addHandler(&ws); + + // attach AsyncEventSource + wServer.addHandler(&events); + + // Default + wServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send_P(200, "text/html", management_HTML, templateProcessor); + }); + + // Log + wServer.on("/log", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", Log_GetRingBuffer()); + }); + + // NVS-backup-upload + wServer.on( + "/upload", HTTP_POST, [](AsyncWebServerRequest *request) { + request->send_P(200, "text/html", backupRecoveryWebsite); + }, + handleUpload); + + // ESP-restart + wServer.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send_P(200, "text/html", restartWebsite); + Serial.flush(); + ESP.restart(); + }); + + // ESP-shutdown + wServer.on("/shutdown", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send_P(200, "text/html", shutdownWebsite); + System_RequestSleep(); + }); + + // Fileexplorer (realtime) + wServer.on("/explorer", HTTP_GET, explorerHandleListRequest); + + wServer.on( + "/explorer", HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200); + }, + explorerHandleFileUpload); + + wServer.on("/explorer", HTTP_DELETE, explorerHandleDeleteRequest); + + wServer.on("/explorer", HTTP_PUT, explorerHandleCreateRequest); + + wServer.on("/explorer", HTTP_PATCH, explorerHandleRenameRequest); + + wServer.on("/exploreraudio", HTTP_POST, explorerHandleAudioRequest); + + wServer.onNotFound(notFound); + + // allow cors for local debug + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + wServer.begin(); + webserverStarted = true; + } +} + +// Used for substitution of some variables/templates of html-files. Is called by webserver's template-engine +String templateProcessor(const String &templ) +{ + if (templ == "FTP_USER") + { + return gPrefsSettings.getString("ftpuser", "-1"); + } + else if (templ == "FTP_PWD") + { + return gPrefsSettings.getString("ftppassword", "-1"); + } + else if (templ == "FTP_USER_LENGTH") + { + return String(ftpUserLength - 1); + } + else if (templ == "FTP_PWD_LENGTH") + { + return String(ftpPasswordLength - 1); + } + else if (templ == "SHOW_FTP_TAB") + { // Only show FTP-tab if FTP-support was compiled +#ifdef FTP_ENABLE + return (String)FPSTR(ftpTab); +#else + return String(); +#endif + } + else if (templ == "INIT_LED_BRIGHTNESS") + { + return String(gPrefsSettings.getUChar("iLedBrightness", 0)); + } + else if (templ == "NIGHT_LED_BRIGHTNESS") + { + return String(gPrefsSettings.getUChar("nLedBrightness", 0)); + } + else if (templ == "MAX_INACTIVITY") + { + return String(gPrefsSettings.getUInt("mInactiviyT", 0)); + } + else if (templ == "INIT_VOLUME") + { + return String(gPrefsSettings.getUInt("initVolume", 0)); + } + else if (templ == "CURRENT_VOLUME") + { + return String(AudioPlayer_GetCurrentVolume()); + } + else if (templ == "MAX_VOLUME_SPEAKER") + { + return String(gPrefsSettings.getUInt("maxVolumeSp", 0)); + } + else if (templ == "MAX_VOLUME_HEADPHONE") + { + return String(gPrefsSettings.getUInt("maxVolumeHp", 0)); + } + else if (templ == "WARNING_LOW_VOLTAGE") + { + return String(gPrefsSettings.getFloat("wLowVoltage", warningLowVoltage)); + } + else if (templ == "VOLTAGE_INDICATOR_LOW") + { + return String(gPrefsSettings.getFloat("vIndicatorLow", voltageIndicatorLow)); + } + else if (templ == "VOLTAGE_INDICATOR_HIGH") + { + return String(gPrefsSettings.getFloat("vIndicatorHigh", voltageIndicatorHigh)); + } + else if (templ == "VOLTAGE_CHECK_INTERVAL") + { + return String(gPrefsSettings.getUInt("vCheckIntv", voltageCheckInterval)); + } + else if (templ == "MQTT_SERVER") + { + return gPrefsSettings.getString("mqttServer", "-1"); + } + else if (templ == "SHOW_MQTT_TAB") + { // Only show MQTT-tab if MQTT-support was compiled +#ifdef MQTT_ENABLE + return (String)FPSTR(mqttTab); +#else + return String(); +#endif + } + else if (templ == "MQTT_ENABLE") + { + if (Mqtt_IsEnabled()) + { + return String("checked=\"checked\""); + } + else + { + return String(); + } + } + else if (templ == "MQTT_USER") + { + return gPrefsSettings.getString("mqttUser", "-1"); + } + else if (templ == "MQTT_PWD") + { + return gPrefsSettings.getString("mqttPassword", "-1"); + } + else if (templ == "MQTT_USER_LENGTH") + { + return String(mqttUserLength - 1); + } + else if (templ == "MQTT_PWD_LENGTH") + { + return String(mqttPasswordLength - 1); + } + else if (templ == "MQTT_SERVER_LENGTH") + { + return String(mqttServerLength - 1); + } + else if (templ == "MQTT_PORT") + { + return String(gMqttPort); + } + else if (templ == "IPv4") + { + IPAddress myIP = WiFi.localIP(); + snprintf(Log_Buffer, Log_BufferLength, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); + return String(Log_Buffer); + } + else if (templ == "RFID_TAG_ID") + { + return String(gCurrentRfidTagId); + } + else if (templ == "HOSTNAME") + { + return gPrefsSettings.getString("Hostname", "-1"); + } + + return String(); +} + +// Takes inputs from webgui, parses JSON and saves values in NVS +// If operation was successful (NVS-write is verified) true is returned +bool processJsonRequest(char *_serialJson) +{ + StaticJsonDocument<1000> doc; + DeserializationError error = deserializeJson(doc, _serialJson); + JsonObject object = doc.as(); + + if (error) + { +#if (LANGUAGE == 1) + Serial.print(F("deserializeJson() fehlgeschlagen: ")); +#else + Serial.print(F("deserializeJson() failed: ")); +#endif + Serial.println(error.c_str()); + return false; + } + + if (doc.containsKey("general")) + { + uint8_t iVol = doc["general"]["iVol"].as(); + uint8_t mVolSpeaker = doc["general"]["mVolSpeaker"].as(); + uint8_t mVolHeadphone = doc["general"]["mVolHeadphone"].as(); + uint8_t iBright = doc["general"]["iBright"].as(); + uint8_t nBright = doc["general"]["nBright"].as(); + uint8_t iTime = doc["general"]["iTime"].as(); + float vWarning = doc["general"]["vWarning"].as(); + float vIndLow = doc["general"]["vIndLow"].as(); + float vIndHi = doc["general"]["vIndHi"].as(); + uint8_t vInt = doc["general"]["vInt"].as(); + + gPrefsSettings.putUInt("initVolume", iVol); + gPrefsSettings.putUInt("maxVolumeSp", mVolSpeaker); + gPrefsSettings.putUInt("maxVolumeHp", mVolHeadphone); + gPrefsSettings.putUChar("iLedBrightness", iBright); + gPrefsSettings.putUChar("nLedBrightness", nBright); + gPrefsSettings.putUInt("mInactiviyT", iTime); + gPrefsSettings.putFloat("wLowVoltage", vWarning); + gPrefsSettings.putFloat("vIndicatorLow", vIndLow); + gPrefsSettings.putFloat("vIndicatorHigh", vIndHi); + gPrefsSettings.putUInt("vCheckIntv", vInt); + + // Check if settings were written successfully + if (gPrefsSettings.getUInt("initVolume", 0) != iVol || + gPrefsSettings.getUInt("maxVolumeSp", 0) != mVolSpeaker || + gPrefsSettings.getUInt("maxVolumeHp", 0) != mVolHeadphone || + gPrefsSettings.getUChar("iLedBrightness", 0) != iBright || + gPrefsSettings.getUChar("nLedBrightness", 0) != nBright || + gPrefsSettings.getUInt("mInactiviyT", 0) != iTime || + gPrefsSettings.getFloat("wLowVoltage", 999.99) != vWarning || + gPrefsSettings.getFloat("vIndicatorLow", 999.99) != vIndLow || + gPrefsSettings.getFloat("vIndicatorHigh", 999.99) != vIndHi || + gPrefsSettings.getUInt("vCheckIntv", 17777) != vInt) + { + return false; + } + } + else if (doc.containsKey("ftp")) + { + const char *_ftpUser = doc["ftp"]["ftpUser"]; + const char *_ftpPwd = doc["ftp"]["ftpPwd"]; + + gPrefsSettings.putString("ftpuser", (String)_ftpUser); + gPrefsSettings.putString("ftppassword", (String)_ftpPwd); + + if (!(String(_ftpUser).equals(gPrefsSettings.getString("ftpuser", "-1")) || + String(_ftpPwd).equals(gPrefsSettings.getString("ftppassword", "-1")))) + { + return false; + } + } + else if (doc.containsKey("mqtt")) + { + uint8_t _mqttEnable = doc["mqtt"]["mqttEnable"].as(); + const char *_mqttServer = object["mqtt"]["mqttServer"]; + gPrefsSettings.putUChar("enableMQTT", _mqttEnable); + gPrefsSettings.putString("mqttServer", (String)_mqttServer); + const char *_mqttUser = doc["mqtt"]["mqttUser"]; + const char *_mqttPwd = doc["mqtt"]["mqttPwd"]; + uint16_t _mqttPort = doc["mqtt"]["mqttPort"].as(); + + gPrefsSettings.putUChar("enableMQTT", _mqttEnable); + gPrefsSettings.putString("mqttServer", (String)_mqttServer); + gPrefsSettings.putString("mqttServer", (String)_mqttServer); + gPrefsSettings.putString("mqttUser", (String)_mqttUser); + gPrefsSettings.putString("mqttPassword", (String)_mqttPwd); + gPrefsSettings.putUInt("mqttPort", _mqttPort); + + if ((gPrefsSettings.getUChar("enableMQTT", 99) != _mqttEnable) || + (!String(_mqttServer).equals(gPrefsSettings.getString("mqttServer", "-1")))) + { + return false; + } + } + else if (doc.containsKey("rfidMod")) + { + const char *_rfidIdModId = object["rfidMod"]["rfidIdMod"]; + uint8_t _modId = object["rfidMod"]["modId"]; + char rfidString[12]; + if (_modId <= 0) + { + gPrefsRfid.remove(_rfidIdModId); + } + else + { + snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s0%s0%s%u%s0", stringDelimiter, stringDelimiter, stringDelimiter, _modId, stringDelimiter); + gPrefsRfid.putString(_rfidIdModId, rfidString); + + String s = gPrefsRfid.getString(_rfidIdModId, "-1"); + if (s.compareTo(rfidString)) + { + return false; + } + } + Web_DumpNvsToSd("rfidTags", (const char*)FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed + } + else if (doc.containsKey("rfidAssign")) + { + const char *_rfidIdAssinId = object["rfidAssign"]["rfidIdMusic"]; + char _fileOrUrlAscii[MAX_FILEPATH_LENTGH]; + convertUtf8ToAscii(object["rfidAssign"]["fileOrUrl"], _fileOrUrlAscii); + uint8_t _playMode = object["rfidAssign"]["playMode"]; + char rfidString[275]; + snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s%s%s0%s%u%s0", stringDelimiter, _fileOrUrlAscii, stringDelimiter, stringDelimiter, _playMode, stringDelimiter); + gPrefsRfid.putString(_rfidIdAssinId, rfidString); + Serial.println(_rfidIdAssinId); + Serial.println(rfidString); + + String s = gPrefsRfid.getString(_rfidIdAssinId, "-1"); + if (s.compareTo(rfidString)) + { + return false; + } + Web_DumpNvsToSd("rfidTags", (const char*)FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed + } + else if (doc.containsKey("wifiConfig")) + { + const char *_ssid = object["wifiConfig"]["ssid"]; + const char *_pwd = object["wifiConfig"]["pwd"]; + const char *_hostname = object["wifiConfig"]["hostname"]; + + gPrefsSettings.putString("SSID", _ssid); + gPrefsSettings.putString("Password", _pwd); + gPrefsSettings.putString("Hostname", (String)_hostname); + + String sSsid = gPrefsSettings.getString("SSID", "-1"); + String sPwd = gPrefsSettings.getString("Password", "-1"); + String sHostname = gPrefsSettings.getString("Hostname", "-1"); + + if (sSsid.compareTo(_ssid) || sPwd.compareTo(_pwd)) + { + return false; + } + } + else if (doc.containsKey("ping")) + { + Web_SendWebsocketData(0, 20); + return false; + } + else if (doc.containsKey("controls")) + { + if (object["controls"].containsKey("set_volume")) + { + uint8_t new_vol = doc["controls"]["set_volume"].as(); + AudioPlayer_VolumeToQueueSender(new_vol, true); + } + if (object["controls"].containsKey("action")) + { + uint8_t cmd = doc["controls"]["action"].as(); + Cmd_Action(cmd); + } + } + + return true; +} + +// Sends JSON-answers via websocket +void Web_SendWebsocketData(uint32_t client, uint8_t code) +{ + char *jBuf; + jBuf = (char *)x_calloc(255, sizeof(char)); + + const size_t CAPACITY = JSON_OBJECT_SIZE(1) + 20; + StaticJsonDocument doc; + JsonObject object = doc.to(); + + if (code == 1) + { + object["status"] = "ok"; + } + else if (code == 2) + { + object["status"] = "error"; + } + else if (code == 10) + { + object["rfidId"] = gCurrentRfidTagId; + } + else if (code == 20) + { + object["pong"] = "pong"; + } + + serializeJson(doc, jBuf, 255); + + if (client == 0) + { + ws.printfAll(jBuf); + } + else + { + ws.printf(client, jBuf); + } + free(jBuf); +} + +// Processes websocket-requests +void onWebsocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) +{ + if (type == WS_EVT_CONNECT) + { + //client connected + Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); + //client->printf("Hello Client %u :)", client->id()); + client->ping(); + } + else if (type == WS_EVT_DISCONNECT) + { + //client disconnected + Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id()); + } + else if (type == WS_EVT_ERROR) + { + //error was received from the other end + Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t *)arg), (char *)data); + } + else if (type == WS_EVT_PONG) + { + //pong message was received (in response to a ping request maybe) + Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len) ? (char *)data : ""); + } + else if (type == WS_EVT_DATA) + { + //data packet + AwsFrameInfo *info = (AwsFrameInfo *)arg; + if (info->final && info->index == 0 && info->len == len) + { + //the whole message is in a single frame and we got all of it's data + Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT) ? "text" : "binary", info->len); + + if (processJsonRequest((char *)data)) + { + Web_SendWebsocketData(client->id(), 1); + } + + if (info->opcode == WS_TEXT) + { + data[len] = 0; + Serial.printf("%s\n", (char *)data); + } + else + { + for (size_t i = 0; i < info->len; i++) + { + Serial.printf("%02x ", data[i]); + } + Serial.printf("\n"); + } + } + } +} + +// Handles file upload request from the explorer +// requires a GET parameter path, as directory path to the file +void explorerHandleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + + System_UpdateActivityTimer(); + + // New File + if (!index) + { + String utf8FilePath; + static char filePath[MAX_FILEPATH_LENTGH]; + if (request->hasParam("path")) + { + AsyncWebParameter *param = request->getParam("path"); + utf8FilePath = param->value() + "/" + filename; + } + else + { + utf8FilePath = "/" + filename; + } + + convertUtf8ToAscii(utf8FilePath, filePath); + + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(writingFile), utf8FilePath.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + + // Create Ringbuffer for upload + if (explorerFileUploadRingBuffer == NULL) + { + explorerFileUploadRingBuffer = xRingbufferCreate(4096, RINGBUF_TYPE_BYTEBUF); + } + + // Create Queue for receiving a signal from the store task as synchronisation + if (explorerFileUploadStatusQueue == NULL) + { + explorerFileUploadStatusQueue = xQueueCreate(1, sizeof(uint8_t)); + } + + // Create Task for handling the storage of the data + xTaskCreate( + explorerHandleFileStorageTask, /* Function to implement the task */ + "fileStorageTask", /* Name of the task */ + 4000, /* Stack size in words */ + filePath, /* Task input parameter */ + 2 | portPRIVILEGE_BIT, /* Priority of the task */ + &fileStorageTaskHandle /* Task handle. */ + ); + } + + if (len) + { + // stream the incoming chunk to the ringbuffer + xRingbufferSend(explorerFileUploadRingBuffer, data, len, portTICK_PERIOD_MS * 1000); + } + + if (final) + { + // notify storage task that last data was stored on the ring buffer + xTaskNotify(fileStorageTaskHandle, 1u, eNoAction); + // watit until the storage task is sending the signal to finish + uint8_t signal; + xQueueReceive(explorerFileUploadStatusQueue, &signal, portMAX_DELAY); + + // delete task + vTaskDelete(fileStorageTaskHandle); + } +} + +void explorerHandleFileStorageTask(void *parameter) +{ + + File uploadFile; + size_t item_size; + uint8_t *item; + uint8_t value = 0; + + BaseType_t uploadFileNotification; + uint32_t uploadFileNotificationValue; + + uploadFile = gFSystem.open((char *)parameter, "w"); + + for (;;) + { + esp_task_wdt_reset(); + + item = (uint8_t *)xRingbufferReceive(explorerFileUploadRingBuffer, &item_size, portTICK_PERIOD_MS * 100); + if (item != NULL) + { + uploadFile.write(item, item_size); + vRingbufferReturnItem(explorerFileUploadRingBuffer, (void *)item); + } + else + { + // No data in the buffer, check if all data arrived for the file + uploadFileNotification = xTaskNotifyWait(0, 0, &uploadFileNotificationValue, 0); + if (uploadFileNotification == pdPASS) + { + uploadFile.close(); + // done exit loop to terminate + break; + } + vTaskDelay(portTICK_PERIOD_MS * 100); + } + } + // send signal to upload function to terminate + xQueueSend(explorerFileUploadStatusQueue, &value, 0); + vTaskDelete(NULL); +} + +// Sends a list of the content of a directory as JSON file +// requires a GET parameter path for the directory +void explorerHandleListRequest(AsyncWebServerRequest *request) +{ + DynamicJsonDocument jsonBuffer(16384); + //StaticJsonDocument<4096> jsonBuffer; + String serializedJsonString; + AsyncWebParameter *param; + char filePath[MAX_FILEPATH_LENTGH]; + JsonArray obj = jsonBuffer.createNestedArray(); + File root; + if (request->hasParam("path")) + { + param = request->getParam("path"); + convertUtf8ToAscii(param->value(), filePath); + root = gFSystem.open(filePath); + } + else + { + root = gFSystem.open("/"); + } + + if (!root) + { + snprintf(Log_Buffer, Log_BufferLength, (char *)FPSTR(failedToOpenDirectory)); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG); + return; + } + + if (!root.isDirectory()) + { + snprintf(Log_Buffer, Log_BufferLength, (char *)FPSTR(notADirectory)); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG); + return; + } + + File file = root.openNextFile(); + + while (file) + { + // ignore hidden folders, e.g. MacOS spotlight files + if (!startsWith(file.name(), (char *)"/.")) + { + JsonObject entry = obj.createNestedObject(); + convertAsciiToUtf8(file.name(), filePath); + std::string path = filePath; + std::string fileName = path.substr(path.find_last_of("/") + 1); + + entry["name"] = fileName; + entry["dir"].set(file.isDirectory()); + } + file = root.openNextFile(); + + esp_task_wdt_reset(); + } + + serializeJson(obj, serializedJsonString); + request->send(200, "application/json; charset=utf-8", serializedJsonString); +} + +bool explorerDeleteDirectory(File dir) +{ + + File file = dir.openNextFile(); + while (file) + { + + if (file.isDirectory()) + { + explorerDeleteDirectory(file); + } + else + { + gFSystem.remove(file.name()); + } + + file = dir.openNextFile(); + + esp_task_wdt_reset(); + } + + return gFSystem.rmdir(dir.name()); +} + +// Handles delete request of a file or directory +// requires a GET parameter path to the file or directory +void explorerHandleDeleteRequest(AsyncWebServerRequest *request) +{ + File file; + AsyncWebParameter *param; + char filePath[MAX_FILEPATH_LENTGH]; + if (request->hasParam("path")) + { + param = request->getParam("path"); + convertUtf8ToAscii(param->value(), filePath); + if (gFSystem.exists(filePath)) + { + file = gFSystem.open(filePath); + if (file.isDirectory()) + { + if (explorerDeleteDirectory(file)) + { + snprintf(Log_Buffer, Log_BufferLength, "DELETE: %s deleted", param->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "DELETE: Cannot delete %s", param->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } + } + else + { + if (gFSystem.remove(filePath)) + { + snprintf(Log_Buffer, Log_BufferLength, "DELETE: %s deleted", param->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "DELETE: Cannot delete %s", param->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } + } + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "DELETE: Path %s does not exist", param->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } + } + else + { + Log_Println("DELETE: No path variable set", LOGLEVEL_ERROR); + } + request->send(200); + esp_task_wdt_reset(); +} + +// Handles create request of a directory +// requires a GET parameter path to the new directory +void explorerHandleCreateRequest(AsyncWebServerRequest *request) +{ + AsyncWebParameter *param; + char filePath[MAX_FILEPATH_LENTGH]; + if (request->hasParam("path")) + { + param = request->getParam("path"); + convertUtf8ToAscii(param->value(), filePath); + if (gFSystem.mkdir(filePath)) + { + snprintf(Log_Buffer, Log_BufferLength, "CREATE: %s created", param->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "CREATE: Cannot create %s", param->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } + } + else + { + Log_Println("CREATE: No path variable set", LOGLEVEL_ERROR); + } + request->send(200); +} + +// Handles rename request of a file or directory +// requires a GET parameter srcpath to the old file or directory name +// requires a GET parameter dstpath to the new file or directory name +void explorerHandleRenameRequest(AsyncWebServerRequest *request) +{ + AsyncWebParameter *srcPath; + AsyncWebParameter *dstPath; + char srcFullFilePath[MAX_FILEPATH_LENTGH]; + char dstFullFilePath[MAX_FILEPATH_LENTGH]; + if (request->hasParam("srcpath") && request->hasParam("dstpath")) + { + srcPath = request->getParam("srcpath"); + dstPath = request->getParam("dstpath"); + convertUtf8ToAscii(srcPath->value(), srcFullFilePath); + convertUtf8ToAscii(dstPath->value(), dstFullFilePath); + if (gFSystem.exists(srcFullFilePath)) + { + if (gFSystem.rename(srcFullFilePath, dstFullFilePath)) + { + snprintf(Log_Buffer, Log_BufferLength, "RENAME: %s renamed to %s", srcPath->value().c_str(), dstPath->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "RENAME: Cannot rename %s", srcPath->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "RENAME: Path %s does not exist", srcPath->value().c_str()); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } + } + else + { + Log_Println("RENAME: No path variable set", LOGLEVEL_ERROR); + } + + request->send(200); +} + +// Handles audio play requests +// requires a GET parameter path to the audio file or directory +// requires a GET parameter playmode +void explorerHandleAudioRequest(AsyncWebServerRequest *request) +{ + AsyncWebParameter *param; + String playModeString; + uint32_t playMode; + char filePath[MAX_FILEPATH_LENTGH]; + if (request->hasParam("path") && request->hasParam("playmode")) + { + param = request->getParam("path"); + convertUtf8ToAscii(param->value(), filePath); + param = request->getParam("playmode"); + playModeString = param->value(); + + playMode = atoi(playModeString.c_str()); + AudioPlayer_TrackQueueDispatcher(filePath, 0, playMode, 0); + } + else + { + Log_Println("AUDIO: No path variable set", LOGLEVEL_ERROR); + } + + request->send(200); +} + +// Handles uploaded backup-file and writes valid entries into NVS +void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + Led_SetPause(true); // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write + char ebuf[290]; + uint16_t j = 0; + char *token; + uint8_t count = 0; + nvs_t nvsEntry[1]; + + for (size_t i = 0; i < len; i++) + { + if (data[i] != '\n') + { + ebuf[j++] = data[i]; + } + else + { + ebuf[j] = '\0'; + j = 0; + token = strtok(ebuf, stringOuterDelimiter); + while (token != NULL) + { + if (!count) + { + count++; + memcpy(nvsEntry[0].nvsKey, token, strlen(token)); + nvsEntry[0].nvsKey[strlen(token)] = '\0'; + } + else if (count == 1) + { + count = 0; + memcpy(nvsEntry[0].nvsEntry, token, strlen(token)); + nvsEntry[0].nvsEntry[strlen(token)] = '\0'; + } + token = strtok(NULL, stringOuterDelimiter); + } + if (isNumber(nvsEntry[0].nvsKey) && nvsEntry[0].nvsEntry[0] == '#') + { + snprintf(Log_Buffer, Log_BufferLength, "%s: %s => %s", (char *)FPSTR(writeEntryToNvs), nvsEntry[0].nvsKey, nvsEntry[0].nvsEntry); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + gPrefsRfid.putString(nvsEntry[0].nvsKey, nvsEntry[0].nvsEntry); + } + } + } + Led_SetPause(false); +} + +// Dumps all RFID-entries from NVS into a file on SD-card +bool Web_DumpNvsToSd(const char *_namespace, const char *_destFile) +{ + Led_SetPause(true); // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write + esp_partition_iterator_t pi; // Iterator for find + const esp_partition_t *nvs; // Pointer to partition struct + esp_err_t result = ESP_OK; + const char *partname = "nvs"; + uint8_t pagenr = 0; // Page number in NVS + uint8_t i; // Index in Entry 0..125 + uint8_t bm; // Bitmap for an entry + uint32_t offset = 0; // Offset in nvs partition + uint8_t namespace_ID; // Namespace ID found + + pi = esp_partition_find(ESP_PARTITION_TYPE_DATA, // Get partition iterator for + ESP_PARTITION_SUBTYPE_ANY, // this partition + partname); + if (pi) + { + nvs = esp_partition_get(pi); // Get partition struct + esp_partition_iterator_release(pi); // Release the iterator + dbgprint("Partition %s found, %d bytes", partname, nvs->size); + } + else + { + snprintf(Log_Buffer, Log_BufferLength, "Partition %s not found!", partname); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + return NULL; + } + namespace_ID = FindNsID(nvs, _namespace); // Find ID of our namespace in NVS + File backupFile = gFSystem.open(_destFile, FILE_WRITE); + if (!backupFile) + { + return false; + } + while (offset < nvs->size) + { + result = esp_partition_read(nvs, offset, // Read 1 page in nvs partition + &buf, + sizeof(nvs_page)); + if (result != ESP_OK) + { + snprintf(Log_Buffer, Log_BufferLength, "Error reading NVS!"); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + return false; + } + + i = 0; + + while (i < 126) + { + bm = (buf.Bitmap[i / 4] >> ((i % 4) * 2)) & 0x03; // Get bitmap for this entry + if (bm == 2) + { + if ((namespace_ID == 0xFF) || // Show all if ID = 0xFF + (buf.Entry[i].Ns == namespace_ID)) + { // otherwise just my namespace + if (isNumber(buf.Entry[i].Key)) + { + String s = gPrefsRfid.getString((const char *)buf.Entry[i].Key); + backupFile.printf("%s%s%s%s\n", stringOuterDelimiter, buf.Entry[i].Key, stringOuterDelimiter, s.c_str()); + } + } + i += buf.Entry[i].Span; // Next entry + } + else + { + i++; + } + } + offset += sizeof(nvs_page); // Prepare to read next page in nvs + pagenr++; + } + + backupFile.close(); + Led_SetPause(false); + + return true; +} diff --git a/src/Web.h b/src/Web.h new file mode 100644 index 0000000..1c0529d --- /dev/null +++ b/src/Web.h @@ -0,0 +1,5 @@ +#pragma once + +void Web_Init(void); +void Web_Cyclic(void); +void Web_SendWebsocketData(uint32_t client, uint8_t code); diff --git a/src/Wlan.cpp b/src/Wlan.cpp new file mode 100644 index 0000000..fd17606 --- /dev/null +++ b/src/Wlan.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include "settings.h" +#include "AudioPlayer.h" +#include "RotaryEncoder.h" +#include "Log.h" +#include "System.h" +#include "Web.h" + +// Don't change anything here unless you know what you're doing +// HELPER // +// WiFi +unsigned long wifiCheckLastTimestamp = 0; +bool wifiEnabled; // Current status if wifi is enabled +uint32_t wifiStatusToggledTimestamp = 0; +bool wifiNeedsRestart = false; + +// AP-WiFi +IPAddress apIP(192, 168, 4, 1); // Access-point's static IP +IPAddress apNetmask(255, 255, 255, 0); // Access-point's netmask +bool accessPointStarted = false; + +void accessPointStart(const char *SSID, IPAddress ip, IPAddress netmask); +bool getWifiEnableStatusFromNVS(void); +void writeWifiStatusToNVS(bool wifiStatus); + +void Wlan_Init(void) +{ + wifiEnabled = getWifiEnableStatusFromNVS(); +} + +void Wlan_Cyclic(void) +{ + // If wifi whould not be activated, return instantly + if (!wifiEnabled) + { + return; + } + + if (!wifiCheckLastTimestamp || wifiNeedsRestart) + { + // Get credentials from NVS + String strSSID = gPrefsSettings.getString("SSID", "-1"); + if (!strSSID.compareTo("-1")) + { + Log_Println((char *)FPSTR(ssidNotFoundInNvs), LOGLEVEL_ERROR); + } + String strPassword = gPrefsSettings.getString("Password", "-1"); + if (!strPassword.compareTo("-1")) + { + Log_Println((char *)FPSTR(wifiPwdNotFoundInNvs), LOGLEVEL_ERROR); + } + const char *_ssid = strSSID.c_str(); + const char *_pwd = strPassword.c_str(); + + // Get (optional) hostname-configration from NVS + String hostname = gPrefsSettings.getString("Hostname", "-1"); + if (hostname.compareTo("-1")) + { + //WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); + WiFi.setHostname(hostname.c_str()); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(restoredHostnameFromNvs), hostname.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); + } + else + { + Log_Println((char *)FPSTR(wifiHostnameNotSet), LOGLEVEL_INFO); + } + +// Add configration of static IP (if requested) +#ifdef STATIC_IP_ENABLE + snprintf(Log_Buffer, Log_BufferLength, "%s", (char *)FPSTR(tryStaticIpConfig)); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + if (!WiFi.config(IPAddress(LOCAL_IP), IPAddress(GATEWAY_IP), IPAddress(SUBNET_IP), IPAddress(DNS_IP))) + { + snprintf(Log_Buffer, Log_BufferLength, "%s", (char *)FPSTR(staticIPConfigFailed)); + Log_Println(Log_Buffer, LOGLEVEL_ERROR); + } +#endif + + // Try to join local WiFi. If not successful, an access-point is opened + WiFi.begin(_ssid, _pwd); + + uint8_t tryCount = 0; + while (WiFi.status() != WL_CONNECTED && tryCount <= 4) + { + delay(500); + Serial.print(F(".")); + tryCount++; + wifiCheckLastTimestamp = millis(); + if (tryCount >= 4 && WiFi.status() == WL_CONNECT_FAILED) + { + WiFi.begin(_ssid, _pwd); // ESP32-workaround (otherwise WiFi-connection sometimes fails) + } + } + + if (WiFi.status() == WL_CONNECTED) + { + IPAddress myIP = WiFi.localIP(); +#if (LANGUAGE == 1) + snprintf(Log_Buffer, Log_BufferLength, "Aktuelle IP: %d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); +#else + snprintf(Log_Buffer, Log_BufferLength, "Current IP: %d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); +#endif + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + } + else + { // Starts AP if WiFi-connect wasn't successful + accessPointStart((char *)FPSTR(accessPointNetworkSSID), apIP, apNetmask); + } + +#ifdef MDNS_ENABLE + // zero conf, make device available as .local + if (MDNS.begin(hostname.c_str())) + { + MDNS.addService("http", "tcp", 80); + } +#endif + + wifiNeedsRestart = false; + } +} + +void Wlan_ToggleEnable(void) +{ + writeWifiStatusToNVS(!getWifiEnableStatusFromNVS()); +} + +String Wlan_GetIpAddress(void) +{ + return WiFi.localIP().toString(); +} + +// Initialize soft access-point +void accessPointStart(const char *SSID, IPAddress ip, IPAddress netmask) +{ + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(ip, ip, netmask); + WiFi.softAP(SSID); + delay(500); + + Log_Println((char *)FPSTR(apReady), LOGLEVEL_NOTICE); + snprintf(Log_Buffer, Log_BufferLength, "IP-Adresse: %d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]); + Log_Println(Log_Buffer, LOGLEVEL_NOTICE); + + Web_Init(); + + accessPointStarted = true; +} + +// Reads stored WiFi-status from NVS +bool getWifiEnableStatusFromNVS(void) +{ + uint32_t wifiStatus = gPrefsSettings.getUInt("enableWifi", 99); + + // if not set so far, preseed with 1 (enable) + if (wifiStatus == 99) + { + gPrefsSettings.putUInt("enableWifi", 1); + wifiStatus = 1; + } + + return wifiStatus; +} + +// Writes to NVS whether WiFi should be activated +void writeWifiStatusToNVS(bool wifiStatus) +{ + if (!wifiStatus) + { + if (gPrefsSettings.putUInt("enableWifi", 0)) + { // disable + Log_Println((char *)FPSTR(wifiDisabledAfterRestart), LOGLEVEL_NOTICE); + if (gPlayProperties.playMode == WEBSTREAM) + { + AudioPlayer_TrackControlToQueueSender(STOP); + } + delay(300); + WiFi.mode(WIFI_OFF); + wifiEnabled = false; + } + } + else + { + if (gPrefsSettings.putUInt("enableWifi", 1)) + { // enable + Log_Println((char *)FPSTR(wifiEnabledAfterRestart), LOGLEVEL_NOTICE); + wifiNeedsRestart = true; + wifiEnabled = true; + } + } +} + +bool Wlan_IsConnected(void) +{ + return (WiFi.status() == WL_CONNECTED); +} \ No newline at end of file diff --git a/src/Wlan.h b/src/Wlan.h new file mode 100644 index 0000000..3ccdbcd --- /dev/null +++ b/src/Wlan.h @@ -0,0 +1,7 @@ +#pragma once + +void Wlan_Init(void); +void Wlan_Cyclic(void); +bool Wlan_IsConnected(void); +boolean Wlan_ToggleEnable(void); +String Wlan_GetIpAddress(void); \ No newline at end of file diff --git a/src/logmessages.h b/src/logmessages.h index f21c671..5a0610c 100644 --- a/src/logmessages.h +++ b/src/logmessages.h @@ -1,181 +1,183 @@ -static const char stillOnlineMqtt[] PROGMEM = "MQTT: Bin noch online."; -static const char tryConnectMqttS[] PROGMEM = "Versuche Verbindung zu MQTT-Broker aufzubauen"; -static const char mqttOk[] PROGMEM = "MQTT-Session aufgebaut."; -static const char sleepTimerEOP[] PROGMEM = "Sleep-Timer: Nach dem letzten Track der Playlist."; -static const char sleepTimerEOT[] PROGMEM = "Sleep-Timer: Nach dem Ende des laufenden Tracks."; -static const char sleepTimerStop[] PROGMEM = "Sleep-Timer wurde deaktiviert."; -static const char sleepTimerEO5[] PROGMEM = "Sleep Timer: Nach Ende des Titels oder, wenn früher, Ende der Playlist"; -static const char sleepTimerAlreadyStopped[] PROGMEM = "Sleep-Timer ist bereits deaktiviert."; -static const char sleepTimerSetTo[] PROGMEM = "Sleep-Timer gesetzt auf"; -static const char allowButtons[] PROGMEM = "Alle Tasten werden freigegeben."; -static const char lockButtons[] PROGMEM = "Alle Tasten werden gesperrt."; -static const char noPlaylistNotAllowedMqtt[] PROGMEM = "Playmode kann nicht auf 'Keine Playlist' gesetzt werden via MQTT."; -static const char playmodeChangedMQtt[] PROGMEM = "Playmode per MQTT angepasst."; -static const char noPlaymodeChangeIfIdle[] PROGMEM = "Playmode kann nicht verändert werden, wenn keine Playlist aktiv ist."; -static const char noValidTopic[] PROGMEM = "Kein gültiges Topic"; -static const char freePtr[] PROGMEM = "Ptr-Freigabe"; -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 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."; -static const char nameOfFileFound[] PROGMEM = "Gefundenes File"; -static const char reallocCalled[] PROGMEM = "Speicher reallokiert."; -static const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Speicher für lineare Playlist konnte nicht allokiert werden!"; -static const char numberOfValidFiles[] PROGMEM = "Anzahl gültiger Files"; -static const char newLoudnessReceivedQueue[] PROGMEM = "Neue Lautstärke empfangen via Queue"; -static const char newCntrlReceivedQueue[] PROGMEM = "Kontroll-Kommando empfangen via Queue"; -static const char newPlaylistReceived[] PROGMEM = "Neue Playlist empfangen"; -static const char repeatTrackDueToPlaymode[] PROGMEM = "Wiederhole Titel aufgrund von Playmode."; -static const char repeatPlaylistDueToPlaymode[] PROGMEM = "Wiederhole Playlist aufgrund von Playmode."; -static const char cmndStop[] PROGMEM = "Kommando: Stop"; -static const char cmndPause[] PROGMEM = "Kommando: Pause"; -static const char cmndNextTrack[] PROGMEM = "Kommando: Nächster Titel"; -static const char cmndPrevTrack[] PROGMEM = "Kommando: Vorheriger Titel"; -static const char cmndFirstTrack[] PROGMEM = "Kommando: Erster Titel von Playlist"; -static const char cmndLastTrack[] PROGMEM = "Kommando: Letzter Titel von Playlist"; -static const char cmndDoesNotExist[] PROGMEM = "Dieses Kommando existiert nicht."; -static const char lastTrackAlreadyActive[] PROGMEM = "Es wird bereits der letzte Track gespielt."; -static const char firstTrackAlreadyActive[] PROGMEM = "Es wird bereits der erste Track gespielt."; -static const char trackStartAudiobook[] PROGMEM = "Titel wird im Hörspielmodus von vorne gespielt."; -static const char trackStart[] PROGMEM = "Titel wird von vorne gespielt."; -static const char trackChangeWebstream[] PROGMEM = "Im Webradio-Modus kann nicht an den Anfang gesprungen werden."; -static const char endOfPlaylistReached[] PROGMEM = "Ende der Playlist erreicht."; -static const char trackStartatPos[] PROGMEM = "Titel wird abgespielt ab Position"; -static const char rfidScannerReady[] PROGMEM = "RFID-Tags koennen jetzt gescannt werden..."; -static const char rfidTagDetected[] PROGMEM = "RFID-Karte erkannt: "; -static const char rfid15693TagDetected[] PROGMEM = "RFID-Karte (ISO-15693) erkannt: "; -static const char rfidTagReceived[] PROGMEM = "RFID-Karte empfangen"; -static const char rfidTagUnknownInNvs[] PROGMEM = "RFID-Karte ist im NVS nicht hinterlegt."; -static const char goToSleepDueToIdle[] PROGMEM = "Gehe in Deep Sleep wegen Inaktivität..."; -static const char goToSleepDueToTimer[] PROGMEM = "Gehe in Deep Sleep wegen Sleep Timer..."; -static const char goToSleepNow[] PROGMEM = "Gehe jetzt in Deep Sleep!"; -static const char maxLoudnessReached[] PROGMEM = "Maximale Lautstärke bereits erreicht!"; -static const char minLoudnessReached[] PROGMEM = "Minimale Lautstärke bereits erreicht!"; -static const char errorOccured[] PROGMEM = "Fehler aufgetreten!"; -static const char noMp3FilesInDir[] PROGMEM = "Verzeichnis beinhaltet keine mp3-Files."; -static const char modeSingleTrack[] PROGMEM = "Modus: Einzelner Track"; -static const char modeSingleTrackLoop[] PROGMEM = "Modus: Einzelner Track in Endlosschleife"; -static const char modeSingleAudiobook[] PROGMEM = "Modus: Hoerspiel"; -static const char modeSingleAudiobookLoop[] PROGMEM = "Modus: Hoerspiel in Endlosschleife"; -static const char modeAllTrackAlphSorted[] PROGMEM = "Modus: Spiele alle Tracks (alphabetisch sortiert) des Ordners"; -static const char modeAllTrackRandom[] PROGMEM = "Modus: Alle Tracks eines Ordners zufällig"; -static const char modeAllTrackAlphSortedLoop[] PROGMEM = "Modus: Alle Tracks eines Ordners sortiert (alphabetisch) in Endlosschleife"; -static const char modeAllTrackRandomLoop[] PROGMEM = "Modus: Alle Tracks eines Ordners zufällig in Endlosschleife"; -static const char modeWebstream[] PROGMEM = "Modus: Webstream"; -static const char webstreamNotAvailable[] PROGMEM = "Aktuell kein Webstream möglich, da keine WLAN-Verbindung vorhanden!"; -static const char modeDoesNotExist[] PROGMEM = "Abspielmodus existiert nicht!"; -static const char modeRepeatNone[] PROGMEM = "Repeatmodus: Kein Repeat"; -static const char modeRepeatTrack[] PROGMEM = "Repeatmodus: Aktueller Titel"; -static const char modeRepeatPlaylist[] PROGMEM = "Repeatmodus: Gesamte Playlist"; -static const char modeRepeatTracknPlaylist[] PROGMEM = "Repeatmodus: Track und Playlist"; -static const char modificatorAllButtonsLocked[] PROGMEM = "Modifikator: Alle Tasten werden per RFID gesperrt."; -static const char modificatorAllButtonsUnlocked[] PROGMEM = "Modifikator: Alle Tasten werden per RFID freigegeben."; -static const char modificatorSleepd[] PROGMEM = "Modifikator: Sleep-Timer wieder deaktiviert."; -static const char modificatorSleepTimer15[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (15 Minuten)."; -static const char modificatorSleepTimer30[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (30 Minuten)."; -static const char modificatorSleepTimer60[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (60 Minuten)."; -static const char modificatorSleepTimer120[] PROGMEM = "Modifikator: Sleep-Timer per RFID aktiviert (2 Stunden)."; -static const char ledsDimmedToNightmode[] PROGMEM = "LEDs wurden auf Nachtmodus gedimmt."; -static const char modificatorNotallowedWhenIdle[] PROGMEM = "Modifikator kann bei nicht aktivierter Playlist nicht angewendet werden."; -static const char modificatorSleepAtEOT[] PROGMEM = "Modifikator: Sleep-Timer am Ende des Titels aktiviert."; -static const char modificatorSleepAtEOTd[] PROGMEM = "Modifikator: Sleep-Timer am Ende des Titels deaktiviert."; -static const char modificatorSleepAtEOP[] PROGMEM = "Modifikator: Sleep-Timer am Ende der Playlist aktiviert."; -static const char modificatorSleepAtEOPd[] PROGMEM = "Modifikator: Sleep-Timer am Ende der Playlist deaktiviert."; -static const char modificatorAllTrackAlphSortedLoop[] PROGMEM = "Modifikator: Alle Titel (alphabetisch sortiert) in Endlosschleife."; -static const char modificatorAllTrackRandomLoop[] PROGMEM = "Modifikator: Alle Titel (zufällige Reihenfolge) in Endlosschleife."; -static const char modificatorCurTrackLoop[] PROGMEM = "Modifikator: Aktueller Titel in Endlosschleife."; -static const char modificatorCurAudiobookLoop[] PROGMEM = "Modifikator: Aktuelles Hörspiel in Endlosschleife."; -static const char modificatorPlaylistLoopActive[] PROGMEM = "Modifikator: Alle Titel in Endlosschleife aktiviert."; -static const char modificatorPlaylistLoopDeactive[] PROGMEM = "Modifikator: Alle Titel in Endlosschleife deaktiviert."; -static const char modificatorTrackActive[] PROGMEM = "Modifikator: Titel in Endlosschleife aktiviert."; -static const char modificatorTrackDeactive[] PROGMEM = "Modifikator: Titel in Endlosschleife deaktiviert."; -static const char modificatorNotAllowed[] PROGMEM = "Modifikator konnte nicht angewendet werden."; -static const char modificatorLoopRev[] PROGMEM = "Modifikator: Endlosschleife beendet."; -static const char modificatorDoesNotExist[] PROGMEM = "Ein Karten-Modifikator existiert nicht vom Typ"; -static const char errorOccuredNvs[] PROGMEM = "Es ist ein Fehler aufgetreten beim Lesen aus dem NVS!"; -static const char statementsReceivedByServer[] PROGMEM = "Vom Server wurde Folgendes empfangen"; -static const char savedSsidInNvs[] PROGMEM = "Speichere SSID in NVS"; -static const char savedWifiPwdInNvs[] PROGMEM = "Speichere WLAN-Password in NVS"; -static const char apReady[] PROGMEM = "Access-Point geöffnet"; -static const char httpReady[] PROGMEM = "HTTP-Server gestartet."; -static const char unableToMountSd[] PROGMEM = "SD-Karte konnte nicht gemountet werden."; -static const char unableToCreateVolQ[] PROGMEM = "Konnte Volume-Queue nicht anlegen."; -static const char unableToCreateRfidQ[] PROGMEM = "Konnte RFID-Queue nicht anlegen."; -static const char unableToCreateMgmtQ[] PROGMEM = "Konnte Play-Management-Queue nicht anlegen."; -static const char unableToCreatePlayQ[] PROGMEM = "Konnte Track-Queue nicht anlegen.."; -static const char initialBrightnessfromNvs[] PROGMEM = "Initiale LED-Helligkeit wurde aus NVS geladen"; -static const char wroteInitialBrightnessToNvs[] PROGMEM = "Initiale LED-Helligkeit wurde ins NVS geschrieben."; -static const char restoredInitialBrightnessForNmFromNvs[] PROGMEM = "LED-Helligkeit für Nachtmodus wurde aus NVS geladen"; -static const char wroteNmBrightnessToNvs[] PROGMEM = "LED-Helligkeit für Nachtmodus wurde ins NVS geschrieben."; -static const char wroteFtpUserToNvs[] PROGMEM = "FTP-User wurde ins NVS geschrieben."; -static const char restoredFtpUserFromNvs[] PROGMEM = "FTP-User wurde aus NVS geladen"; -static const char wroteFtpPwdToNvs[] PROGMEM = "FTP-Passwort wurde ins NVS geschrieben."; -static const char restoredFtpPwdFromNvs[] PROGMEM = "FTP-Passwort wurde aus NVS geladen"; -static const char restoredMaxInactivityFromNvs[] PROGMEM = "Maximale Inaktivitätszeit wurde aus NVS geladen"; -static const char wroteMaxInactivityToNvs[] PROGMEM = "Maximale Inaktivitätszeit wurde ins NVS geschrieben."; -static const char restoredInitialLoudnessFromNvs[] PROGMEM = "Initiale Lautstärke wurde aus NVS geladen"; -static const char wroteInitialLoudnessToNvs[] PROGMEM = "Initiale Lautstärke wurde ins NVS geschrieben."; -static const char restoredMaxLoudnessForSpeakerFromNvs[] PROGMEM = "Maximale Lautstärke für Lautsprecher wurde aus NVS geladen"; -static const char restoredMaxLoudnessForHeadphoneFromNvs[] PROGMEM = "Maximale Lautstärke für Kopfhörer wurde aus NVS geladen"; -static const char wroteMaxLoudnessForSpeakerToNvs[] PROGMEM = "Maximale Lautstärke für Lautsprecher wurde ins NVS geschrieben."; -static const char wroteMaxLoudnessForHeadphoneToNvs[] PROGMEM = "Maximale Lautstärke für Kopfhörer wurde ins NVS geschrieben."; -static const char maxVolumeSet[] PROGMEM = "Maximale Lautstärke wurde gesetzt auf"; -static const char wroteMqttFlagToNvs[] PROGMEM = "MQTT-Flag wurde ins NVS geschrieben."; -static const char restoredMqttActiveFromNvs[] PROGMEM = "MQTT-Flag (aktiviert) wurde aus NVS geladen"; -static const char restoredMqttDeactiveFromNvs[] PROGMEM = "MQTT-Flag (deaktiviert) wurde aus NVS geladen"; -static const char wroteMqttServerToNvs[] PROGMEM = "MQTT-Server wurde ins NVS geschrieben."; -static const char restoredMqttServerFromNvs[] PROGMEM = "MQTT-Server wurde aus NVS geladen"; -static const char wroteMqttUserToNvs[] PROGMEM = "MQTT-User wurde ins NVS geschrieben."; -static const char restoredMqttUserFromNvs[] PROGMEM = "MQTT-User wurde aus NVS geladen"; -static const char wroteMqttPwdToNvs[] PROGMEM = "MQTT-Passwort wurde ins NVS geschrieben."; -static const char restoredMqttPwdFromNvs[] PROGMEM = "MQTT-Passwort wurde aus NVS geladen"; -static const char restoredMqttPortFromNvs[] PROGMEM = "MQTT-Port wurde aus NVS geladen"; -static const char mqttWithPwd[] PROGMEM = "Verbinde zu MQTT-Server mit User und Passwort"; -static const char mqttWithoutPwd[] PROGMEM = "Verbinde zu MQTT-Server ohne User und Passwort"; -static const char ssidNotFoundInNvs[] PROGMEM = "SSID wurde im NVS nicht gefunden."; -static const char wifiPwdNotFoundInNvs[] PROGMEM = "WLAN-Passwort wurde im NVS nicht gefunden."; -static const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Statische WLAN-IP-Konfiguration wurde im NVS nicht gefunden."; -static const char wifiHostnameNotSet[] PROGMEM = "Keine Hostname-Konfiguration im NVS gefunden."; -static const char mqttConnFailed[] PROGMEM = "Verbindung fehlgeschlagen, versuche in Kürze erneut"; -static const char restoredHostnameFromNvs[] PROGMEM = "Hostname aus NVS geladen"; -static const char currentVoltageMsg[] PROGMEM = "Aktuelle Batteriespannung"; -static const char voltageTooLow[] PROGMEM = "Batteriespannung niedrig"; -static const char sdBootFailedDeepsleep[] PROGMEM = "Bootgang wegen SD fehlgeschlagen. Gehe in Deepsleep..."; -static const char wifiEnabledAfterRestart[] PROGMEM = "WLAN wird aktiviert."; -static const char wifiDisabledAfterRestart[] PROGMEM = "WLAN wird deaktiviert."; -static const char voltageIndicatorLowFromNVS[] PROGMEM = "Unterer Spannungslevel (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; -static const char voltageIndicatorHighFromNVS[] PROGMEM = "Oberer Spannungslevel (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; -static const char voltageCheckIntervalFromNVS[] PROGMEM = "Zyklus für Spannungsmessung (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; -static const char warningLowVoltageFromNVS[] PROGMEM = "Spannungslevel (Batterie) fuer Warnung via Neopixel aus NVS geladen"; -static const char unableToRestoreLastRfidFromNVS[] PROGMEM = "Letzte RFID konnte nicht aus NVS geladen werden"; -static const char restoredLastRfidFromNVS[] PROGMEM = "Letzte RFID wurde aus NVS geladen"; -static const char failedOpenFileForWrite[] PROGMEM = "Öffnen der Datei für den Schreibvorgang fehlgeschlagen"; -static const char fileWritten[] PROGMEM = "Datei geschrieben"; -static const char writeFailed[] PROGMEM = "Schreibvorgang fehlgeschlagen"; -static const char writingFile[] PROGMEM = "Schreibe Datei"; -static const char failedToOpenFileForAppending[] PROGMEM = "Öffnen der Datei zum Schreiben der JSON-Datei fehlgeschlagen"; -static const char listingDirectory[] PROGMEM = "Verzeichnisinhalt anzeigen"; -static const char failedToOpenDirectory[] PROGMEM = "Öffnen des Verzeichnisses fehlgeschlagen"; -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 ESPuino wird neu gestartet...
Zur letzten Seite zurückkehren.

"; -static const char shutdownWebsite[] PROGMEM = "

Der ESPuino wird ausgeschaltet...

"; -static const char mqttMsgReceived[] PROGMEM = "MQTT-Nachricht empfangen"; -static const char trackPausedAtPos[] PROGMEM = "Titel pausiert bei Position"; -static const char freeHeapWithoutFtp[] PROGMEM = "Freier Heap-Speicher vor FTP-Instanzierung"; -static const char freeHeapWithFtp[] PROGMEM = "Freier Heap-Speicher nach FTP-Instanzierung"; -static const char freeHeapAfterSetup[] PROGMEM = "Freier Heap-Speicher nach Setup-Routine"; -static const char tryStaticIpConfig[] PROGMEM = "Statische IP-Konfiguration wird durchgeführt..."; -static const char staticIPConfigFailed[] PROGMEM = "Statische IP-Konfiguration fehlgeschlagen"; -static const char wakeUpRfidNoIso14443[] PROGMEM = "ESP32 wurde vom Kartenleser aus dem Deepsleep aufgeweckt. Allerdings wurde keine ISO-14443-Karte gefunden. Gehe zurück in den Deepsleep..."; -static const char lowPowerCardSuccess[] PROGMEM = "Kartenerkennung via 'low power' erfolgreich durchgeführt"; -static const char rememberLastVolume[] PROGMEM = "Lautstärke vor dem letzten Shutdown wird wiederhergestellt. Dies überschreibt die Einstellung der initialen Lautstärke aus der GUI."; -static const char unableToStartFtpServer[] PROGMEM = "Der FTP-Server konnte nicht gestartet werden. Entweder weil er ist bereits gestartet oder kein WLAN verfügbar ist."; -static const char newPlayModeStereo[] PROGMEM = "Neuer Modus: stereo"; -static const char newPlayModeMono[] PROGMEM = "Neuer Modus: mono"; \ No newline at end of file +#pragma once + +extern const char stillOnlineMqtt[]; +extern const char tryConnectMqttS[]; +extern const char mqttOk[]; +extern const char sleepTimerEOP[]; +extern const char sleepTimerEOT[]; +extern const char sleepTimerStop[]; +extern const char sleepTimerEO5[]; +extern const char sleepTimerAlreadyStopped[]; +extern const char sleepTimerSetTo[]; +extern const char allowButtons[]; +extern const char lockButtons[]; +extern const char noPlaylistNotAllowedMqtt[]; +extern const char playmodeChangedMQtt[]; +extern const char noPlaymodeChangeIfIdle[]; +extern const char noValidTopic[]; +extern const char freePtr[]; +extern const char freeMemory[]; +extern const char writeEntryToNvs[]; +extern const char freeMemoryAfterFree[]; +extern const char releaseMemoryOfOldPlaylist[]; +extern const char dirOrFileDoesNotExist[]; +extern const char unableToAllocateMemForPlaylist[]; +extern const char unableToAllocateMem[]; +extern const char fileModeDetected[]; +extern const char nameOfFileFound[]; +extern const char reallocCalled[]; +extern const char unableToAllocateMemForLinearPlaylist[]; +extern const char numberOfValidFiles[]; +extern const char newLoudnessReceivedQueue[]; +extern const char newCntrlReceivedQueue[]; +extern const char newPlaylistReceived[]; +extern const char repeatTrackDueToPlaymode[]; +extern const char repeatPlaylistDueToPlaymode[]; +extern const char cmndStop[]; +extern const char cmndPause[]; +extern const char cmndNextTrack[]; +extern const char cmndPrevTrack[]; +extern const char cmndFirstTrack[]; +extern const char cmndLastTrack[]; +extern const char cmndDoesNotExist[]; +extern const char lastTrackAlreadyActive[]; +extern const char firstTrackAlreadyActive[]; +extern const char trackStartAudiobook[]; +extern const char trackStart[]; +extern const char trackChangeWebstream[]; +extern const char endOfPlaylistReached[]; +extern const char trackStartatPos[]; +extern const char rfidScannerReady[]; +extern const char rfidTagDetected[]; +extern const char rfid15693TagDetected[]; +extern const char rfidTagReceived[]; +extern const char rfidTagUnknownInNvs[]; +extern const char goToSleepDueToIdle[]; +extern const char goToSleepDueToTimer[]; +extern const char goToSleepNow[]; +extern const char maxLoudnessReached[]; +extern const char minLoudnessReached[]; +extern const char errorOccured[]; +extern const char noMp3FilesInDir[]; +extern const char modeSingleTrack[]; +extern const char modeSingleTrackLoop[]; +extern const char modeSingleAudiobook[]; +extern const char modeSingleAudiobookLoop[]; +extern const char modeAllTrackAlphSorted[]; +extern const char modeAllTrackRandom[]; +extern const char modeAllTrackAlphSortedLoop[]; +extern const char modeAllTrackRandomLoop[]; +extern const char modeWebstream[]; +extern const char webstreamNotAvailable[]; +extern const char modeDoesNotExist[]; +extern const char modeRepeatNone[]; +extern const char modeRepeatTrack[]; +extern const char modeRepeatPlaylist[]; +extern const char modeRepeatTracknPlaylist[]; +extern const char modificatorAllButtonsLocked[]; +extern const char modificatorAllButtonsUnlocked[]; +extern const char modificatorSleepd[]; +extern const char modificatorSleepTimer15[]; +extern const char modificatorSleepTimer30[]; +extern const char modificatorSleepTimer60[]; +extern const char modificatorSleepTimer120[]; +extern const char ledsDimmedToNightmode[]; +extern const char modificatorNotallowedWhenIdle[]; +extern const char modificatorSleepAtEOT[]; +extern const char modificatorSleepAtEOTd[]; +extern const char modificatorSleepAtEOP[]; +extern const char modificatorSleepAtEOPd[]; +extern const char modificatorAllTrackAlphSortedLoop[]; +extern const char modificatorAllTrackRandomLoop[]; +extern const char modificatorCurTrackLoop[]; +extern const char modificatorCurAudiobookLoop[]; +extern const char modificatorPlaylistLoopActive[]; +extern const char modificatorPlaylistLoopDeactive[]; +extern const char modificatorTrackActive[]; +extern const char modificatorTrackDeactive[]; +extern const char modificatorNotAllowed[]; +extern const char modificatorLoopRev[]; +extern const char modificatorDoesNotExist[]; +extern const char errorOccuredNvs[]; +extern const char statementsReceivedByServer[]; +extern const char savedSsidInNvs[]; +extern const char savedWifiPwdInNvs[]; +extern const char apReady[]; +extern const char httpReady[]; +extern const char unableToMountSd[]; +extern const char unableToCreateVolQ[]; +extern const char unableToCreateRfidQ[]; +extern const char unableToCreateMgmtQ[]; +extern const char unableToCreatePlayQ[]; +extern const char initialBrightnessfromNvs[]; +extern const char wroteInitialBrightnessToNvs[]; +extern const char restoredInitialBrightnessForNmFromNvs[]; +extern const char wroteNmBrightnessToNvs[]; +extern const char wroteFtpUserToNvs[]; +extern const char restoredFtpUserFromNvs[]; +extern const char wroteFtpPwdToNvs[]; +extern const char restoredFtpPwdFromNvs[]; +extern const char restoredMaxInactivityFromNvs[]; +extern const char wroteMaxInactivityToNvs[]; +extern const char restoredInitialLoudnessFromNvs[]; +extern const char wroteInitialLoudnessToNvs[]; +extern const char restoredMaxLoudnessForSpeakerFromNvs[]; +extern const char restoredMaxLoudnessForHeadphoneFromNvs[]; +extern const char wroteMaxLoudnessForSpeakerToNvs[]; +extern const char wroteMaxLoudnessForHeadphoneToNvs[]; +extern const char maxVolumeSet[]; +extern const char wroteMqttFlagToNvs[]; +extern const char restoredMqttActiveFromNvs[]; +extern const char restoredMqttDeactiveFromNvs[]; +extern const char wroteMqttServerToNvs[]; +extern const char restoredMqttServerFromNvs[]; +extern const char wroteMqttUserToNvs[]; +extern const char restoredMqttUserFromNvs[]; +extern const char wroteMqttPwdToNvs[]; +extern const char restoredMqttPwdFromNvs[]; +extern const char restoredMqttPortFromNvs[]; +extern const char mqttWithPwd[]; +extern const char mqttWithoutPwd[]; +extern const char ssidNotFoundInNvs[]; +extern const char wifiPwdNotFoundInNvs[]; +extern const char wifiStaticIpConfigNotFoundInNvs[]; +extern const char wifiHostnameNotSet[]; +extern const char mqttConnFailed[]; +extern const char restoredHostnameFromNvs[]; +extern const char currentVoltageMsg[]; +extern const char voltageTooLow[]; +extern const char sdBootFailedDeepsleep[]; +extern const char wifiEnabledAfterRestart[]; +extern const char wifiDisabledAfterRestart[]; +extern const char voltageIndicatorLowFromNVS[]; +extern const char voltageIndicatorHighFromNVS[]; +extern const char voltageCheckIntervalFromNVS[]; +extern const char warningLowVoltageFromNVS[]; +extern const char unableToRestoreLastRfidFromNVS[]; +extern const char restoredLastRfidFromNVS[]; +extern const char failedOpenFileForWrite[]; +extern const char fileWritten[]; +extern const char writeFailed[]; +extern const char writingFile[]; +extern const char failedToOpenFileForAppending[]; +extern const char listingDirectory[]; +extern const char failedToOpenDirectory[]; +extern const char notADirectory[]; +extern const char sdMountedMmc1BitMode[]; +extern const char sdMountedSpiMode[]; +extern const char backupRecoveryWebsite[]; +extern const char restartWebsite[]; +extern const char shutdownWebsite[]; +extern const char mqttMsgReceived[]; +extern const char trackPausedAtPos[]; +extern const char freeHeapWithoutFtp[]; +extern const char freeHeapWithFtp[]; +extern const char freeHeapAfterSetup[]; +extern const char tryStaticIpConfig[]; +extern const char staticIPConfigFailed[]; +extern const char wakeUpRfidNoIso14443[]; +extern const char lowPowerCardSuccess[]; +extern const char rememberLastVolume[]; +extern const char unableToStartFtpServer[]; +extern const char newPlayModeStereo[]; +extern const char newPlayModeMono[]; diff --git a/src/logmessages_EN.h b/src/logmessages_EN.h deleted file mode 100644 index f70b9fa..0000000 --- a/src/logmessages_EN.h +++ /dev/null @@ -1,181 +0,0 @@ -static const char stillOnlineMqtt[] PROGMEM = "MQTT: still online."; -static const char tryConnectMqttS[] PROGMEM = "Trying to connect to MQTT-broker"; -static const char mqttOk[] PROGMEM = "MQTT-connection established."; -static const char sleepTimerEOP[] PROGMEM = "Sleep-timer: after last track of playlist."; -static const char sleepTimerEOT[] PROGMEM = "Sleep-timer: after end of current track."; -static const char sleepTimerStop[] PROGMEM = "Sleep-timer has been disabled."; -static const char sleepTimerEO5[] PROGMEM = "Sleep-timer: after five track or end of playlist - whatever is reached first"; -static const char sleepTimerAlreadyStopped[] PROGMEM = "sleep-timer is already disabled."; -static const char sleepTimerSetTo[] PROGMEM = "sleep-timer adjusted to"; -static const char allowButtons[] PROGMEM = "Unlocking all keys."; -static const char lockButtons[] PROGMEM = "Locking all keys."; -static const char noPlaylistNotAllowedMqtt[] PROGMEM = "Playmode cannot be adjusted to 'no playlist' via MQTT."; -static const char playmodeChangedMQtt[] PROGMEM = "Playlist adjusted via MQTT."; -static const char noPlaymodeChangeIfIdle[] PROGMEM = "Playlist cannot be adjusted while no playlist is active."; -static const char noValidTopic[] PROGMEM = "No valid MQTT-topic"; -static const char freePtr[] PROGMEM = "Releasing Pointer"; -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 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."; -static const char nameOfFileFound[] PROGMEM = "File found"; -static const char reallocCalled[] PROGMEM = "Reallocated memory."; -static const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Unable to allocate memory for linear playlist!"; -static const char numberOfValidFiles[] PROGMEM = "Number of valid files"; -static const char newLoudnessReceivedQueue[] PROGMEM = "New volume received via queue"; -static const char newCntrlReceivedQueue[] PROGMEM = "Control-command received via queue"; -static const char newPlaylistReceived[] PROGMEM = "New playlist received"; -static const char repeatTrackDueToPlaymode[] PROGMEM = "Repeating track due to playmode configured."; -static const char repeatPlaylistDueToPlaymode[] PROGMEM = "Repeating playlist due to playmode configured."; -static const char cmndStop[] PROGMEM = "Command: stop"; -static const char cmndPause[] PROGMEM = "Command: pause"; -static const char cmndNextTrack[] PROGMEM = "Command: next track"; -static const char cmndPrevTrack[] PROGMEM = "Command: previous track"; -static const char cmndFirstTrack[] PROGMEM = "Command: first track of playlist"; -static const char cmndLastTrack[] PROGMEM = "Command: last track of playlist"; -static const char cmndDoesNotExist[] PROGMEM = "Command requested does not exist."; -static const char lastTrackAlreadyActive[] PROGMEM = "Already playing last track."; -static const char firstTrackAlreadyActive[] PROGMEM = "Already playing first track."; -static const char trackStartAudiobook[] PROGMEM = "Starting track in playmode from the very beginning."; -static const char trackStart[] PROGMEM = "Starting track from the very beginning."; -static const char trackChangeWebstream[] PROGMEM = "Playing from the very beginning is not possible while webradio-mode is active."; -static const char endOfPlaylistReached[] PROGMEM = "Reached end of playlist."; -static const char trackStartatPos[] PROGMEM = "Starting track at position"; -static const char rfidScannerReady[] PROGMEM = "RFID-tags can now be applied..."; -static const char rfidTagDetected[] PROGMEM = "RFID-tag detected: "; -static const char rfid15693TagDetected[] PROGMEM = "RFID-ta (ISO-15693) detected: "; -static const char rfidTagReceived[] PROGMEM = "RFID-tag received"; -static const char rfidTagUnknownInNvs[] PROGMEM = "RFID-tag is unkown to NVS."; -static const char goToSleepDueToIdle[] PROGMEM = "Going to deepsleep due to inactivity-timer..."; -static const char goToSleepDueToTimer[] PROGMEM = "Going to deepsleep due to sleep timer..."; -static const char goToSleepNow[] PROGMEM = "Going to deepsleep now!"; -static const char maxLoudnessReached[] PROGMEM = "Already reached max volume!"; -static const char minLoudnessReached[] PROGMEM = "Already reached min volume!"; -static const char errorOccured[] PROGMEM = "Error occured!"; -static const char noMp3FilesInDir[] PROGMEM = "Directory does not contain mp3-files."; -static const char modeSingleTrack[] PROGMEM = "Mode: Single track"; -static const char modeSingleTrackLoop[] PROGMEM = "Mode: single track as infinite loop"; -static const char modeSingleAudiobook[] PROGMEM = "Mode: audiobook"; -static const char modeSingleAudiobookLoop[] PROGMEM = "Mode: audiobook as infinite loop"; -static const char modeAllTrackAlphSorted[] PROGMEM = "Mode: all tracks (in alph. order) of directory"; -static const char modeAllTrackRandom[] PROGMEM = "Mode: all tracks (in random. order) of directory"; -static const char modeAllTrackAlphSortedLoop[] PROGMEM = "Mode: all tracks (in alph. order) of directory as infinite loop"; -static const char modeAllTrackRandomLoop[] PROGMEM = "Mode: all tracks (in random order) of directory as infinite loop"; -static const char modeWebstream[] PROGMEM = "Mode: webstream"; -static const char webstreamNotAvailable[] PROGMEM = "Unable to access webstream as no wifi-connection is available!"; -static const char modeDoesNotExist[] PROGMEM = "Playmode does not exist!"; -static const char modeRepeatNone[] PROGMEM = "Repeatmode: no repeat"; -static const char modeRepeatTrack[] PROGMEM = "Repeatmode: current track"; -static const char modeRepeatPlaylist[] PROGMEM = "Repeatmode: whole playlist"; -static const char modeRepeatTracknPlaylist[] PROGMEM = "Repeatmode: track and playlist"; -static const char modificatorAllButtonsLocked[] PROGMEM = "Modificator: locking all keys via RFID-tag."; -static const char modificatorAllButtonsUnlocked[] PROGMEM = "Modificator: unlocking all keys via RFID-tag."; -static const char modificatorSleepd[] PROGMEM = "Modificator: sleep-Timer deactivated."; -static const char modificatorSleepTimer15[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (15 minutes)."; -static const char modificatorSleepTimer30[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (30 minutes)."; -static const char modificatorSleepTimer60[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (60 minutes)."; -static const char modificatorSleepTimer120[] PROGMEM = "Modificator: sleep-Timer enabled via RFID (2 hours)."; -static const char ledsDimmedToNightmode[] PROGMEM = "Dimmed LEDs to nightmode."; -static const char modificatorNotallowedWhenIdle[] PROGMEM = "Modificator cannot be applied while playlist is inactive."; -static const char modificatorSleepAtEOT[] PROGMEM = "Modificator: adjusted sleep-timer to after end of current track."; -static const char modificatorSleepAtEOTd[] PROGMEM = "Modificator: disabled sleep-timer after end of current track."; -static const char modificatorSleepAtEOP[] PROGMEM = "Modificator: adjusted sleep-timer to after end of playlist."; -static const char modificatorSleepAtEOPd[] PROGMEM = "Modificator: disabled sleep-timer after end of playlist."; -static const char modificatorAllTrackAlphSortedLoop[] PROGMEM = "Modificator: adjusted to all tracks (in alph. order) as infinite loop."; -static const char modificatorAllTrackRandomLoop[] PROGMEM = "Modificator: adjusted to all tracks (in random order) as infinite loop."; -static const char modificatorCurTrackLoop[] PROGMEM = "Modificator: adjusted to current track as infinite loop."; -static const char modificatorCurAudiobookLoop[] PROGMEM = "Modificator: adjusted to current audiobook as infinite loop."; -static const char modificatorPlaylistLoopActive[] PROGMEM = "Modificator: adjusted to all tracks as infinite loop."; -static const char modificatorPlaylistLoopDeactive[] PROGMEM = "Modificator: disabled all tracks as infinite loop."; -static const char modificatorTrackActive[] PROGMEM = "Modificator: adjusted to current track as infinite loop."; -static const char modificatorTrackDeactive[] PROGMEM = "Modificator: disabled current track as infinite loop."; -static const char modificatorNotAllowed[] PROGMEM = "Unable to apply modificator."; -static const char modificatorLoopRev[] PROGMEM = "Modificator: infinite loop ended."; -static const char modificatorDoesNotExist[] PROGMEM = "This type of card-modificator does not exist"; -static const char errorOccuredNvs[] PROGMEM = "Error occured while reading from NVS!"; -static const char statementsReceivedByServer[] PROGMEM = "Data received from server"; -static const char savedSsidInNvs[] PROGMEM = "Storing SSID to NVS"; -static const char savedWifiPwdInNvs[] PROGMEM = "Storing wifi-password to NVS"; -static const char apReady[] PROGMEM = "Started wifi-access-point"; -static const char httpReady[] PROGMEM = "Started HTTP-server."; -static const char unableToMountSd[] PROGMEM = "Unable to mount sd-card."; -static const char unableToCreateVolQ[] PROGMEM = "Unable to create volume-queue."; -static const char unableToCreateRfidQ[] PROGMEM = "Unable to create RFID-queue."; -static const char unableToCreateMgmtQ[] PROGMEM = "Unable to play-management-queue."; -static const char unableToCreatePlayQ[] PROGMEM = "Unable to create track-queue.."; -static const char initialBrightnessfromNvs[] PROGMEM = "Restoring initial LED-brightness from NVS"; -static const char wroteInitialBrightnessToNvs[] PROGMEM = "Storing initial LED-brightness to NVS."; -static const char restoredInitialBrightnessForNmFromNvs[] PROGMEM = "Restored LED-brightness for nightmode from NVS"; -static const char wroteNmBrightnessToNvs[] PROGMEM = "Stored LED-brightness for nightmode to NVS."; -static const char wroteFtpUserToNvs[] PROGMEM = "Stored FTP-user to NVS."; -static const char restoredFtpUserFromNvs[] PROGMEM = "Restored FTP-user from NVS"; -static const char wroteFtpPwdToNvs[] PROGMEM = "Stored FTP-password to NVS."; -static const char restoredFtpPwdFromNvs[] PROGMEM = "Restored FTP-password from NVS"; -static const char restoredMaxInactivityFromNvs[] PROGMEM = "Restored maximum inactivity-time from NVS."; -static const char wroteMaxInactivityToNvs[] PROGMEM = "Stored maximum inactivity-time to NVS."; -static const char restoredInitialLoudnessFromNvs[] PROGMEM = "Restored initial volume from NVS"; -static const char wroteInitialLoudnessToNvs[] PROGMEM = "Stored initial volume to NVS."; -static const char restoredMaxLoudnessForSpeakerFromNvs[] PROGMEM = "Restored maximum volume for speaker from NVS"; -static const char restoredMaxLoudnessForHeadphoneFromNvs[] PROGMEM = "Restored maximum volume for headphone from NVS"; -static const char wroteMaxLoudnessForSpeakerToNvs[] PROGMEM = "Wrote maximum volume for speaker to NVS."; -static const char wroteMaxLoudnessForHeadphoneToNvs[] PROGMEM = "Wrote maximum volume for headphone to NVS."; -static const char maxVolumeSet[] PROGMEM = "Maximum volume set to"; -static const char wroteMqttFlagToNvs[] PROGMEM = "Stored MQTT-flag to NVS."; -static const char restoredMqttActiveFromNvs[] PROGMEM = "Restored MQTT-flag (enabled) from NVS"; -static const char restoredMqttDeactiveFromNvs[] PROGMEM = "Restored MQTT-flag (disabled) from NVS"; -static const char wroteMqttServerToNvs[] PROGMEM = "Stored MQTT-server to NVS."; -static const char restoredMqttServerFromNvs[] PROGMEM = "Restored MQTT-Server from NVS"; -static const char wroteMqttUserToNvs[] PROGMEM = "Stored MQTT-user to NVS."; -static const char restoredMqttUserFromNvs[] PROGMEM = "Restored MQTT-user from NVS"; -static const char wroteMqttPwdToNvs[] PROGMEM = "Stored MQTT-password to NVS."; -static const char restoredMqttPwdFromNvs[] PROGMEM = "Restored MQTT-password from NVS"; -static const char restoredMqttPortFromNvs[] PROGMEM = "Restored MQTT-port from NVS"; -static const char mqttWithPwd[] PROGMEM = "Try to connect to MQTT-server with user und password"; -static const char mqttWithoutPwd[] PROGMEM = "Try to connect to MQTT-server without user und password"; -static const char ssidNotFoundInNvs[] PROGMEM = "Unable to find SSID to NVS."; -static const char wifiPwdNotFoundInNvs[] PROGMEM = "Unable to find wifi-password to NVS."; -static const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Unable to find static wifi-ip-configuration to NVS."; -static const char wifiHostnameNotSet[] PROGMEM = "Unable to find hostname-configuration to NVS."; -static const char mqttConnFailed[] PROGMEM = "Unable to establish mqtt-connection, trying again..."; -static const char restoredHostnameFromNvs[] PROGMEM = "Restored hostname from NVS"; -static const char currentVoltageMsg[] PROGMEM = "Current battery-voltage"; -static const char voltageTooLow[] PROGMEM = "Low battery-voltage"; -static const char sdBootFailedDeepsleep[] PROGMEM = "Failed to boot due to SD. Will go to deepsleep..."; -static const char wifiEnabledAfterRestart[] PROGMEM = "WiFi will be enabled."; -static const char wifiDisabledAfterRestart[] PROGMEM = "WiFi will be disabled ."; -static const char voltageIndicatorLowFromNVS[] PROGMEM = "Restored lower voltage-level for Neopixel-display from NVS"; -static const char voltageIndicatorHighFromNVS[] PROGMEM = "Restored upper voltage-level for Neopixel-display from NVS"; -static const char voltageCheckIntervalFromNVS[] PROGMEM = "Restored interval of battery-measurement or Neopixel-display from NVS"; -static const char warningLowVoltageFromNVS[] PROGMEM = "Restored battery-voltage-level for warning via Neopixel from NVS"; -static const char unableToRestoreLastRfidFromNVS[] PROGMEM = "Unable to restore last RFID from NVS"; -static const char restoredLastRfidFromNVS[] PROGMEM = "Restored last RFID from NVS"; -static const char failedOpenFileForWrite[] PROGMEM = "Failed to open file for writing"; -static const char fileWritten[] PROGMEM = "File written"; -static const char writeFailed[] PROGMEM = "Write failed"; -static const char writingFile[] PROGMEM = "Writing file"; -static const char failedToOpenFileForAppending[] PROGMEM = "Failed to open file for appending"; -static const char listingDirectory[] PROGMEM = "Listing directory"; -static const char failedToOpenDirectory[] PROGMEM = "Failed to open directory"; -static const char notADirectory[] PROGMEM = "Not a directory"; -static const char sdMountedMmc1BitMode[] PROGMEM = "SD card mounted in SPI-mode configured..."; -static const char sdMountedSpiMode[] PROGMEM = "Mounting SD card in SPI-mode..."; -static const char backupRecoveryWebsite[] PROGMEM = "

Backup-file is being applied...
Back to last page.

"; -static const char restartWebsite[] PROGMEM = "

ESPuino is being restarted...
Back to last page.

"; -static const char shutdownWebsite[] PROGMEM = "

Der ESPuino is being shutdown...

"; -static const char mqttMsgReceived[] PROGMEM = "MQTT-message received"; -static const char trackPausedAtPos[] PROGMEM = "Track paused at position"; -static const char freeHeapWithoutFtp[] PROGMEM = "Free heap before FTP-allocation"; -static const char freeHeapWithFtp[] PROGMEM = "Free heap after FTP-allocation"; -static const char freeHeapAfterSetup[] PROGMEM = "Free heap after setup"; -static const char tryStaticIpConfig[] PROGMEM = "Performing static IP-configuration..."; -static const char staticIPConfigFailed[] PROGMEM = "Static IP-configuration failed"; -static const char wakeUpRfidNoIso14443[] PROGMEM = "Wakeup caused by low power card-detection. RF-field changed but no ISO-14443 card on reader was found. So I'll return back to sleep now..."; -static const char lowPowerCardSuccess[] PROGMEM = "Switch to low power card-detection: success"; -static const char rememberLastVolume[] PROGMEM = "Restored volume used before last shutdown. This overwrites the initial volume configured via webgui."; -static const char unableToStartFtpServer[] PROGMEM = "FTP-server cannot be started. This is because FTP-service is already active of because WiFi is unavailable."; -static const char newPlayModeStereo[] PROGMEM = "New mode: stereo"; -static const char newPlayModeMono[] PROGMEM = "New mode: mono"; diff --git a/src/main.cpp b/src/main.cpp index 3ea2d56..6c49e89 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5199 +1,244 @@ // !!! MAKE SURE TO EDIT settings.h !!! +#include +#include +#include "settings.h" // Contains all user-relevant settings (general) + +#include "AudioPlayer.h" +#include "Battery.h" +#include "Bluetooth.h" +#include "Button.h" +#include "Cmd.h" +#include "Common.h" +#include "Ftp.h" +#include "IrReceiver.h" +#include "Led.h" +#include "Log.h" +#include "Mqtt.h" +#include "MemX.h" +#include "Port.h" +#include "Queues.h" +#include "Rfid.h" +#include "RotaryEncoder.h" +#include "SdCard.h" +#include "System.h" +#include "Web.h" +#include "Wlan.h" -#include "settings.h" // Contains all user-relevant settings (general) - -// !!! MAKE SURE TO EDIT PLATFORM SPECIFIC settings-****.h !!! -#if (HAL == 1) - #include "settings-lolin32.h" // Contains all user-relevant settings for Wemos Lolin32 -#elif (HAL == 2) - #include "settings-espa1s.h" // Contains all user-relevant settings for ESP32-A1S Audiokit -#elif (HAL == 3) - #include "settings-lolin_d32.h" // Contains all user-relevant settings for Wemos Lolin D32 -#elif (HAL == 4) - #include "settings-lolin_d32_pro.h" // Contains all user-relevant settings for Wemos Lolin D32 pro -#elif (HAL == 5) - #include "settings-ttgo_t8.h" // Contains all user-relevant settings for Lilygo TTGO T8 1.7 -#elif (HAL == 99) - #include "settings-custom.h" // Contains all user-relevant settings custom-board -#endif - -#ifdef USEROTARY_ENABLE - #include -#endif -#include "Arduino.h" -#include -#ifdef MDNS_ENABLE - #include -#endif -#ifdef FTP_ENABLE - #include "ESP32FtpServer.h" -#endif -#ifdef BLUETOOTH_ENABLE - #include "esp_bt.h" - #include "BluetoothA2DPSink.h" -#endif -#ifdef IR_CONTROL_ENABLE - #include -#endif -#include "Audio.h" -#include "SPI.h" -#include "FS.h" -#ifdef SD_MMC_1BIT_MODE - #include "SD_MMC.h" -#else - #include "SD.h" -#endif -#include "esp_task_wdt.h" -#ifdef RFID_READER_TYPE_MFRC522_SPI - #include -#endif -#if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(PORT_EXPANDER_ENABLE) - #include "Wire.h" -#endif -#ifdef RFID_READER_TYPE_MFRC522_I2C - #include -#endif -#ifdef RFID_READER_TYPE_PN5180 - #include - #include - #include -#endif -#include -#ifdef MQTT_ENABLE - #define MQTT_SOCKET_TIMEOUT 1 // https://github.com/knolleary/pubsubclient/issues/403 - #include -#endif -#include -#ifdef NEOPIXEL_ENABLE - #include -#endif - -#if (LANGUAGE == 1) - #include "logmessages.h" - #include "HTMLmanagement.h" - #include "HTMLaccesspoint.h" -#endif -#if (LANGUAGE == 2) - #include "logmessages_EN.h" - #include "HTMLmanagement_EN.h" - #include "HTMLaccesspoint_EN.h" -#endif - -#include -#include -#include -#include -#include "freertos/ringbuf.h" -#include "values.h" - -// Prototypes -void accessPointStart(const char *SSID, IPAddress ip, IPAddress netmask); -void actionError(void); -void actionOk(void); -static int arrSortHelper(const void* a, const void* b); -void batteryVoltageTester(void); -void buttonHandler(); -void deepSleepManager(void); -bool digitalReadFromAll(const uint8_t _channel); -void doButtonActions(void); -void doRfidCardModifications(const uint32_t mod); -void doCmdAction(const uint16_t mod); -bool dumpNvsToSd(char *_namespace, char *_destFile); -bool endsWith (const char *str, const char *suf); -bool fileValid(const char *_fileItem); -void freeMultiCharArray(char **arr, const uint32_t cnt); -uint8_t getRepeatMode(void); -bool getWifiEnableStatusFromNVS(void); -void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); -void convertAsciiToUtf8(String asciiString, char *utf8String); -void convertUtf8ToAscii(String utf8String, char *asciiString); -void explorerHandleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); -void explorerHandleFileStorageTask(void *parameter); -void explorerHandleListRequest(AsyncWebServerRequest *request); -void explorerHandleDeleteRequest(AsyncWebServerRequest *request); -void explorerHandleCreateRequest(AsyncWebServerRequest *request); -void explorerHandleRenameRequest(AsyncWebServerRequest *request); -void explorerHandleAudioRequest(AsyncWebServerRequest *request); -void headphoneVolumeManager(void); -bool isNumber(const char *str); -void loggerNl(const uint8_t _currentLogLevel, const char *str, const uint8_t _logLevel); -void logger(const uint8_t _currentLogLevel, const char *str, const uint8_t _logLevel); -float measureBatteryVoltage(void); -#ifdef MQTT_ENABLE - void callback(const char *topic, const byte *payload, uint32_t length); - bool publishMqtt(const char *topic, const char *payload, bool retained); - void postHeartbeatViaMqtt(void); - bool reconnect(); -#endif -size_t nvsRfidWriteWrapper (const char *_rfidCardId, const char *_track, const uint32_t _playPosition, const uint8_t _playMode, const uint16_t _trackLastPlayed, const uint16_t _numberOfTracks); -void onWebsocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); -#ifdef PORT_EXPANDER_ENABLE - bool portExpanderHandler(); -#endif -bool processJsonRequest(char *_serialJson); -void randomizePlaylist (char *str[], const uint32_t count); -char ** returnPlaylistFromWebstream(const char *_webUrl); -char ** returnPlaylistFromSD(File _fileOrDirectory); -#ifdef RFID_READER_TYPE_PN5180 - void rfidScanner(void *parameter); -#else - void rfidScanner(void); -#endif -void sleepHandler(void) ; -void sortPlaylist(const char** arr, int n); -bool startsWith(const char *str, const char *pre); -String templateProcessor(const String& templ); -void trackControlToQueueSender(const uint8_t trackCommand); -void rfidPreferenceLookupHandler (void); -void sendWebsocketData(uint32_t client, uint8_t code); -void setupVolume(void); -void trackQueueDispatcher(const char *_sdFile, const uint32_t _lastPlayPos, const uint32_t _playMode, const uint16_t _trackLastPlayed); -#ifdef USEROTARY_ENABLE - void rotaryVolumeHandler(const int32_t _minVolume, const int32_t _maxVolume); -#endif -void volumeToQueueSender(const int32_t _newVolume, bool reAdjustRotary); -wl_status_t wifiManager(void); -bool writeWifiStatusToNVS(bool wifiStatus); -void bluetoothHandler(void); -uint8_t readOperationModeFromNVS(void); -bool setOperationMode(uint8_t operationMode); -char * x_calloc(uint32_t _allocSize, uint32_t _unitSize); -char * x_malloc(uint32_t _allocSize); -char * x_strdup(const char *_str); -char * x_strndup(const char *_str, uint32_t _len); - - -// Serial-logging buffer -uint8_t serialLoglength = 200; -char *logBuf; // Defintion in setup() - -// FilePathLength -#define MAX_FILEPATH_LENTGH 256 - -#ifdef HEADPHONE_ADJUST_ENABLE - bool headphoneLastDetectionState; - uint32_t headphoneLastDetectionTimestamp = 0; -#endif - -#ifdef BLUETOOTH_ENABLE - BluetoothA2DPSink *a2dp_sink; -#endif - -#ifdef MEASURE_BATTERY_VOLTAGE - uint16_t maxAnalogValue = 4095; // Highest value given by analogRead(); don't change! - uint32_t lastVoltageCheckTimestamp = 0; - #ifdef NEOPIXEL_ENABLE - bool showVoltageWarning = false; - #endif -#endif - -#ifdef PLAY_LAST_RFID_AFTER_REBOOT - bool recoverLastRfid = true; -#endif - -typedef struct { // Bit field - uint8_t playMode: 4; // playMode - char **playlist; // playlist - bool repeatCurrentTrack: 1; // If current track should be looped - bool repeatPlaylist: 1; // If whole playlist should be looped - uint16_t currentTrackNumber: 9; // Current tracknumber - uint16_t numberOfTracks: 9; // Number of tracks in playlist - unsigned long startAtFilePos; // Offset to start play (in bytes) - uint8_t currentRelPos: 7; // Current relative playPosition (in %) - bool sleepAfterCurrentTrack: 1; // If uC should go to sleep after current track - bool sleepAfterPlaylist: 1; // If uC should go to sleep after whole playlist - bool saveLastPlayPosition: 1; // If playposition/current track should be saved (for AUDIOBOOK) - char playRfidTag[13]; // ID of RFID-tag that started playlist - bool pausePlay: 1; // If pause is active - bool trackFinished: 1; // If current track is finished - bool playlistFinished: 1; // If whole playlist is finished - uint8_t playUntilTrackNumber: 6; // Number of tracks to play after which uC goes to sleep - uint8_t seekmode: 2; // If seekmode is active and if yes: forward or backwards? - bool newPlayMono: 1; // true if mono; false if stereo (helper) - bool currentPlayMono: 1; // true if mono; false if stereo -} playProps; -playProps playProperties; - -typedef struct { - char nvsKey[13]; - char nvsEntry[275]; -} nvs_t; - -// Operation Mode -volatile uint8_t operationMode; - -// Configuration of initial values (for the first start) goes here.... -// There's no need to change them here as they can be configured via webinterface -// Neopixel -uint8_t initialLedBrightness = 16; // Initial brightness of Neopixel -uint8_t ledBrightness = initialLedBrightness; -uint8_t nightLedBrightness = 2; // Brightness of Neopixel in nightmode - -// MQTT -bool enableMqtt = true; -#ifdef MQTT_ENABLE - uint8_t const stillOnlineInterval = 60; // Interval 'I'm still alive' is sent via MQTT (in seconds) - uint32_t mqttLastRetryTimestamp = 0; -#endif - -uint8_t const cardIdSize = 4; // RFID -// Volume -uint8_t maxVolume = 21; // Current maximum volume that can be adjusted -uint8_t maxVolumeSpeaker = 21; // Maximum volume that can be adjusted in speaker-mode (default; can be changed later via GUI) -uint8_t minVolume = 0; // Lowest volume that can be adjusted -uint8_t initVolume = 3; // 0...21 (If not found in NVS, this one will be taken) (default; can be changed later via GUI) -#ifdef HEADPHONE_ADJUST_ENABLE - uint8_t maxVolumeHeadphone = 11; // Maximum volume that can be adjusted in headphone-mode (default; can be changed later via GUI) -#endif -// Sleep -uint8_t maxInactivityTime = 10; // Time in minutes, after uC is put to deep sleep because of inactivity (and modified later via GUI) -uint8_t sleepTimer = 30; // Sleep timer in minutes that can be optionally used (and modified later via MQTT or RFID) -// FTP -uint8_t ftpUserLength = 10; // Length will be published n-1 as maxlength to GUI -uint8_t ftpPasswordLength = 15; // Length will be published n-1 as maxlength to GUI -char *ftpUser = x_strndup((char*) "esp32", ftpUserLength); // FTP-user (default; can be changed later via GUI) -char *ftpPassword = x_strndup((char*) "esp32", ftpPasswordLength); // FTP-password (default; can be changed later via GUI) - - -// Don't change anything here unless you know what you're doing -// HELPER // -// WiFi -unsigned long wifiCheckLastTimestamp = 0; -bool wifiEnabled; // Current status if wifi is enabled -uint32_t wifiStatusToggledTimestamp = 0; -bool webserverStarted = false; -bool wifiNeedsRestart = false; -// Neopixel -#ifdef NEOPIXEL_ENABLE - bool showLedError = false; - bool showLedOk = false; - bool showPlaylistProgress = false; - bool showRewind = false; - bool showLedVoltage = false; - bool pauseNeopixel = false; // Used to pause Neopixel-signalisation (while NVS-writes as this leads to exceptions; don't know why) -#endif -// MQTT -#ifdef MQTT_ENABLE - unsigned long lastOnlineTimestamp = 0; -#endif -// RFID -unsigned long lastRfidCheckTimestamp = 0; -char *currentRfidTagId = NULL; -// Sleep -unsigned long lastTimeActiveTimestamp = 0; // Timestamp of last user-interaction -unsigned long sleepTimerStartTimestamp = 0; // Flag if sleep-timer is active -bool gotoSleep = false; // Flag for turning uC immediately into deepsleep -bool sleeping = false; // Flag for turning into deepsleep is in progress -bool lockControls = false; // Flag if buttons and rotary encoder is locked -bool bootComplete = false; -// Rotary encoder-helper -int32_t lastEncoderValue; -int32_t currentEncoderValue; -int32_t lastVolume = -1; // Don't change -1 as initial-value! -uint8_t currentVolume = initVolume; -//////////// - -// AP-WiFi -IPAddress apIP(192, 168, 4, 1); // Access-point's static IP -IPAddress apNetmask(255, 255, 255, 0); // Access-point's netmask -bool accessPointStarted = false; - - -// MQTT-configuration -// Please note: all lengths will be published n-1 as maxlength to GUI -uint8_t mqttServerLength = 32; -uint8_t mqttUserLength = 16; -uint8_t mqttPasswordLength = 16; - -// Please note: all of them are defaults that can be changed later via GUI -char *mqtt_server = x_strndup((char*) "192.168.2.43", mqttServerLength); // IP-address of MQTT-server (if not found in NVS this one will be taken) -char *mqttUser = x_strndup((char*) "mqtt-user", mqttUserLength); // MQTT-user -char *mqttPassword = x_strndup((char*) "mqtt-password", mqttPasswordLength); // MQTT-password*/ -uint16_t mqttPort = 1883; // MQTT-Port - -char stringDelimiter[] = "#"; // Character used to encapsulate data in linear NVS-strings (don't change) -char stringOuterDelimiter[] = "^"; // Character used to encapsulate encapsulated data along with RFID-ID in backup-file - - -void notFound(AsyncWebServerRequest *request) { - request->send(404, "text/plain", "Not found"); -} -AsyncWebServer wServer(80); -AsyncWebSocket ws("/ws"); -AsyncEventSource events("/events"); - - -// Audio/mp3 -#ifndef SD_MMC_1BIT_MODE - SPIClass spiSD(HSPI); - fs::FS FSystem = (fs::FS)SD; -#else - fs::FS FSystem = (fs::FS)SD_MMC; -#endif - -TaskHandle_t mp3Play; -#ifdef RFID_READER_TYPE_PN5180 - TaskHandle_t rfid; -#endif -TaskHandle_t fileStorageTaskHandle; - -#ifdef NEOPIXEL_ENABLE - TaskHandle_t LED; -#endif - -// I2C -#if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(PORT_EXPANDER_ENABLE) - static TwoWire i2cBusTwo = TwoWire(1); -#endif -#ifdef RFID_READER_TYPE_MFRC522_I2C - static MFRC522_I2C mfrc522(MFRC522_ADDR, MFRC522_RST_PIN, &i2cBusTwo); -#endif - -#ifdef PORT_EXPANDER_ENABLE - uint8_t expanderPorts[portsToRead]; -#endif - -#if (HAL == 2) - #include "AC101.h" - static TwoWire i2cBusOne = TwoWire(0); - static AC101 ac(&i2cBusOne); -#endif -#ifdef RFID_READER_TYPE_MFRC522_SPI - static MFRC522 mfrc522(RFID_CS, RST_PIN); -#endif - -// FTP -#ifdef FTP_ENABLE - FtpServer *ftpSrv; // Heap-alloction takes place later (when needed) - bool ftpEnableLastStatus = false; - bool ftpEnableCurrentStatus = false; -#endif - -// Info: SSID / password are stored in NVS -WiFiClient wifiClient; -IPAddress myIP; - -// MQTT-helper -#ifdef MQTT_ENABLE - PubSubClient MQTTclient(wifiClient); -#endif - -// Rotary encoder-configuration -#ifdef USEROTARY_ENABLE - ESP32Encoder encoder; -#endif - -// HW-Timer -#ifndef IR_CONTROL_ENABLE - hw_timer_t *timer = NULL; -#else - uint32_t lastRcCmdTimestamp = 0; -#endif -volatile SemaphoreHandle_t timerSemaphore; - -// Button-helper -typedef struct { - bool lastState : 1; - bool currentState : 1; - bool isPressed : 1; - bool isReleased : 1; - unsigned long lastPressedTimestamp; - unsigned long lastReleasedTimestamp; -} t_button; - -t_button buttons[7]; // next + prev + pplay + rotEnc + button4 + button5 + dummy-button -uint8_t shutdownButton = 99; // Helper used for Neopixel: stores button-number of shutdown-button - -Preferences prefsRfid; -Preferences prefsSettings; -static const char prefsRfidNamespace[] PROGMEM = "rfidTags"; // Namespace used to save IDs of rfid-tags -static const char prefsSettingsNamespace[] PROGMEM = "settings"; // Namespace used for generic settings - -QueueHandle_t volumeQueue; -QueueHandle_t trackQueue; -QueueHandle_t trackControlQueue; -QueueHandle_t rfidCardQueue; - -RingbufHandle_t explorerFileUploadRingBuffer; -QueueHandle_t explorerFileUploadStatusQueue; - -// Only enable those buttons that are not disabled (99 or >115) -// 0 -> 39: GPIOs -// 100 -> 115: Port-expander -#if (NEXT_BUTTON >= 0 && NEXT_BUTTON <= 39) - #define BUTTON_0_ENABLE -#elif (NEXT_BUTTON >= 100 && NEXT_BUTTON <= 115) - #define EXPANDER_0_ENABLE -#endif -#if (PREVIOUS_BUTTON >= 0 && PREVIOUS_BUTTON <= 39) - #define BUTTON_1_ENABLE -#elif (PREVIOUS_BUTTON >= 100 && PREVIOUS_BUTTON <= 115) - #define EXPANDER_1_ENABLE -#endif -#if (PAUSEPLAY_BUTTON >= 0 && PAUSEPLAY_BUTTON <= 39) - #define BUTTON_2_ENABLE -#elif (PAUSEPLAY_BUTTON >= 100 && PAUSEPLAY_BUTTON <= 115) - #define EXPANDER_2_ENABLE -#endif -#ifdef USEROTARY_ENABLE - #if (DREHENCODER_BUTTON >= 0 && DREHENCODER_BUTTON <= 39) - #define BUTTON_3_ENABLE - #elif (DREHENCODER_BUTTON >= 100 && DREHENCODER_BUTTON <= 115) - #define EXPANDER_3_ENABLE - #endif -#endif -#if (BUTTON_4 >= 0 && BUTTON_4 <= 39) - #define BUTTON_4_ENABLE -#elif (BUTTON_4 >= 100 && BUTTON_4 <= 115) - #define EXPANDER_4_ENABLE -#endif -#if (BUTTON_5 >= 0 && BUTTON_5 <= 39) - #define BUTTON_5_ENABLE -#elif (BUTTON_5 >= 100 && BUTTON_5 <= 115) - #define EXPANDER_5_ENABLE -#endif - - - -/* Wrapper-function for serial-logging (with newline) - _currentLogLevel: loglevel that's currently active - _logBuffer: char* to log - _minLogLevel: loglevel configured for this message. - - If (_currentLogLevel <= _minLogLevel) message will be logged -*/ -void loggerNl(const uint8_t _currentLogLevel, const char *_logBuffer, const uint8_t _minLogLevel) { - if (_currentLogLevel >= _minLogLevel) { - Serial.println(_logBuffer); - } -} - -/* Wrapper-function for serial-logging (without newline) */ -void logger(const uint8_t _currentLogLevel, const char *_logBuffer, const uint8_t _minLogLevel) { - if (_currentLogLevel >= _minLogLevel) { - Serial.print(_logBuffer); - } -} - - -// Indicate if error occured -void actionError(void) { - #ifdef NEOPIXEL_ENABLE - showLedError = true; - #endif -} - - -// Indicate if action was ok -void actionOk(void) { - #ifdef NEOPIXEL_ENABLE - showLedOk = true; - #endif -} - - -// Wraps strdup(). Without PSRAM, strdup is called => so heap is used. -// With PSRAM being available, the same is done what strdup() does, but with allocation on PSRAM. -char * x_strdup(const char *_str) { - if (!psramInit()) { - return strdup(_str); - } else { - char *dst = (char *) ps_malloc(strlen (_str) + 1); - if (dst == NULL) { - return NULL; - } - strcpy(dst, _str); - return dst; - } -} - - -// Wraps strndup(). Without PSRAM, strdup is called => so heap is used. -// With PSRAM being available, the same is done what strndup() does, but with allocation on PSRAM. -char * x_strndup(const char *_str, uint32_t _len) { - if (!psramInit()) { - return strndup(_str, _len); - } else { - char *dst = (char *) ps_malloc(_len + 1); - if (dst == NULL) { - return NULL; - } - strncpy(dst, _str, _len); - dst[_len] = '\0'; - return dst; - } -} - - -// Wraps ps_malloc() and malloc(). Selection depends on whether PSRAM is available or not. -char * x_malloc(uint32_t _allocSize) { - if (psramInit()) { - return (char *) ps_malloc(_allocSize); - } else { - return (char *) malloc(_allocSize); - } -} - - -// Wraps ps_calloc() and calloc(). Selection depends on whether PSRAM is available or not. -char * x_calloc(uint32_t _allocSize, uint32_t _unitSize) { - if (psramInit()) { - return (char *) ps_calloc(_allocSize, _unitSize); - } else { - return (char *) calloc(_allocSize, _unitSize); - } -} - - -void IRAM_ATTR onTimer() { - xSemaphoreGiveFromISR(timerSemaphore, NULL); -} - - -#ifdef PLAY_LAST_RFID_AFTER_REBOOT - // Store last RFID-tag to NVS - void storeLastRfidPlayed(char *_rfid) { - prefsSettings.putString("lastRfid", (String) _rfid); - } - - // Get last RFID-tag applied from NVS - void recoverLastRfidPlayed(void) { - if (recoverLastRfid) { - if (operationMode == OPMODE_BLUETOOTH) { // Don't recover if BT-mode is desired - recoverLastRfid = false; - return; - } - recoverLastRfid = false; - String lastRfidPlayed = prefsSettings.getString("lastRfid", "-1"); - if (!lastRfidPlayed.compareTo("-1")) { - loggerNl(serialDebug,(char *) FPSTR(unableToRestoreLastRfidFromNVS), LOGLEVEL_INFO); - } else { - char *lastRfid = x_strdup(lastRfidPlayed.c_str()); - xQueueSend(rfidCardQueue, &lastRfid, 0); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredLastRfidFromNVS), lastRfidPlayed.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } - } - } -#endif - - -#ifdef MEASURE_BATTERY_VOLTAGE - // The average of several analog reads will be taken to reduce the noise (Note: One analog read takes ~10µs) - float measureBatteryVoltage(void) { - float factor = 1 / ((float) rdiv2/(rdiv2+rdiv1)); - float averagedAnalogValue = 0; - uint8_t i; - for (i=0; i<=19; i++) { - averagedAnalogValue += (float)analogRead(VOLTAGE_READ_PIN); - } - averagedAnalogValue /= 20.0; - return (averagedAnalogValue / maxAnalogValue) * referenceVoltage * factor + offsetVoltage; - } - - // Measures voltage of a battery as per interval or after bootup (after allowing a few seconds to settle down) - void batteryVoltageTester(void) { - if ((millis() - lastVoltageCheckTimestamp >= voltageCheckInterval*60000) || (!lastVoltageCheckTimestamp && millis()>=10000)) { - float voltage = measureBatteryVoltage(); - #ifdef NEOPIXEL_ENABLE - if (voltage <= warningLowVoltage) { - snprintf(logBuf, serialLoglength, "%s: (%.2f V)", (char *) FPSTR(voltageTooLow), voltage); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - showVoltageWarning = true; - } - #endif - - #ifdef MQTT_ENABLE - char vstr[6]; - snprintf(vstr, 6, "%.2f", voltage); - publishMqtt((char *) FPSTR(topicBatteryVoltage), vstr, false); - #endif - snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(currentVoltageMsg), voltage); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - lastVoltageCheckTimestamp = millis(); - } - } -#endif - -#ifdef PORT_EXPANDER_ENABLE - // Reads input from port-expander and writes output into global array - // Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA9555.pdf - bool portExpanderHandler() { - i2cBusTwo.beginTransmission(expanderI2cAddress); - for (uint8_t i=0; i 107 - return (expanderPorts[1] & (1 << (_channel-108))); // Remove offset 100 + 8 (return false if pressed) - } else { - return true; - } - - #endif - - default: // Everything else (doesn't make sense at all) isn't supposed to be pressed - return true; - } -} - - -// If timer-semaphore is set, read buttons (unless controls are locked) -void buttonHandler() { - if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) { - if (lockControls) { - return; - } - #ifdef PORT_EXPANDER_ENABLE - portExpanderHandler(); - #endif - unsigned long currentTimestamp = millis(); - - // Buttons can be mixed between GPIO and port-expander. - // But at the same time only one of them can be for example NEXT_BUTTON - #if defined(BUTTON_0_ENABLE) || defined(EXPANDER_0_ENABLE) - buttons[0].currentState = digitalReadFromAll(NEXT_BUTTON); - #endif - #if defined(BUTTON_1_ENABLE) || defined(EXPANDER_1_ENABLE) - buttons[1].currentState = digitalReadFromAll(PREVIOUS_BUTTON); - #endif - #if defined(BUTTON_2_ENABLE) || defined(EXPANDER_2_ENABLE) - buttons[2].currentState = digitalReadFromAll(PAUSEPLAY_BUTTON); - #endif - #if defined(BUTTON_3_ENABLE) || defined(EXPANDER_3_ENABLE) - buttons[3].currentState = digitalReadFromAll(DREHENCODER_BUTTON); - #endif - #if defined(BUTTON_4_ENABLE) || defined(EXPANDER_4_ENABLE) - buttons[4].currentState = digitalReadFromAll(BUTTON_4); - #endif - #if defined(BUTTON_5_ENABLE) || defined(EXPANDER_5_ENABLE) - buttons[5].currentState = digitalReadFromAll(BUTTON_5); - #endif - - // Iterate over all buttons in struct-array - for (uint8_t i=0; i < sizeof(buttons) / sizeof(buttons[0]); i++) { - if (buttons[i].currentState != buttons[i].lastState && currentTimestamp - buttons[i].lastPressedTimestamp > buttonDebounceInterval) { - if (!buttons[i].currentState) { - buttons[i].isPressed = true; - buttons[i].lastPressedTimestamp = currentTimestamp; - } else { - buttons[i].isReleased = true; - buttons[i].lastReleasedTimestamp = currentTimestamp; - } - } - buttons[i].lastState = buttons[i].currentState; - } - } - doButtonActions(); -} - - -// Do corresponding actions for all buttons -void doButtonActions(void) { - if (buttons[0].isPressed && buttons[1].isPressed) { - buttons[0].isPressed = false; - buttons[1].isPressed = false; - doCmdAction(BUTTON_MULTI_01); - } - else if (buttons[0].isPressed && buttons[2].isPressed) { - buttons[0].isPressed = false; - buttons[2].isPressed = false; - doCmdAction(BUTTON_MULTI_02); - } - else if (buttons[0].isPressed && buttons[3].isPressed) { - buttons[0].isPressed = false; - buttons[3].isPressed = false; - doCmdAction(BUTTON_MULTI_03); - } - else if (buttons[0].isPressed && buttons[4].isPressed) { - buttons[0].isPressed = false; - buttons[4].isPressed = false; - doCmdAction(BUTTON_MULTI_04); - } - else if (buttons[0].isPressed && buttons[5].isPressed) { - buttons[0].isPressed = false; - buttons[5].isPressed = false; - doCmdAction(BUTTON_MULTI_05); - } - else if (buttons[1].isPressed && buttons[2].isPressed) { - buttons[1].isPressed = false; - buttons[2].isPressed = false; - doCmdAction(BUTTON_MULTI_12); - } - else if (buttons[1].isPressed && buttons[3].isPressed) { - buttons[1].isPressed = false; - buttons[3].isPressed = false; - doCmdAction(BUTTON_MULTI_13); - } - else if (buttons[1].isPressed && buttons[4].isPressed) { - buttons[1].isPressed = false; - buttons[4].isPressed = false; - doCmdAction(BUTTON_MULTI_14); - } - else if (buttons[1].isPressed && buttons[5].isPressed) { - buttons[1].isPressed = false; - buttons[5].isPressed = false; - doCmdAction(BUTTON_MULTI_15); - } - else if (buttons[2].isPressed && buttons[3].isPressed) { - buttons[2].isPressed = false; - buttons[3].isPressed = false; - doCmdAction(BUTTON_MULTI_23); - } - else if (buttons[2].isPressed && buttons[4].isPressed) { - buttons[2].isPressed = false; - buttons[4].isPressed = false; - doCmdAction(BUTTON_MULTI_24); - } - else if (buttons[2].isPressed && buttons[5].isPressed) { - buttons[2].isPressed = false; - buttons[5].isPressed = false; - doCmdAction(BUTTON_MULTI_25); - } - else if (buttons[3].isPressed && buttons[4].isPressed) { - buttons[3].isPressed = false; - buttons[4].isPressed = false; - doCmdAction(BUTTON_MULTI_34); - } - else if (buttons[3].isPressed && buttons[5].isPressed) { - buttons[3].isPressed = false; - buttons[5].isPressed = false; - doCmdAction(BUTTON_MULTI_35); - } - else if (buttons[4].isPressed && buttons[5].isPressed) { - buttons[4].isPressed = false; - buttons[5].isPressed = false; - doCmdAction(BUTTON_MULTI_45); - } - else { - for (uint8_t i=0; i < sizeof(buttons) / sizeof(buttons[0]); i++) { - if (buttons[i].isPressed) { - if (buttons[i].lastReleasedTimestamp > buttons[i].lastPressedTimestamp) { - if (buttons[i].lastReleasedTimestamp - buttons[i].lastPressedTimestamp >= intervalToLongPress) { - switch (i) // Long-press-actions - { - case 0: - doCmdAction(BUTTON_0_LONG); - buttons[i].isPressed = false; - break; - - case 1: - doCmdAction(BUTTON_1_LONG); - buttons[i].isPressed = false; - break; - - case 2: - doCmdAction(BUTTON_2_LONG); - buttons[i].isPressed = false; - break; - - case 3: - doCmdAction(BUTTON_3_LONG); - buttons[i].isPressed = false; - break; - - case 4: - doCmdAction(BUTTON_4_LONG); - buttons[i].isPressed = false; - break; - - case 5: - doCmdAction(BUTTON_5_LONG); - buttons[i].isPressed = false; - break; - } - } else { - switch (i) // Short-press-actions - { - case 0: - doCmdAction(BUTTON_0_SHORT); - buttons[i].isPressed = false; - break; - - case 1: - doCmdAction(BUTTON_1_SHORT); - buttons[i].isPressed = false; - break; - - case 2: - doCmdAction(BUTTON_2_SHORT); - buttons[i].isPressed = false; - break; - - case 3: - doCmdAction(BUTTON_3_SHORT); - buttons[i].isPressed = false; - break; - - case 4: - doCmdAction(BUTTON_4_SHORT); - buttons[i].isPressed = false; - break; - - case 5: - doCmdAction(BUTTON_5_SHORT); - buttons[i].isPressed = false; - break; - } - } - } - } - } - } -} - -/* Wrapper-functions for MQTT-publish */ -#ifdef MQTT_ENABLE -bool publishMqtt(const char *topic, const char *payload, bool retained) { - if (strcmp(topic, "") != 0) { - if (MQTTclient.connected()) { - MQTTclient.publish(topic, payload, retained); - delay(100); - return true; - } - } - return false; -} - - -bool publishMqtt(const char *topic, int32_t payload, bool retained) { - char buf[11]; - snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%d", payload); - return publishMqtt(topic, buf, retained); -} - -bool publishMqtt(const char *topic, unsigned long payload, bool retained) { - char buf[11]; - snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%lu", payload); - return publishMqtt(topic, buf, retained); -} - -bool publishMqtt(const char *topic, uint32_t payload, bool retained) { - char buf[11]; - snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%u", payload); - return publishMqtt(topic, buf, retained); -} - - -/* Cyclic posting via MQTT that ESP is still alive. Use case: when ESPuino is switched off, it will post via - MQTT it's gonna be offline now. But when unplugging ESPuino e.g. openHAB doesn't know ESPuino is offline. - One way to recognize this is to determine, when a topic has been updated for the last time. So by - telling openHAB connection is timed out after 2mins for instance, this is the right topic to check for. */ -void postHeartbeatViaMqtt(void) { - if (millis() - lastOnlineTimestamp >= stillOnlineInterval*1000) { - lastOnlineTimestamp = millis(); - if (publishMqtt((char *) FPSTR(topicState), "Online", false)) { - loggerNl(serialDebug, (char *) FPSTR(stillOnlineMqtt), LOGLEVEL_DEBUG); - } - } -} - - -/* Connects/reconnects to MQTT-Broker unless connection is not already available. - Manages MQTT-subscriptions. -*/ -bool reconnect() { - uint8_t connect = false; - uint8_t i = 0; - - if (!mqttLastRetryTimestamp || millis() - mqttLastRetryTimestamp >= mqttRetryInterval * 1000) { - mqttLastRetryTimestamp = millis(); - } else { - return false; - } - - while (!MQTTclient.connected() && i < mqttMaxRetriesPerInterval) { - i++; - snprintf(logBuf, serialLoglength, "%s %s", (char *) FPSTR(tryConnectMqttS), mqtt_server); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - - // Try to connect to MQTT-server. If username AND password are set, they'll be used - if (strlen(mqttUser) < 1 || strlen(mqttPassword) < 1) { - loggerNl(serialDebug, (char *) FPSTR(mqttWithoutPwd), LOGLEVEL_NOTICE); - if (MQTTclient.connect(DEVICE_HOSTNAME)) { - connect = true; - } - } else { - loggerNl(serialDebug, (char *) FPSTR(mqttWithPwd), LOGLEVEL_NOTICE); - if (MQTTclient.connect(DEVICE_HOSTNAME, mqttUser, mqttPassword)) { - connect = true; - } - } - if (connect) { - loggerNl(serialDebug, (char *) FPSTR(mqttOk), LOGLEVEL_NOTICE); - - // Deepsleep-subscription - MQTTclient.subscribe((char *) FPSTR(topicSleepCmnd)); - - // RFID-Tag-ID-subscription - MQTTclient.subscribe((char *) FPSTR(topicRfidCmnd)); - - // Loudness-subscription - MQTTclient.subscribe((char *) FPSTR(topicLoudnessCmnd)); - - // Sleep-Timer-subscription - MQTTclient.subscribe((char *) FPSTR(topicSleepTimerCmnd)); - - // Next/previous/stop/play-track-subscription - MQTTclient.subscribe((char *) FPSTR(topicTrackControlCmnd)); - - // Lock controls - MQTTclient.subscribe((char *) FPSTR(topicLockControlsCmnd)); - - // Current repeat-Mode - MQTTclient.subscribe((char *) FPSTR(topicRepeatModeCmnd)); - - // LED-brightness - MQTTclient.subscribe((char *) FPSTR(topicLedBrightnessCmnd)); - - // Publish some stuff - publishMqtt((char *) FPSTR(topicState), "Online", false); - publishMqtt((char *) FPSTR(topicTrackState), "---", false); - publishMqtt((char *) FPSTR(topicLoudnessState), currentVolume, false); - publishMqtt((char *) FPSTR(topicSleepTimerState), sleepTimerStartTimestamp, false); - publishMqtt((char *) FPSTR(topicLockControlsState), "OFF", false); - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), 0, false); - - char currentIPString[16]; - sprintf(currentIPString, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); - publishMqtt((char *) FPSTR(topicCurrentIPv4IP), currentIPString, false); - - return MQTTclient.connected(); - } else { - snprintf(logBuf, serialLoglength, "%s: rc=%i (%d / %d)", (char *) FPSTR(mqttConnFailed), MQTTclient.state(), i, mqttMaxRetriesPerInterval); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - } - } - return false; -} - - -// Is called if there's a new MQTT-message for us -void callback(const char *topic, const byte *payload, uint32_t length) { - char *receivedString = x_strndup((char*)payload, length); - char *mqttTopic = x_strdup(topic); - - snprintf(logBuf, serialLoglength, "%s: [Topic: %s] [Command: %s]", (char *) FPSTR(mqttMsgReceived), mqttTopic, receivedString); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - - // Go to sleep? - if (strcmp_P(topic, topicSleepCmnd) == 0) { - if ((strcmp(receivedString, "OFF") == 0) || (strcmp(receivedString, "0") == 0)) { - gotoSleep = true; - } - } - - // New track to play? Take RFID-ID as input - else if (strcmp_P(topic, topicRfidCmnd) == 0) { - char *_rfidId = x_strdup(receivedString); - xQueueSend(rfidCardQueue, &_rfidId, 0); - //free(_rfidId); - } - // Loudness to change? - else if (strcmp_P(topic, topicLoudnessCmnd) == 0) { - unsigned long vol = strtoul(receivedString, NULL, 10); - volumeToQueueSender(vol, true); - } - // Modify sleep-timer? - else if (strcmp_P(topic, topicSleepTimerCmnd) == 0) { - if (playProperties.playMode == NO_PLAYLIST) { // Don't allow sleep-modications if no playlist is active - loggerNl(serialDebug, (char *) FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_INFO); - publishMqtt((char *) FPSTR(topicSleepState), 0, false); - actionError(); - return; - } - if (strcmp(receivedString, "EOP") == 0) { - playProperties.sleepAfterPlaylist = true; - loggerNl(serialDebug, (char *) FPSTR(sleepTimerEOP), LOGLEVEL_NOTICE); - actionOk(); - return; - } else if (strcmp(receivedString, "EOT") == 0) { - playProperties.sleepAfterCurrentTrack = true; - loggerNl(serialDebug, (char *) FPSTR(sleepTimerEOT), LOGLEVEL_NOTICE); - actionOk(); - return; - } else if (strcmp(receivedString, "EO5T") == 0) { - if ((playProperties.numberOfTracks - 1) >= (playProperties.currentTrackNumber + 5)) { - playProperties.playUntilTrackNumber = playProperties.currentTrackNumber + 5; - } else { - playProperties.sleepAfterPlaylist = true; - } - loggerNl(serialDebug, (char *) FPSTR(sleepTimerEO5), LOGLEVEL_NOTICE); - actionOk(); - return; - } else if (strcmp(receivedString, "0") == 0) { - if (sleepTimerStartTimestamp) { - sleepTimerStartTimestamp = 0; - loggerNl(serialDebug, (char *) FPSTR(sleepTimerStop), LOGLEVEL_NOTICE); - actionOk(); - publishMqtt((char *) FPSTR(topicSleepState), 0, false); - return; - } else { - loggerNl(serialDebug, (char *) FPSTR(sleepTimerAlreadyStopped), LOGLEVEL_INFO); - actionError(); - return; - } - } - sleepTimer = strtoul(receivedString, NULL, 10); - snprintf(logBuf, serialLoglength, "%s: %u Minute(n)", (char *) FPSTR(sleepTimerSetTo), sleepTimer); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - actionOk(); - - sleepTimerStartTimestamp = millis(); // Activate timer - playProperties.sleepAfterPlaylist = false; - playProperties.sleepAfterCurrentTrack = false; - } - // Track-control (pause/play, stop, first, last, next, previous) - else if (strcmp_P(topic, topicTrackControlCmnd) == 0) { - uint8_t controlCommand = strtoul(receivedString, NULL, 10); - trackControlToQueueSender(controlCommand); - } - - // Check if controls should be locked - else if (strcmp_P(topic, topicLockControlsCmnd) == 0) { - if (strcmp(receivedString, "OFF") == 0) { - lockControls = false; - loggerNl(serialDebug, (char *) FPSTR(allowButtons), LOGLEVEL_NOTICE); - actionOk(); - - } else if (strcmp(receivedString, "ON") == 0) { - lockControls = true; - loggerNl(serialDebug, (char *) FPSTR(lockButtons), LOGLEVEL_NOTICE); - actionOk(); - } - } - - // Check if playmode should be adjusted - else if (strcmp_P(topic, topicRepeatModeCmnd) == 0) { - char rBuf[2]; - uint8_t repeatMode = strtoul(receivedString, NULL, 10); - Serial.printf("Repeat: %d" , repeatMode); - if (playProperties.playMode != NO_PLAYLIST) { - if (playProperties.playMode == NO_PLAYLIST) { - snprintf(rBuf, 2, "%u", getRepeatMode()); - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - loggerNl(serialDebug, (char *) FPSTR(noPlaylistNotAllowedMqtt), LOGLEVEL_ERROR); - actionError(); - } else { - switch (repeatMode) { - case NO_REPEAT: - playProperties.repeatCurrentTrack = false; - playProperties.repeatPlaylist = false; - snprintf(rBuf, 2, "%u", getRepeatMode()); - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - loggerNl(serialDebug, (char *) FPSTR(modeRepeatNone), LOGLEVEL_INFO); - actionOk(); - break; - - case TRACK: - playProperties.repeatCurrentTrack = true; - playProperties.repeatPlaylist = false; - snprintf(rBuf, 2, "%u", getRepeatMode()); - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - loggerNl(serialDebug, (char *) FPSTR(modeRepeatTrack), LOGLEVEL_INFO); - actionOk(); - break; - - case PLAYLIST: - playProperties.repeatCurrentTrack = false; - playProperties.repeatPlaylist = true; - snprintf(rBuf, 2, "%u", getRepeatMode()); - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - loggerNl(serialDebug, (char *) FPSTR(modeRepeatPlaylist), LOGLEVEL_INFO); - actionOk(); - break; - - case TRACK_N_PLAYLIST: - playProperties.repeatCurrentTrack = true; - playProperties.repeatPlaylist = true; - snprintf(rBuf, 2, "%u", getRepeatMode()); - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - loggerNl(serialDebug, (char *) FPSTR(modeRepeatTracknPlaylist), LOGLEVEL_INFO); - actionOk(); - break; - - default: - actionError(); - snprintf(rBuf, 2, "%u", getRepeatMode()); - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - break; - } - } - } - } - - // Check if LEDs should be dimmed - else if (strcmp_P(topic, topicLedBrightnessCmnd) == 0) { - ledBrightness = strtoul(receivedString, NULL, 10); - } - - // Requested something that isn't specified? - else { - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(noValidTopic), topic); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - actionError(); - } - - free(receivedString); - free(mqttTopic); -} -#endif - - -// Returns current repeat-mode (mix of repeat current track and current playlist) -uint8_t getRepeatMode(void) { - if (playProperties.repeatPlaylist && playProperties.repeatCurrentTrack) { - return TRACK_N_PLAYLIST; - } else if (playProperties.repeatPlaylist && !playProperties.repeatCurrentTrack) { - return PLAYLIST; - } else if (!playProperties.repeatPlaylist && playProperties.repeatCurrentTrack) { - return TRACK; - } else { - return NO_REPEAT; - } -} - - -// Checks if string starts with prefix -// Returns true if so -bool startsWith(const char *str, const char *pre) { - if (strlen(pre) < 1) { - return false; - } - - return !strncmp(str, pre, strlen(pre)); -} - - -// Checks if string ends with suffix -// Returns true if so -bool endsWith (const char *str, const char *suf) { - const char *a = str + strlen(str); - const char *b = suf + strlen(suf); - - while (a != str && b != suf) { - if (*--a != *--b) break; - } - - return b == suf && *a == *b; -} - - -// Release previously allocated memory -void freeMultiCharArray(char **arr, const uint32_t cnt) { - for (uint32_t i=0; i<=cnt; i++) { - /*snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(freePtr), *(arr+i)); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG);*/ - free(*(arr+i)); - } - *arr = NULL; -} - - -// Knuth-Fisher-Yates-algorithm to randomize playlist -void randomizePlaylist (char *str[], const uint32_t count) { - if (count < 1) { - return; - } - - uint32_t i, r; - char *swap = NULL; - uint32_t max = count-1; - - for (i=0; i 0) { - r = rand() % max; - } else { - r = 0; - } - swap = *(str+max); - *(str+max) = *(str+r); - *(str+r) = swap; - max--; - } -} - - -// Helper to sort playlist alphabetically -static int arrSortHelper(const void* a, const void* b) { - return strcmp(*(const char**)a, *(const char**)b); -} - -// Sort playlist alphabetically -void sortPlaylist(const char** arr, int n) { - qsort(arr, n, sizeof(const char*), arrSortHelper); -} - - -// Check if file-type is correct -bool fileValid(const char *_fileItem) { - const char ch = '/'; - char *subst; - subst = strrchr(_fileItem, ch); // Don't use files that start with . - - return (!startsWith(subst, (char *) "/.")) && - (endsWith(_fileItem, ".mp3") || endsWith(_fileItem, ".MP3") || - endsWith(_fileItem, ".aac") || endsWith(_fileItem, ".AAC") || - endsWith(_fileItem, ".m3u") || endsWith(_fileItem, ".M3U") || - endsWith(_fileItem, ".m4a") || endsWith(_fileItem, ".M4A") || - endsWith(_fileItem, ".wav") || endsWith(_fileItem, ".WAV") || - endsWith(_fileItem, ".flac") || endsWith(_fileItem, ".FLAC") || - endsWith(_fileItem, ".asx") || endsWith(_fileItem, ".ASX")); -} - - -// Adds webstream to playlist; same like returnPlaylistFromSD() but always only one entry -char ** returnPlaylistFromWebstream(const char *_webUrl) { - char *webUrl = x_strdup(_webUrl); - static char **url; - - if (url != NULL) { - --url; - freeMultiCharArray(url, strtoul(*url, NULL, 10)); - } - - url = (char **) x_malloc(sizeof(char *) * 2); - - url[0] = x_strdup("1"); // Number of files is always 1 in url-mode - url[1] = x_strdup(webUrl); - - free(webUrl); - return ++url; -} - - -/* Puts SD-file(s) or directory into a playlist - First element of array always contains the number of payload-items. */ -char ** returnPlaylistFromSD(File _fileOrDirectory) { - static char **files; - char fileNameBuf[255]; - - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(freeMemory), ESP.getFreeHeap()); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG); - - if (files != NULL) { // If **ptr already exists, de-allocate its memory - loggerNl(serialDebug, (char *) FPSTR(releaseMemoryOfOldPlaylist), LOGLEVEL_DEBUG); - --files; - freeMultiCharArray(files, strtoul(*files, NULL, 10)); - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(freeMemoryAfterFree), ESP.getFreeHeap()); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG); - } - - if (!_fileOrDirectory) { - loggerNl(serialDebug, (char *) FPSTR(dirOrFileDoesNotExist), LOGLEVEL_ERROR); - return NULL; - } - - // File-mode - if (!_fileOrDirectory.isDirectory()) { - files = (char **) x_malloc(sizeof(char *) * 2); - if (files == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR); - actionError(); - return NULL; - } - loggerNl(serialDebug, (char *) FPSTR(fileModeDetected), LOGLEVEL_INFO); - strncpy(fileNameBuf, (char *) _fileOrDirectory.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0])); - if (fileValid(fileNameBuf)) { - files = (char **) x_malloc(sizeof(char *) * 2); - files[1] = x_strdup(fileNameBuf); - } - files[0] = x_strdup("1"); // Number of files is always 1 in file-mode - - return ++files; - } - - // Directory-mode - uint16_t allocCount = 1; - uint16_t allocSize = 512; - if (psramInit()) { - allocSize = 16384; // There's enough PSRAM. So we don't have to care... - } - char *serializedPlaylist; - - serializedPlaylist = (char*) x_calloc(allocSize, sizeof(char)); - - - while (true) { - File fileItem = _fileOrDirectory.openNextFile(); - if (!fileItem) { - break; - } - if (fileItem.isDirectory()) { - continue; - } else { - strncpy(fileNameBuf, (char *) fileItem.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0])); - - // Don't support filenames that start with "." and only allow .mp3 - if (fileValid(fileNameBuf)) { - /*snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(nameOfFileFound), fileNameBuf); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO);*/ - if ((strlen(serializedPlaylist) + strlen(fileNameBuf) + 2) >= allocCount * allocSize) { - serializedPlaylist = (char*) realloc(serializedPlaylist, ++allocCount * allocSize); - loggerNl(serialDebug, (char *) FPSTR(reallocCalled), LOGLEVEL_DEBUG); - if (serializedPlaylist == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR); - actionError(); - return files; - } - } - strcat(serializedPlaylist, stringDelimiter); - strcat(serializedPlaylist, fileNameBuf); - } - } - } - - // Get number of elements out of serialized playlist - uint32_t cnt = 0; - for (uint32_t k=0; k<(strlen(serializedPlaylist)); k++) { - if (serializedPlaylist[k] == '#') { - cnt++; - } - } - - // Alloc only necessary number of playlist-pointers - files = (char **) x_malloc(sizeof(char *) * cnt + 1); - - if (files == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR); - actionError(); - free(serializedPlaylist); - return NULL; - } - - // Extract elements out of serialized playlist and copy to playlist - char *token; - token = strtok(serializedPlaylist, stringDelimiter); - uint32_t pos = 1; - while (token != NULL) { - files[pos++] = x_strdup(token); - token = strtok(NULL, stringDelimiter); - } - - free(serializedPlaylist); - - files[0] = (char *) x_malloc(sizeof(char) * 5); - - if (files[0] == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR); - actionError(); - return NULL; - } - sprintf(files[0], "%u", cnt); - snprintf(logBuf, serialLoglength, "%s: %d", (char *) FPSTR(numberOfValidFiles), cnt); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - - return ++files; // return ptr+1 (starting at 1st payload-item); ptr+0 contains number of items -} - - -/* Wraps putString for writing settings into NVS for RFID-cards. - Returns number of characters written. */ -size_t nvsRfidWriteWrapper (const char *_rfidCardId, const char *_track, const uint32_t _playPosition, const uint8_t _playMode, const uint16_t _trackLastPlayed, const uint16_t _numberOfTracks) { - #ifdef NEOPIXEL_ENABLE - pauseNeopixel = true; // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write - #endif - char prefBuf[275]; - char trackBuf[255]; - snprintf(trackBuf, sizeof(trackBuf) / sizeof(trackBuf[0]), _track); - - // If it's a directory we just want to play/save basename(path) - if (_numberOfTracks > 1) { - const char s = '/'; - char *last = strrchr(_track, s); - char *first = strchr(_track, s); - unsigned long substr = last-first+1; - if (substr <= sizeof(trackBuf) / sizeof(trackBuf[0])) { - snprintf(trackBuf, substr, _track); // save substring basename(_track) - } else { - return 0; // Filename too long! - } - } - - snprintf(prefBuf, sizeof(prefBuf) / sizeof(prefBuf[0]), "%s%s%s%u%s%d%s%u", stringDelimiter, trackBuf, stringDelimiter, _playPosition, stringDelimiter, _playMode, stringDelimiter, _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(serialDebug, logBuf, LOGLEVEL_INFO); - loggerNl(serialDebug, prefBuf, LOGLEVEL_INFO); - #ifdef NEOPIXEL_ENABLE - pauseNeopixel = false; - #endif - return prefsRfid.putString(_rfidCardId, prefBuf); -} - - -// Function to play music as task -void playAudio(void *parameter) { - static Audio audio; - uint8_t settleCount = 0; - audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); - audio.setVolume(initVolume); - audio.forceMono(playProperties.currentPlayMono); - if (playProperties.currentPlayMono) { - audio.setTone(3,0,0); - } - - - uint8_t currentVolume; - static BaseType_t trackQStatus; - static uint8_t trackCommand = 0; - bool audioReturnCode; - - for (;;) { - if (xQueueReceive(volumeQueue, ¤tVolume, 0) == pdPASS ) { - snprintf(logBuf, serialLoglength, "%s: %d", (char *) FPSTR(newLoudnessReceivedQueue), currentVolume); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - audio.setVolume(currentVolume); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLoudnessState), currentVolume, false); - #endif - } - - if (xQueueReceive(trackControlQueue, &trackCommand, 0) == pdPASS) { - snprintf(logBuf, serialLoglength, "%s: %d", (char *) FPSTR(newCntrlReceivedQueue), trackCommand); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } - - trackQStatus = xQueueReceive(trackQueue, &playProperties.playlist, 0); - if (trackQStatus == pdPASS || playProperties.trackFinished || trackCommand != 0) { - if (trackQStatus == pdPASS) { - if (playProperties.pausePlay) { - playProperties.pausePlay = !playProperties.pausePlay; - } - audio.stopSong(); - #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(serialDebug, logBuf, LOGLEVEL_NOTICE); - Serial.print(F("Free heap: ")); - Serial.println(ESP.getFreeHeap()); - - // If we're in audiobook-mode and apply a modification-card, we don't - // want to save lastPlayPosition for the mod-card but for the card that holds the playlist - if (currentRfidTagId != NULL) { - strncpy(playProperties.playRfidTag, currentRfidTagId, sizeof(playProperties.playRfidTag) / sizeof(playProperties.playRfidTag[0])); - } - - } - if (playProperties.trackFinished) { - playProperties.trackFinished = false; - if (playProperties.playMode == NO_PLAYLIST) { - playProperties.playlistFinished = true; - continue; - } - if (playProperties.saveLastPlayPosition) { // Don't save for AUDIOBOOK_LOOP because not necessary - if (playProperties.currentTrackNumber + 1 < playProperties.numberOfTracks) { - // Only save if there's another track, otherwise it will be saved at end of playlist anyway - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), 0, playProperties.playMode, playProperties.currentTrackNumber+1, playProperties.numberOfTracks); - } - } - if (playProperties.sleepAfterCurrentTrack) { // Go to sleep if "sleep after track" was requested - gotoSleep = true; - break; - } - if (!playProperties.repeatCurrentTrack) { // If endless-loop requested, track-number will not be incremented - playProperties.currentTrackNumber++; - } else { - loggerNl(serialDebug, (char *) FPSTR(repeatTrackDueToPlaymode), LOGLEVEL_INFO); - #ifdef NEOPIXEL_ENABLE - showRewind = true; - #endif - } - } - - if (playProperties.playlistFinished && trackCommand != 0) { - loggerNl(serialDebug, (char *) FPSTR(noPlaymodeChangeIfIdle), LOGLEVEL_NOTICE); - trackCommand = 0; - actionError(); - continue; - } - /* Check if track-control was called - (stop, start, next track, prev. track, last track, first track...) */ - switch (trackCommand) { - case STOP: - audio.stopSong(); - trackCommand = 0; - loggerNl(serialDebug, (char *) FPSTR(cmndStop), LOGLEVEL_INFO); - playProperties.pausePlay = true; - playProperties.playlistFinished = true; - playProperties.playMode = NO_PLAYLIST; - continue; - - case PAUSEPLAY: - audio.pauseResume(); - trackCommand = 0; - loggerNl(serialDebug, (char *) FPSTR(cmndPause), LOGLEVEL_INFO); - if (playProperties.saveLastPlayPosition && !playProperties.pausePlay) { - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(trackPausedAtPos), audio.getFilePos()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), audio.getFilePos(), playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); - } - playProperties.pausePlay = !playProperties.pausePlay; - continue; - - case NEXTTRACK: - if (playProperties.pausePlay) { - audio.pauseResume(); - playProperties.pausePlay = !playProperties.pausePlay; - } - if (playProperties.repeatCurrentTrack) { // End loop if button was pressed - playProperties.repeatCurrentTrack = !playProperties.repeatCurrentTrack; - char rBuf[2]; - snprintf(rBuf, 2, "%u", getRepeatMode()); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - #endif - } - if (playProperties.currentTrackNumber+1 < playProperties.numberOfTracks) { - playProperties.currentTrackNumber++; - if (playProperties.saveLastPlayPosition) { - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), 0, playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); - loggerNl(serialDebug, (char *) FPSTR(trackStartAudiobook), LOGLEVEL_INFO); - } - loggerNl(serialDebug, (char *) FPSTR(cmndNextTrack), LOGLEVEL_INFO); - if (!playProperties.playlistFinished) { - audio.stopSong(); - } - } else { - loggerNl(serialDebug, (char *) FPSTR(lastTrackAlreadyActive), LOGLEVEL_NOTICE); - trackCommand = 0; - actionError(); - continue; - } - trackCommand = 0; - break; - - case PREVIOUSTRACK: - if (playProperties.pausePlay) { - audio.pauseResume(); - playProperties.pausePlay = !playProperties.pausePlay; - } - if (playProperties.repeatCurrentTrack) { // End loop if button was pressed - playProperties.repeatCurrentTrack = !playProperties.repeatCurrentTrack; - char rBuf[2]; - snprintf(rBuf, 2, "%u", getRepeatMode()); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - #endif - } - if (playProperties.currentTrackNumber > 0) { - // play previous track when current track time is small, else play current track again - if(audio.getAudioCurrentTime() < 2) { - playProperties.currentTrackNumber--; - } - if (playProperties.saveLastPlayPosition) { - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), 0, playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); - loggerNl(serialDebug, (char *) FPSTR(trackStartAudiobook), LOGLEVEL_INFO); - } - - loggerNl(serialDebug, (char *) FPSTR(cmndPrevTrack), LOGLEVEL_INFO); - if (!playProperties.playlistFinished) { - audio.stopSong(); - } - } else { - if (playProperties.playMode == WEBSTREAM) { - loggerNl(serialDebug, (char *) FPSTR(trackChangeWebstream), LOGLEVEL_INFO); - actionError(); - trackCommand = 0; - continue; - } - if (playProperties.saveLastPlayPosition) { - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), 0, playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); - } - audio.stopSong(); - #ifdef NEOPIXEL_ENABLE - showRewind = true; - #endif - audioReturnCode = audio.connecttoFS(FSystem, *(playProperties.playlist + playProperties.currentTrackNumber)); - // consider track as finished, when audio lib call was not successful - if (!audioReturnCode) { - actionError(); - playProperties.trackFinished = true; - continue; - } - loggerNl(serialDebug, (char *) FPSTR(trackStart), LOGLEVEL_INFO); - trackCommand = 0; - continue; - } - trackCommand = 0; - break; - - case FIRSTTRACK: - if (playProperties.pausePlay) { - audio.pauseResume(); - playProperties.pausePlay = !playProperties.pausePlay; - } - if (playProperties.currentTrackNumber > 0) { - playProperties.currentTrackNumber = 0; - if (playProperties.saveLastPlayPosition) { - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), 0, playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); - loggerNl(serialDebug, (char *) FPSTR(trackStartAudiobook), LOGLEVEL_INFO); - } - loggerNl(serialDebug, (char *) FPSTR(cmndFirstTrack), LOGLEVEL_INFO); - if (!playProperties.playlistFinished) { - audio.stopSong(); - } - } else { - loggerNl(serialDebug, (char *) FPSTR(firstTrackAlreadyActive), LOGLEVEL_NOTICE); - actionError(); - trackCommand = 0; - continue; - } - trackCommand = 0; - break; - - case LASTTRACK: - if (playProperties.pausePlay) { - audio.pauseResume(); - playProperties.pausePlay = !playProperties.pausePlay; - } - if (playProperties.currentTrackNumber+1 < playProperties.numberOfTracks) { - playProperties.currentTrackNumber = playProperties.numberOfTracks-1; - if (playProperties.saveLastPlayPosition) { - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), 0, playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); - loggerNl(serialDebug, (char *) FPSTR(trackStartAudiobook), LOGLEVEL_INFO); - } - loggerNl(serialDebug, (char *) FPSTR(cmndLastTrack), LOGLEVEL_INFO); - if (!playProperties.playlistFinished) { - audio.stopSong(); - } - } else { - loggerNl(serialDebug, (char *) FPSTR(lastTrackAlreadyActive), LOGLEVEL_NOTICE); - actionError(); - trackCommand = 0; - continue; - } - trackCommand = 0; - break; - - case 0: - break; - - default: - trackCommand = 0; - loggerNl(serialDebug, (char *) FPSTR(cmndDoesNotExist), LOGLEVEL_NOTICE); - actionError(); - continue; - } - - if (playProperties.playUntilTrackNumber == playProperties.currentTrackNumber && playProperties.playUntilTrackNumber > 0) { - if (playProperties.saveLastPlayPosition) { - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), 0, playProperties.playMode, 0, playProperties.numberOfTracks); - } - playProperties.playlistFinished = true; - playProperties.playMode = NO_PLAYLIST; - gotoSleep = true; - continue; - } - - if (playProperties.currentTrackNumber >= playProperties.numberOfTracks) { // Check if last element of playlist is already reached - loggerNl(serialDebug, (char *) FPSTR(endOfPlaylistReached), LOGLEVEL_NOTICE); - if (!playProperties.repeatPlaylist) { - if (playProperties.saveLastPlayPosition) { - // Set back to first track - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + 0), 0, playProperties.playMode, 0, playProperties.numberOfTracks); - } - #ifdef MQTT_ENABLE - #if (LANGUAGE == 1) - publishMqtt((char *) FPSTR(topicTrackState), "", false); - #else - publishMqtt((char *) FPSTR(topicTrackState), "", false); - #endif - #endif - playProperties.playlistFinished = true; - playProperties.playMode = NO_PLAYLIST; - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - #endif - playProperties.currentTrackNumber = 0; - playProperties.numberOfTracks = 0; - if (playProperties.sleepAfterPlaylist) { - gotoSleep = true; - } - continue; - } else { // Check if sleep after current track/playlist was requested - if (playProperties.sleepAfterPlaylist || playProperties.sleepAfterCurrentTrack) { - playProperties.playlistFinished = true; - playProperties.playMode = NO_PLAYLIST; - gotoSleep = true; - continue; - } // Repeat playlist; set current track number back to 0 - loggerNl(serialDebug, (char *) FPSTR(repeatPlaylistDueToPlaymode), LOGLEVEL_NOTICE); - playProperties.currentTrackNumber = 0; - if (playProperties.saveLastPlayPosition) { - nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + 0), 0, playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); - } - } - } - - if (playProperties.playMode == WEBSTREAM) { // Webstream - audio.connecttohost(*(playProperties.playlist + playProperties.currentTrackNumber)); - playProperties.playlistFinished = false; - } else { - // Files from SD - if (!FSystem.exists(*(playProperties.playlist + playProperties.currentTrackNumber))) { // Check first if file/folder exists - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(dirOrFileDoesNotExist), *(playProperties.playlist + playProperties.currentTrackNumber)); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - playProperties.trackFinished = true; - continue; - } else { - audioReturnCode = audio.connecttoFS(FSystem, *(playProperties.playlist + playProperties.currentTrackNumber)); - // consider track as finished, when audio lib call was not successful - if(!audioReturnCode) { - actionError(); - playProperties.trackFinished = true; - continue; - } - #ifdef NEOPIXEL_ENABLE - showPlaylistProgress = true; - #endif - if (playProperties.startAtFilePos > 0) { - audio.setFilePos(playProperties.startAtFilePos); - snprintf(logBuf, serialLoglength, "%s %u", (char *) FPSTR(trackStartatPos), audio.getFilePos()); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - } - char buf[255]; - snprintf(buf, sizeof(buf)/sizeof(buf[0]), "(%d/%d) %s", (playProperties.currentTrackNumber+1), playProperties.numberOfTracks, (const char*) *(playProperties.playlist + playProperties.currentTrackNumber)); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicTrackState), buf, false); - #endif - #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(serialDebug, logBuf, LOGLEVEL_NOTICE); - playProperties.playlistFinished = false; - } - } - } - - // Handle seekmodes - if (playProperties.seekmode != SEEK_NORMAL) { - if (playProperties.seekmode == SEEK_FORWARDS) { - if (audio.setTimeOffset(jumpOffset)) { - #if (LANGUAGE == 1) - Serial.printf("%d Sekunden nach vorne gesprungen\n", jumpOffset); - #else - Serial.printf("Jumped %d seconds forwards\n", jumpOffset); - #endif - } else { - actionError(); - } - } else if (playProperties.seekmode == SEEK_BACKWARDS) { - if (audio.setTimeOffset(-(jumpOffset))) { - #if (LANGUAGE == 1) - Serial.printf("%d Sekunden zurueck gesprungen\n", jumpOffset); - #else - Serial.printf("Jumped %d seconds backwards\n", jumpOffset); - #endif - } else { - actionError(); - } - } - playProperties.seekmode = SEEK_NORMAL; - } - - // Handle if mono/stereo should be changed (e.g. if plugging headphones) - if (playProperties.newPlayMono != playProperties.currentPlayMono) { - playProperties.currentPlayMono = playProperties.newPlayMono; - audio.forceMono(playProperties.currentPlayMono); - if (playProperties.currentPlayMono) { - loggerNl(serialDebug, newPlayModeMono, LOGLEVEL_NOTICE); - audio.setTone(3,0,0); - } else { - loggerNl(serialDebug, newPlayModeStereo, LOGLEVEL_NOTICE); - audio.setTone(0,0,0); - } - } - - // Calculate relative position in file (for neopixel) for SD-card-mode - #ifdef NEOPIXEL_ENABLE - if (!playProperties.playlistFinished && playProperties.playMode != WEBSTREAM) { - double fp = (double) audio.getFilePos() / (double) audio.getFileSize(); - if (millis() % 100 == 0) { - playProperties.currentRelPos = fp * 100; - } - } else { - playProperties.currentRelPos = 0; - } - #endif - - audio.loop(); - if (playProperties.playlistFinished || playProperties.pausePlay) { - vTaskDelay(portTICK_PERIOD_MS*10); // Waste some time if playlist is not active - } else { - lastTimeActiveTimestamp = millis(); // Refresh if playlist is active so uC will not fall asleep due to reaching inactivity-time - } - - if (audio.isRunning()) { - settleCount = 0; - } - - // If error occured: remove playlist from ESPuino - if (playProperties.playMode != NO_PLAYLIST && playProperties.playMode != BUSY && !audio.isRunning() && !playProperties.pausePlay) { - if (settleCount++ == 50) { // Hack to give audio some time to settle down after playlist was generated - playProperties.playlistFinished = true; - playProperties.playMode = NO_PLAYLIST; - settleCount = 0; - } - } - - esp_task_wdt_reset(); // Don't forget to feed the dog! - } - vTaskDelete(NULL); -} - - -#if defined RFID_READER_TYPE_MFRC522_SPI || defined RFID_READER_TYPE_MFRC522_I2C -// Instructs RFID-scanner to scan for new RFID-tags using RC522 (running as function) -void rfidScanner(void) { - byte cardId[cardIdSize]; - char *cardIdString; - - if ((millis() - lastRfidCheckTimestamp) >= RFID_SCAN_INTERVAL) { - lastRfidCheckTimestamp = millis(); - // Reset the loop if no new card is present on the sensor/reader. This saves the entire process when idle. - - if (!mfrc522.PICC_IsNewCardPresent()) { - return; - } - - // Select one of the cards - if (!mfrc522.PICC_ReadCardSerial()) { - return; - } - - //mfrc522.PICC_DumpToSerial(&(mfrc522.uid)); - mfrc522.PICC_HaltA(); - mfrc522.PCD_StopCrypto1(); - - cardIdString = (char *) x_malloc(cardIdSize*3 +1); - - if (cardIdString == NULL) { - logger(serialDebug, (char *) FPSTR(unableToAllocateMem), LOGLEVEL_ERROR); - actionError(); - return; - } - - uint8_t n = 0; - logger(serialDebug, (char *) FPSTR(rfidTagDetected), LOGLEVEL_NOTICE); - for (uint8_t i=0; i= RFID_SCAN_INTERVAL) { - // Reset the loop if no new card is present on the sensor/reader. This saves the entire process when idle. - lastRfidCheckTimestamp = millis(); - // 1. check for an ISO-14443 card - nfc14443.reset(); - nfc14443.setupRF(); - uint8_t uid[10]; - if (nfc14443.isCardPresent() && nfc14443.readCardSerial(uid)) { - cardIdString = (char *) x_malloc(cardIdSize*3 +1); - - if (cardIdString == NULL) { - logger(serialDebug, (char *) FPSTR(unableToAllocateMem), LOGLEVEL_ERROR); - actionError(); - continue; - } - for (uint8_t i=0; i(leds, NUM_LEDS).setCorrection( TypicalSMD5050 ); - FastLED.setBrightness(ledBrightness); - - for (;;) { - if (pauseNeopixel) { // Workaround to prevent exceptions while NVS-writes take place - vTaskDelay(portTICK_RATE_MS*10); - continue; - } - if (gotoSleep) { // If deepsleep is planned, turn off LEDs first in order to avoid LEDs still glowing when ESP32 is in deepsleep - if (!turnedOffLeds) { - FastLED.clear(true); - turnedOffLeds = true; - } - - vTaskDelay(portTICK_RATE_MS*10); - continue; - } - if (!bootComplete) { // Rotates orange unless boot isn't complete - FastLED.clear(); - for (uint8_t led = 0; led < NUM_LEDS; led++) { - if (showEvenError) { - if (ledAddress(led) % 2 == 0) { - if (millis() <= 10000) { - leds[ledAddress(led)] = CRGB::Orange; - } else { - leds[ledAddress(led)] = CRGB::Red; - } - } - } else { - if (millis() >= 10000) { // Flashes red after 10s (will remain forever if SD cannot be mounted) - leds[ledAddress(led)] = CRGB::Red; - } else { - if (ledAddress(led) % 2 == 1) { - leds[ledAddress(led)] = CRGB::Orange; - } - } - } - } - FastLED.show(); - showEvenError = !showEvenError; - vTaskDelay(portTICK_RATE_MS*500); - esp_task_wdt_reset(); - continue; - } - - if (lastLedBrightness != ledBrightness) { - FastLED.setBrightness(ledBrightness); - lastLedBrightness = ledBrightness; - } - - // LEDs growing red as long button for sleepmode is pressed. - if (shutdownButton < (sizeof(buttons) / sizeof(buttons[0])) - 1) { // Only show animation, if CMD_SLEEPMODE was assigned to BUTTON_n_LONG + button is pressed - if (!buttons[shutdownButton].currentState) { - FastLED.clear(); - for (uint8_t led = 0; led < NUM_LEDS; led++) { - leds[ledAddress(led)] = CRGB::Red; - if (buttons[shutdownButton].currentState) { - FastLED.show(); - delay(5); - deepSleepManager(); - break; - } - FastLED.show(); - vTaskDelay(intervalToLongPress / NUM_LEDS * portTICK_RATE_MS); - } - } - } else { - shutdownButton = (sizeof(buttons) / sizeof(buttons[0])) - 1; // If CMD_SLEEPMODE was not assigned to an enabled button, dummy-button is used - if (!buttons[shutdownButton].currentState) { - buttons[shutdownButton].currentState = true; - } - } - - if (showLedError) { // If error occured (e.g. RFID-modification not accepted) - showLedError = false; - notificationShown = true; - FastLED.clear(); - - for (uint8_t led = 0; led < NUM_LEDS; led++) { - leds[ledAddress(led)] = CRGB::Red; - } - FastLED.show(); - vTaskDelay(portTICK_RATE_MS * 200); - } - - if (showLedOk) { // If action was accepted - showLedOk = false; - notificationShown = true; - FastLED.clear(); - - for (uint8_t led = 0; led < NUM_LEDS; led++) { - leds[ledAddress(led)] = CRGB::Green; - } - FastLED.show(); - vTaskDelay(portTICK_RATE_MS * 400); - } - - #ifdef MEASURE_BATTERY_VOLTAGE - if (showVoltageWarning) { // Flashes red three times if battery-voltage is low - showVoltageWarning = false; - notificationShown = true; - for (uint8_t i=0; i<3; i++) { - FastLED.clear(); - - for (uint8_t led = 0; led < NUM_LEDS; led++) { - leds[ledAddress(led)] = CRGB::Red; - } - FastLED.show(); - vTaskDelay(portTICK_RATE_MS * 200); - FastLED.clear(); - - for (uint8_t led = 0; led < NUM_LEDS; led++) { - leds[ledAddress(led)] = CRGB::Black; - } - FastLED.show(); - vTaskDelay(portTICK_RATE_MS * 200); - } - } - - if (showLedVoltage) { - showLedVoltage = false; - float currentVoltage = measureBatteryVoltage(); - float vDiffIndicatorRange = voltageIndicatorHigh-voltageIndicatorLow; - float vDiffCurrent = currentVoltage-voltageIndicatorLow; - - if (vDiffCurrent < 0) { // If voltage is too low or no battery is connected - showLedError = true; - break; - } else { - uint8_t numLedsToLight = ((float) vDiffCurrent/vDiffIndicatorRange) * NUM_LEDS; - FastLED.clear(); - for (uint8_t led = 0; led < numLedsToLight; led++) { - if (((float) numLedsToLight / NUM_LEDS) >= 0.6) { - leds[ledAddress(led)] = CRGB::Green; - } else if (((float) numLedsToLight / NUM_LEDS) <= 0.6 && ((float) numLedsToLight / NUM_LEDS) >= 0.3) { - leds[ledAddress(led)] = CRGB::Orange; - } else { - leds[ledAddress(led)] = CRGB::Red; - } - FastLED.show(); - vTaskDelay(portTICK_RATE_MS*20); - } - - for (uint8_t i=0; i<=100; i++) { - if (hlastVolume != currentVolume || showLedError || showLedOk || !buttons[shutdownButton].currentState || gotoSleep) { - break; - } - - vTaskDelay(portTICK_RATE_MS*20); - } - } - } - #endif - - if (hlastVolume != currentVolume) { // If volume has been changed - uint8_t numLedsToLight = map(currentVolume, 0, maxVolume, 0, NUM_LEDS); - hlastVolume = currentVolume; - volumeChangeShown = true; - FastLED.clear(); - - for (int led = 0; led < numLedsToLight; led++) { // (Inverse) color-gradient from green (85) back to (still) red (245) using unsigned-cast - leds[ledAddress(led)].setHue((uint8_t) (85 - ((double) 95 / NUM_LEDS) * led)); - } - FastLED.show(); - - for (uint8_t i=0; i<=50; i++) { - if (hlastVolume != currentVolume || showLedError || showLedOk || !buttons[shutdownButton].currentState || gotoSleep) { - if (hlastVolume != currentVolume) { - volumeChangeShown = false; - } - break; - } - - vTaskDelay(portTICK_RATE_MS*20); - } - } - - if (showRewind) { - showRewind = false; - for (uint8_t i=NUM_LEDS-1; i>0; i--) { - leds[ledAddress(i)] = CRGB::Black; - FastLED.show(); - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[shutdownButton].currentState || gotoSleep) { - break; - } else { - vTaskDelay(portTICK_RATE_MS*30); - } - } - } - - if (showPlaylistProgress) { - showPlaylistProgress = false; - if (playProperties.numberOfTracks > 1 && playProperties.currentTrackNumber < playProperties.numberOfTracks) { - uint8_t numLedsToLight = map(playProperties.currentTrackNumber, 0, playProperties.numberOfTracks-1, 0, NUM_LEDS); - FastLED.clear(); - for (uint8_t i=0; i < numLedsToLight; i++) { - leds[ledAddress(i)] = CRGB::Blue; - FastLED.show(); - #ifdef MEASURE_BATTERY_VOLTAGE - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || showLedVoltage || !buttons[shutdownButton].currentState || gotoSleep) { - #else - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[shutdownButton].currentState || gotoSleep) { - #endif - break; - } else { - vTaskDelay(portTICK_RATE_MS*30); - } - } - - for (uint8_t i=0; i<=100; i++) { - #ifdef MEASURE_BATTERY_VOLTAGE - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || showLedVoltage || !buttons[shutdownButton].currentState || gotoSleep) { - #else - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[shutdownButton].currentState || gotoSleep) { - #endif - break; - } else { - vTaskDelay(portTICK_RATE_MS*15); - } - } - - for (uint8_t i=numLedsToLight; i>0; i--) { - leds[ledAddress(i)-1] = CRGB::Black; - FastLED.show(); - #ifdef MEASURE_BATTERY_VOLTAGE - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || showLedVoltage || !buttons[shutdownButton].currentState || gotoSleep) { - #else - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[shutdownButton].currentState || gotoSleep) { - #endif - break; - } else { - vTaskDelay(portTICK_RATE_MS*30); - } - } - } - } - - switch (playProperties.playMode) { - case NO_PLAYLIST: // If no playlist is active (idle) - #ifdef BLUETOOTH_ENABLE - if (operationMode == OPMODE_BLUETOOTH) { - idleColor = CRGB::Blue; - } else { - #endif - if (wifiManager() == WL_CONNECTED) { - idleColor = CRGB::White; - } else { - idleColor = CRGB::Green; - } - #ifdef BLUETOOTH_ENABLE - } - #endif - if (hlastVolume == currentVolume && lastLedBrightness == ledBrightness) { - for (uint8_t i=0; i= ledSwitchInterval * 1000) || redrawProgress) { - redrawProgress = false; - lastSwitchTimestamp = millis(); - FastLED.clear(); - if (ledPosWebstream + 1 < NUM_LEDS) { - ledPosWebstream++; - } else { - ledPosWebstream = 0; - } - if (lockControls) { - leds[ledAddress(ledPosWebstream)] = CRGB::Red; - leds[(ledAddress(ledPosWebstream)+NUM_LEDS/2) % NUM_LEDS] = CRGB::Red; - } else if (!playProperties.pausePlay) { - leds[ledAddress(ledPosWebstream)].setHue(webstreamColor); - leds[(ledAddress(ledPosWebstream)+NUM_LEDS/2) % NUM_LEDS].setHue(webstreamColor++); - } else if (playProperties.pausePlay) { - leds[ledAddress(ledPosWebstream)] = CRGB::Orange; - leds[(ledAddress(ledPosWebstream)+NUM_LEDS/2) % NUM_LEDS] = CRGB::Orange; - } - } - } - FastLED.show(); - vTaskDelay(portTICK_RATE_MS * 5); - } - } - //vTaskDelay(portTICK_RATE_MS * 10); - esp_task_wdt_reset(); - } - vTaskDelete(NULL); -} -#endif - - -// Sets deep-sleep-flag if max. inactivity-time is reached -void sleepHandler(void) { - unsigned long m = millis(); - if (m >= lastTimeActiveTimestamp && (m - lastTimeActiveTimestamp >= maxInactivityTime * 1000 * 60)) { - loggerNl(serialDebug, (char *) FPSTR(goToSleepDueToIdle), LOGLEVEL_INFO); - gotoSleep = true; - } else if (sleepTimerStartTimestamp > 0) { - if (m - sleepTimerStartTimestamp >= sleepTimer * 1000 * 60) { - loggerNl(serialDebug, (char *) FPSTR(goToSleepDueToTimer), LOGLEVEL_INFO); - gotoSleep = true; - } - } -} - -#ifdef PN5180_ENABLE_LPCD -// goto low power card detection mode -void gotoLPCD() { - static PN5180 nfc(RFID_CS, RFID_BUSY, RFID_RST); - nfc.begin(); - // show PN5180 reader version - uint8_t firmwareVersion[2]; - nfc.readEEprom(FIRMWARE_VERSION, firmwareVersion, sizeof(firmwareVersion)); - Serial.print(F("Firmware version=")); - Serial.print(firmwareVersion[1]); - Serial.print("."); - Serial.println(firmwareVersion[0]); - // check firmware version: PN5180 firmware < 4.0 has several bugs preventing the LPCD mode - // you can flash latest firmware with this project: https://github.com/abidxraihan/PN5180_Updater_ESP32 - if (firmwareVersion[1] < 4) { - Serial.println(F("This PN5180 firmware does not work with LPCD! use firmware >= 4.0")); - return; - } - Serial.println(F("prepare low power card detection...")); - nfc.prepareLPCD(); - nfc.clearIRQStatus(0xffffffff); - Serial.print(F("PN5180 IRQ PIN: ")); Serial.println(digitalReadFromAll(RFID_IRQ)); - // turn on LPCD - uint16_t wakeupCounterInMs = 0x3FF; // must be in the range of 0x0 - 0xA82. max wake-up time is 2960 ms. - if (nfc.switchToLPCD(wakeupCounterInMs)) { - Serial.println(F("switch to low power card detection: success")); - // configure wakeup pin for deep-sleep wake-up, use ext1 - esp_sleep_enable_ext1_wakeup((1ULL<<(RFID_IRQ)), ESP_EXT1_WAKEUP_ANY_HIGH); - // freeze pin states in deep sleep - gpio_hold_en(gpio_num_t(RFID_CS)); // CS/NSS - gpio_hold_en(gpio_num_t(RFID_RST)); // RST - gpio_deep_sleep_hold_en(); - } else { - Serial.println(F("switchToLPCD failed")); - } -} -#endif - -// Puts uC to deep-sleep if flag is set -void deepSleepManager(void) { - if (gotoSleep) { - if (sleeping) - return; - sleeping = true; - loggerNl(serialDebug, (char *) FPSTR(goToSleepNow), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicState), "Offline", false); - publishMqtt((char *) FPSTR(topicTrackState), "---", false); - MQTTclient.disconnect(); - #endif - #ifdef NEOPIXEL_ENABLE - FastLED.clear(); - FastLED.show(); - #endif - #ifdef USE_LAST_VOLUME_AFTER_REBOOT - prefsSettings.putUInt("previousVolume", currentVolume); - #endif - // SD card goto idle mode - #ifdef SD_MMC_1BIT_MODE - SD_MMC.end(); - #else - /*SPI.end(); - spiSD.end();*/ - #endif - Serial.flush(); - // switch off power - digitalWrite(POWER, LOW); - delay(200); - #ifdef PN5180_ENABLE_LPCD - // prepare and go to low power card detection mode - gotoLPCD(); - #endif - Serial.println(F("deep-sleep, good night.......")); - esp_deep_sleep_start(); - } -} - - -// Adds new volume-entry to volume-queue -// If volume is changed via webgui or MQTT, it's necessary to re-adjust current value of rotary-encoder. -void volumeToQueueSender(const int32_t _newVolume, bool reAdjustRotary) { - uint32_t _volume; - if (_newVolume < minVolume) { - loggerNl(serialDebug, (char *) FPSTR(minLoudnessReached), LOGLEVEL_INFO); - return; - } else if (_newVolume > maxVolume) { - loggerNl(serialDebug, (char *) FPSTR(maxLoudnessReached), LOGLEVEL_INFO); - return; - } else { - _volume = _newVolume; - currentVolume = _volume; - #ifdef USEROTARY_ENABLE - if (reAdjustRotary) { - encoder.clearCount(); - encoder.setCount(currentVolume * 2); - } - #endif - } - xQueueSend(volumeQueue, &_volume, 0); -} - - -// Adds new control-command to control-queue -void trackControlToQueueSender(const uint8_t trackCommand) { - xQueueSend(trackControlQueue, &trackCommand, 0); -} - - -// Handles volume directed by rotary encoder -#ifdef USEROTARY_ENABLE - void rotaryVolumeHandler(const int32_t _minVolume, const int32_t _maxVolume) { - if (lockControls) { - encoder.clearCount(); - encoder.setCount(currentVolume*2); - return; - } - - currentEncoderValue = encoder.getCount(); - // Only if initial run or value has changed. And only after "full step" of rotary encoder - if (((lastEncoderValue != currentEncoderValue) || lastVolume == -1) && (currentEncoderValue % 2 == 0)) { - lastTimeActiveTimestamp = millis(); // Set inactive back if rotary encoder was used - if (_maxVolume * 2 < currentEncoderValue) { - encoder.clearCount(); - encoder.setCount(_maxVolume * 2); - loggerNl(serialDebug, (char *) FPSTR(maxLoudnessReached), LOGLEVEL_INFO); - currentEncoderValue = encoder.getCount(); - } else if (currentEncoderValue < _minVolume) { - encoder.clearCount(); - encoder.setCount(_minVolume); - loggerNl(serialDebug, (char *) FPSTR(minLoudnessReached), LOGLEVEL_INFO); - currentEncoderValue = encoder.getCount(); - } - lastEncoderValue = currentEncoderValue; - currentVolume = lastEncoderValue / 2; - if (currentVolume != lastVolume) { - lastVolume = currentVolume; - volumeToQueueSender(currentVolume, false); - } - } - } -#endif - - -// Receives de-serialized RFID-data (from NVS) and dispatches playlists for the given -// playmode to the track-queue. -void trackQueueDispatcher(const char *_itemToPlay, const uint32_t _lastPlayPos, const uint32_t _playMode, const uint16_t _trackLastPlayed) { - char *filename; - filename = (char *) x_malloc(sizeof(char) * 255); - - strncpy(filename, _itemToPlay, 255); - playProperties.startAtFilePos = _lastPlayPos; - playProperties.currentTrackNumber = _trackLastPlayed; - char **musicFiles; - playProperties.playMode = BUSY; // Show @Neopixel, if uC is busy with creating playlist - - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), 0, false); - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - #endif - if (_playMode != WEBSTREAM) { - musicFiles = returnPlaylistFromSD(FSystem.open(filename)); - } else { - musicFiles = returnPlaylistFromWebstream(filename); - } - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - - if (musicFiles == NULL) { - loggerNl(serialDebug, (char *) FPSTR(errorOccured), LOGLEVEL_ERROR); - actionError(); - playProperties.playMode = NO_PLAYLIST; - return; - } else if (!strcmp(*(musicFiles-1), "0")) { - loggerNl(serialDebug, (char *) FPSTR(noMp3FilesInDir), LOGLEVEL_NOTICE); - actionError(); - playProperties.playMode = NO_PLAYLIST; - free (filename); - return; - } - - playProperties.playMode = _playMode; - playProperties.numberOfTracks = strtoul(*(musicFiles-1), NULL, 10); - // Set some default-values - playProperties.repeatCurrentTrack = false; - playProperties.repeatPlaylist = false; - playProperties.sleepAfterCurrentTrack = false; - playProperties.sleepAfterPlaylist = false; - playProperties.saveLastPlayPosition = false; - playProperties.playUntilTrackNumber = 0; - - #ifdef PLAY_LAST_RFID_AFTER_REBOOT - storeLastRfidPlayed(currentRfidTagId); - #endif - - switch(playProperties.playMode) { - case SINGLE_TRACK: { - loggerNl(serialDebug, (char *) FPSTR(modeSingleTrack), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), NO_REPEAT, false); - #endif - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case SINGLE_TRACK_LOOP: { - playProperties.repeatCurrentTrack = true; - loggerNl(serialDebug, (char *) FPSTR(modeSingleTrackLoop), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), TRACK, false); - #endif - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case AUDIOBOOK: { // Tracks need to be alph. sorted! - playProperties.saveLastPlayPosition = true; - loggerNl(serialDebug, (char *) FPSTR(modeSingleAudiobook), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), NO_REPEAT, false); - #endif - sortPlaylist((const char**) musicFiles, strtoul(*(musicFiles-1), NULL, 10)); - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case AUDIOBOOK_LOOP: { // Tracks need to be alph. sorted! - playProperties.repeatPlaylist = true; - playProperties.saveLastPlayPosition = true; - loggerNl(serialDebug, (char *) FPSTR(modeSingleAudiobookLoop), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), PLAYLIST, false); - #endif - sortPlaylist((const char**) musicFiles, strtoul(*(musicFiles-1), NULL, 10)); - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case ALL_TRACKS_OF_DIR_SORTED: { - snprintf(logBuf, serialLoglength, "%s '%s' ", (char *) FPSTR(modeAllTrackAlphSorted), filename); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - sortPlaylist((const char**) musicFiles, strtoul(*(musicFiles-1), NULL, 10)); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), NO_REPEAT, false); - #endif - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case ALL_TRACKS_OF_DIR_RANDOM: { - loggerNl(serialDebug, (char *) FPSTR(modeAllTrackRandom), LOGLEVEL_NOTICE); - randomizePlaylist(musicFiles, strtoul(*(musicFiles-1), NULL, 10)); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), NO_REPEAT, false); - #endif - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case ALL_TRACKS_OF_DIR_SORTED_LOOP: { - playProperties.repeatPlaylist = true; - loggerNl(serialDebug, (char *) FPSTR(modeAllTrackAlphSortedLoop), LOGLEVEL_NOTICE); - sortPlaylist((const char**) musicFiles, strtoul(*(musicFiles-1), NULL, 10)); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), PLAYLIST, false); - #endif - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case ALL_TRACKS_OF_DIR_RANDOM_LOOP: { - playProperties.repeatPlaylist = true; - loggerNl(serialDebug, (char *) FPSTR(modeAllTrackRandomLoop), LOGLEVEL_NOTICE); - randomizePlaylist(musicFiles, strtoul(*(musicFiles-1), NULL, 10)); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), PLAYLIST, false); - #endif - xQueueSend(trackQueue, &(musicFiles), 0); - break; - } - - case WEBSTREAM: { // This is always just one "track" - loggerNl(serialDebug, (char *) FPSTR(modeWebstream), LOGLEVEL_NOTICE); - if (wifiManager() == WL_CONNECTED) { - xQueueSend(trackQueue, &(musicFiles), 0); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicPlaymodeState), playProperties.playMode, false); - publishMqtt((char *) FPSTR(topicRepeatModeState), NO_REPEAT, false); - #endif - } else { - loggerNl(serialDebug, (char *) FPSTR(webstreamNotAvailable), LOGLEVEL_ERROR); - actionError(); - playProperties.playMode = NO_PLAYLIST; - } - break; - } - - default: - loggerNl(serialDebug, (char *) FPSTR(modeDoesNotExist), LOGLEVEL_ERROR); - playProperties.playMode = NO_PLAYLIST; - actionError(); - } - free (filename); -} - - -// Modification-cards can change some settings (e.g. introducing track-looping or sleep after track/playlist). -// This function handles them. -void doRfidCardModifications(const uint32_t mod) { - doCmdAction(mod); -} - -void doCmdAction(const uint16_t mod) { - switch (mod) { - case LOCK_BUTTONS_MOD: { // Locks/unlocks all buttons - lockControls = !lockControls; - if (lockControls) { - loggerNl(serialDebug, (char *) FPSTR(modificatorAllButtonsLocked), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLockControlsState), "ON", false); - #endif - actionOk(); - } else { - loggerNl(serialDebug, (char *) FPSTR(modificatorAllButtonsUnlocked), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLockControlsState), "OFF", false); - #endif - } - break; - } - - case SLEEP_TIMER_MOD_15: { // Enables/disables sleep after 15 minutes - if (sleepTimerStartTimestamp && sleepTimer == 15) { - sleepTimerStartTimestamp = 0; - #ifdef NEOPIXEL_ENABLE - ledBrightness = initialLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepd), LOGLEVEL_NOTICE); - #endif - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - - } else { - sleepTimer = 15; - sleepTimerStartTimestamp = millis(); - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepTimer15), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), sleepTimer, false); - publishMqtt((char *) FPSTR(topicLedBrightnessState), nightLedBrightness, false); - #endif - } - - playProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active - playProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active - playProperties.playUntilTrackNumber = 0; - actionOk(); - break; - } - - case SLEEP_TIMER_MOD_30: { // Enables/disables sleep after 30 minutes - if (sleepTimerStartTimestamp && sleepTimer == 30) { - sleepTimerStartTimestamp = 0; - #ifdef NEOPIXEL_ENABLE - ledBrightness = initialLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepd), LOGLEVEL_NOTICE); - #endif - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - - } else { - sleepTimer = 30; - sleepTimerStartTimestamp = millis(); - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepTimer30), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), sleepTimer, false); - publishMqtt((char *) FPSTR(topicLedBrightnessState), nightLedBrightness, false); - #endif - } - - playProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active - playProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active - playProperties.playUntilTrackNumber = 0; - actionOk(); - break; - } - - case SLEEP_TIMER_MOD_60: { // Enables/disables sleep after 60 minutes - if (sleepTimerStartTimestamp && sleepTimer == 60) { - sleepTimerStartTimestamp = 0; - #ifdef NEOPIXEL_ENABLE - ledBrightness = initialLedBrightness; - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepd), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - - } else { - sleepTimer = 60; - sleepTimerStartTimestamp = millis(); - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepTimer60), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), sleepTimer, false); - publishMqtt((char *) FPSTR(topicLedBrightnessState), nightLedBrightness, false); - #endif - } - - playProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active - playProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active - playProperties.playUntilTrackNumber = 0; - actionOk(); - break; - } - - case SLEEP_TIMER_MOD_120: { // Enables/disables sleep after 2 hrs - if (sleepTimerStartTimestamp && sleepTimer == 120) { - sleepTimerStartTimestamp = 0; - #ifdef NEOPIXEL_ENABLE - ledBrightness = initialLedBrightness; - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepd), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - - } else { - sleepTimer = 120; - sleepTimerStartTimestamp = millis(); - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepTimer120), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), sleepTimer, false); - publishMqtt((char *) FPSTR(topicLedBrightnessState), nightLedBrightness, false); - #endif - } - - playProperties.sleepAfterCurrentTrack = false; // deactivate/overwrite if already active - playProperties.sleepAfterPlaylist = false; // deactivate/overwrite if already active - playProperties.playUntilTrackNumber = 0; - actionOk(); - break; - } - - case SLEEP_AFTER_END_OF_TRACK: { // Puts uC to sleep after end of current track - if (playProperties.playMode == NO_PLAYLIST) { - loggerNl(serialDebug, (char *) FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); - actionError(); - return; - } - if (playProperties.sleepAfterCurrentTrack) { - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepAtEOTd), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), "0", false); - #endif - #ifdef NEOPIXEL_ENABLE - ledBrightness = initialLedBrightness; - #endif - } else { - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepAtEOT), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), "EOT", false); - #endif - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); - #endif - } - playProperties.sleepAfterCurrentTrack = !playProperties.sleepAfterCurrentTrack; - playProperties.sleepAfterPlaylist = false; - sleepTimerStartTimestamp = 0; - playProperties.playUntilTrackNumber = 0; - - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - actionOk(); - break; - } - - case SLEEP_AFTER_END_OF_PLAYLIST: { // Puts uC to sleep after end of whole playlist (can take a while :->) - if (playProperties.playMode == NO_PLAYLIST) { - loggerNl(serialDebug, (char *) FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); - actionError(); - return; - } - if (playProperties.sleepAfterCurrentTrack) { - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), "0", false); - #endif - #ifdef NEOPIXEL_ENABLE - ledBrightness = initialLedBrightness; - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepAtEOPd), LOGLEVEL_NOTICE); - } else { - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - loggerNl(serialDebug, (char *) FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepAtEOP), LOGLEVEL_NOTICE); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), "EOP", false); - #endif - } - - playProperties.sleepAfterCurrentTrack = false; - playProperties.sleepAfterPlaylist = !playProperties.sleepAfterPlaylist; - sleepTimerStartTimestamp = 0; - playProperties.playUntilTrackNumber = 0; - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - actionOk(); - break; - } - - case SLEEP_AFTER_5_TRACKS:{ - if (playProperties.playMode == NO_PLAYLIST) { - loggerNl(serialDebug, (char *) FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); - actionError(); - return; - } - - playProperties.sleepAfterCurrentTrack = false; - playProperties.sleepAfterPlaylist = false; - sleepTimerStartTimestamp = 0; - - if (playProperties.playUntilTrackNumber > 0) { - playProperties.playUntilTrackNumber = 0; - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), "0", false); - #endif - #ifdef NEOPIXEL_ENABLE - ledBrightness = initialLedBrightness; - #endif - loggerNl(serialDebug, (char *) FPSTR(modificatorSleepd), LOGLEVEL_NOTICE); - } else { - if (playProperties.currentTrackNumber + 5 > playProperties.numberOfTracks) { // If currentTrack + 5 exceeds number of tracks in playlist, sleep after end of playlist - playProperties.sleepAfterPlaylist = true; - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), "EOP", false); - #endif - } else { - playProperties.playUntilTrackNumber = playProperties.currentTrackNumber + 5; - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicSleepTimerState), "EO5T", false); - #endif - } - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - #endif - loggerNl(serialDebug, (char *) FPSTR(sleepTimerEO5), LOGLEVEL_NOTICE); - } - - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - actionOk(); - break; - } - - case REPEAT_PLAYLIST: { - if (playProperties.playMode == NO_PLAYLIST) { - loggerNl(serialDebug, (char *) FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); - actionError(); - } else { - if (playProperties.repeatPlaylist) { - loggerNl(serialDebug, (char *) FPSTR(modificatorPlaylistLoopDeactive), LOGLEVEL_NOTICE); - } else { - loggerNl(serialDebug, (char *) FPSTR(modificatorPlaylistLoopActive), LOGLEVEL_NOTICE); - } - playProperties.repeatPlaylist = !playProperties.repeatPlaylist; - char rBuf[2]; - snprintf(rBuf, 2, "%u", getRepeatMode()); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - #endif - actionOk(); - } - break; - } - - case REPEAT_TRACK: { // Introduces looping for track-mode - if (playProperties.playMode == NO_PLAYLIST) { - loggerNl(serialDebug, (char *) FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_NOTICE); - actionError(); - } else { - if (playProperties.repeatCurrentTrack) { - loggerNl(serialDebug, (char *) FPSTR(modificatorTrackDeactive), LOGLEVEL_NOTICE); - } else { - loggerNl(serialDebug, (char *) FPSTR(modificatorTrackActive), LOGLEVEL_NOTICE); - } - playProperties.repeatCurrentTrack = !playProperties.repeatCurrentTrack; - char rBuf[2]; - snprintf(rBuf, 2, "%u", getRepeatMode()); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false); - #endif - actionOk(); - } - break; - } - - case DIMM_LEDS_NIGHTMODE: { - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicLedBrightnessState), ledBrightness, false); - #endif - loggerNl(serialDebug, (char *) FPSTR(ledsDimmedToNightmode), LOGLEVEL_INFO); - #ifdef NEOPIXEL_ENABLE - ledBrightness = nightLedBrightness; - #endif - actionOk(); - break; - } - - case TOGGLE_WIFI_STATUS: { - if (writeWifiStatusToNVS(!getWifiEnableStatusFromNVS())) { - actionOk(); - } else { - actionError(); - } - break; - } - #ifdef BLUETOOTH_ENABLE - case TOGGLE_BLUETOOTH_MODE: { - if (readOperationModeFromNVS() == OPMODE_NORMAL) { - actionOk(); - setOperationMode(OPMODE_BLUETOOTH); - } else if (readOperationModeFromNVS() == OPMODE_BLUETOOTH) { - actionOk(); - setOperationMode(OPMODE_NORMAL); - } else { - actionError(); - } - break; - } - #endif - #ifdef FTP_ENABLE - case ENABLE_FTP_SERVER: { - if (wifiManager() == WL_CONNECTED && !ftpEnableLastStatus && !ftpEnableCurrentStatus) { - ftpEnableLastStatus = true; - actionOk(); - } else { - loggerNl(serialDebug, (char *) FPSTR(unableToStartFtpServer), LOGLEVEL_ERROR); - actionError(); - } - break; - } - #endif - case CMD_PLAYPAUSE: { - trackControlToQueueSender(PAUSEPLAY); - break; - } - case CMD_PREVTRACK: { - trackControlToQueueSender(PREVIOUSTRACK); - break; - } - case CMD_NEXTTRACK: { - trackControlToQueueSender(NEXTTRACK); - break; - } - case CMD_FIRSTTRACK: { - trackControlToQueueSender(FIRSTTRACK); - break; - } - case CMD_LASTTRACK: { - trackControlToQueueSender(LASTTRACK); - break; - } - case CMD_VOLUMEINIT: { - volumeToQueueSender(initVolume, true); - break; - } - case CMD_VOLUMEUP: { - volumeToQueueSender(currentVolume+1, true); - break; - } - case CMD_VOLUMEDOWN: { - volumeToQueueSender(currentVolume-1, true); - break; - } - case CMD_MEASUREBATTERY: { - #ifdef MEASURE_BATTERY_VOLTAGE - float voltage = measureBatteryVoltage(); - snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(currentVoltageMsg), voltage); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - #ifdef NEOPIXEL_ENABLE - showLedVoltage = true; - #endif - #ifdef MQTT_ENABLE - char vstr[6]; - snprintf(vstr, 6, "%.2f", voltage); - publishMqtt((char *) FPSTR(topicBatteryVoltage), vstr, false); - #endif - #endif - break; - } - case CMD_SLEEPMODE: { - gotoSleep = true; - break; - } - case CMD_SEEK_FORWARDS: { - playProperties.seekmode = SEEK_FORWARDS; - break; - } - case CMD_SEEK_BACKWARDS: { - playProperties.seekmode = SEEK_BACKWARDS; - break; - } - default: { - snprintf(logBuf, serialLoglength, "%s %d !", (char *) FPSTR(modificatorDoesNotExist), mod); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - actionError(); - } - } -} - - -// Tries to lookup RFID-tag-string in NVS and extracts parameter from it if found -void rfidPreferenceLookupHandler (void) { - BaseType_t rfidStatus; - char *rfidTagId; - char _file[255]; - uint32_t _lastPlayPos = 0; - uint16_t _trackLastPlayed = 0; - uint32_t _playMode = 1; - - rfidStatus = xQueueReceive(rfidCardQueue, &rfidTagId, 0); - if (rfidStatus == pdPASS) { - lastTimeActiveTimestamp = millis(); - free(currentRfidTagId); - currentRfidTagId = x_strdup(rfidTagId); - free(rfidTagId); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(rfidTagReceived), currentRfidTagId); - sendWebsocketData(0, 10); // Push new rfidTagId to all websocket-clients - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - - String s = prefsRfid.getString(currentRfidTagId, "-1"); // Try to lookup rfidId in NVS - if (!s.compareTo("-1")) { - loggerNl(serialDebug, (char *) FPSTR(rfidTagUnknownInNvs), LOGLEVEL_ERROR); - actionError(); - return; - } - - char *token; - uint8_t i = 1; - token = strtok((char *) s.c_str(), stringDelimiter); - while (token != NULL) { // Try to extract data from string after lookup - if (i == 1) { - strncpy(_file, token, sizeof(_file) / sizeof(_file[0])); - } else if (i == 2) { - _lastPlayPos = strtoul(token, NULL, 10); - } else if (i == 3) { - _playMode = strtoul(token, NULL, 10); - }else if (i == 4) { - _trackLastPlayed = strtoul(token, NULL, 10); - } - i++; - token = strtok(NULL, stringDelimiter); - } - - if (i != 5) { - loggerNl(serialDebug, (char *) FPSTR(errorOccuredNvs), LOGLEVEL_ERROR); - actionError(); - } else { - // Only pass file to queue if strtok revealed 3 items - if (_playMode >= 100) { - doRfidCardModifications(_playMode); - } else { - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicRfidState), currentRfidTagId, false); - #endif - - #ifdef BLUETOOTH_ENABLE - // if music rfid was read, go back to normal mode - if (operationMode == OPMODE_BLUETOOTH) { - setOperationMode(OPMODE_NORMAL); - } - #endif - - trackQueueDispatcher(_file, _lastPlayPos, _playMode, _trackLastPlayed); - } - } - } -} - - -// Initialize soft access-point -void accessPointStart(const char *SSID, IPAddress ip, IPAddress netmask) { - WiFi.mode(WIFI_AP); - WiFi.softAPConfig(ip, ip, netmask); - WiFi.softAP(SSID); - delay(500); - - loggerNl(serialDebug, (char *) FPSTR(apReady), LOGLEVEL_NOTICE); - snprintf(logBuf, serialLoglength, "IP-Adresse: %d.%d.%d.%d", apIP[0],apIP[1], apIP[2], apIP[3]); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - - wServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", accesspoint_HTML); - }); - - wServer.on("/init", HTTP_POST, [] (AsyncWebServerRequest *request) { - if (request->hasParam("ssid", true) && request->hasParam("pwd", true) && request->hasParam("hostname", true)) { - Serial.println(request->getParam("ssid", true)->value()); - Serial.println(request->getParam("pwd", true)->value()); - Serial.println(request->getParam("hostname", true)->value()); - prefsSettings.putString("SSID", request->getParam("ssid", true)->value()); - prefsSettings.putString("Password", request->getParam("pwd", true)->value()); - prefsSettings.putString("Hostname", request->getParam("hostname", true)->value()); - } - request->send_P(200, "text/html", accesspoint_HTML); - }); - - wServer.on("/restart", HTTP_GET, [] (AsyncWebServerRequest *request) { - #if (LANGUAGE == 1) - request->send(200, "text/html", "ESPuino wird neu gestartet..."); - #else - request->send(200, "text/html", "ESPuino is being restarted..."); - #endif - Serial.flush(); - ESP.restart(); - }); - - wServer.on("/shutdown", HTTP_GET, [] (AsyncWebServerRequest *request) { - #if (LANGUAGE == 1) - request->send(200, "text/html", "ESPuino wird ausgeschaltet..."); - #else - request->send(200, "text/html", "ESPuino is being shutdown..."); - #endif - gotoSleep = true; - }); - - // allow cors for local debug - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); - wServer.begin(); - loggerNl(serialDebug, (char *) FPSTR(httpReady), LOGLEVEL_NOTICE); - accessPointStarted = true; -} - - -// Reads stored WiFi-status from NVS -bool getWifiEnableStatusFromNVS(void) { - uint32_t wifiStatus = prefsSettings.getUInt("enableWifi", 99); - - // if not set so far, preseed with 1 (enable) - if (wifiStatus == 99) { - prefsSettings.putUInt("enableWifi", 1); - wifiStatus = 1; - } - - return wifiStatus; -} - - -// Writes to NVS whether WiFi should be activated -bool writeWifiStatusToNVS(bool wifiStatus) { - if (!wifiStatus) { - if (prefsSettings.putUInt("enableWifi", 0)) { // disable - loggerNl(serialDebug, (char *) FPSTR(wifiDisabledAfterRestart), LOGLEVEL_NOTICE); - if (playProperties.playMode == WEBSTREAM) { - trackControlToQueueSender(STOP); - } - delay(300); - WiFi.mode(WIFI_OFF); - wifiEnabled = false; - return true; - } - - } else { - if (prefsSettings.putUInt("enableWifi", 1)) { // enable - loggerNl(serialDebug, (char *) FPSTR(wifiEnabledAfterRestart), LOGLEVEL_NOTICE); - wifiNeedsRestart = true; - wifiEnabled = true; - return true; - } - } - return true; -} - - -// Reads from NVS, if bluetooth or "normal" mode is desired -uint8_t readOperationModeFromNVS(void) { - return prefsSettings.getUChar("operationMode", OPMODE_NORMAL); -} - - -// Writes to NVS, if bluetooth or "normal" mode is desired -bool setOperationMode(uint8_t newOperationMode) { - uint8_t currentOperationMode = prefsSettings.getUChar("operationMode", OPMODE_NORMAL); - if(currentOperationMode != newOperationMode) { - if (prefsSettings.putUChar("operationMode", newOperationMode)) { - ESP.restart(); - } - } - return true; -} - - -// Creates FTP-instance only when requested -#ifdef FTP_ENABLE - void ftpManager(void) { - if (ftpEnableLastStatus && !ftpEnableCurrentStatus) { - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(freeHeapWithoutFtp), ESP.getFreeHeap()); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG); - ftpEnableCurrentStatus = true; - ftpSrv = new FtpServer(); - ftpSrv->begin(FSystem, ftpUser, ftpPassword); - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(freeHeapWithFtp), ESP.getFreeHeap()); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG); - #if (LANGUAGE == 1) - Serial.println(F("FTP-Server gestartet")); - #else - Serial.println(F("FTP-server started")); - #endif - } - } -#endif - - -// Provides management for WiFi -wl_status_t wifiManager(void) { - // If wifi whould not be activated, return instantly - if (!wifiEnabled) { - return WiFi.status(); - } - - if (!wifiCheckLastTimestamp || wifiNeedsRestart) { - // Get credentials from NVS - String strSSID = prefsSettings.getString("SSID", "-1"); - if (!strSSID.compareTo("-1")) { - loggerNl(serialDebug, (char *) FPSTR(ssidNotFoundInNvs), LOGLEVEL_ERROR); - } - String strPassword = prefsSettings.getString("Password", "-1"); - if (!strPassword.compareTo("-1")) { - loggerNl(serialDebug, (char *) FPSTR(wifiPwdNotFoundInNvs), LOGLEVEL_ERROR); - } - const char *_ssid = strSSID.c_str(); - const char *_pwd = strPassword.c_str(); - - // Get (optional) hostname-configration from NVS - String hostname = prefsSettings.getString("Hostname", "-1"); - if (hostname.compareTo("-1")) { - //WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - WiFi.setHostname(hostname.c_str()); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredHostnameFromNvs), hostname.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - loggerNl(serialDebug, (char *) FPSTR(wifiHostnameNotSet), LOGLEVEL_INFO); - } - - // Add configration of static IP (if requested) - #ifdef STATIC_IP_ENABLE - snprintf(logBuf, serialLoglength, "%s", (char *) FPSTR(tryStaticIpConfig)); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - if (!WiFi.config(local_IP, gateway, subnet, primaryDNS)) { - snprintf(logBuf, serialLoglength, "%s", (char *) FPSTR(staticIPConfigFailed)); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - } - #endif - - // Try to join local WiFi. If not successful, an access-point is opened - WiFi.begin(_ssid, _pwd); - - uint8_t tryCount=0; - while (WiFi.status() != WL_CONNECTED && tryCount <= 4) { - delay(500); - Serial.print(F(".")); - tryCount++; - wifiCheckLastTimestamp = millis(); - if (tryCount >= 4 && WiFi.status() == WL_CONNECT_FAILED) { - WiFi.begin(_ssid, _pwd); // ESP32-workaround (otherwise WiFi-connection sometimes fails) - } - } - - if (WiFi.status() == WL_CONNECTED) { - myIP = WiFi.localIP(); - #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(serialDebug, logBuf, LOGLEVEL_NOTICE); - } else { // Starts AP if WiFi-connect wasn't successful - accessPointStart((char *) FPSTR(accessPointNetworkSSID), apIP, apNetmask); - } - - #ifdef MDNS_ENABLE - // zero conf, make device available as .local - if (MDNS.begin(hostname.c_str())) { - MDNS.addService("http", "tcp", 80); - } - #endif - - wifiNeedsRestart = false; - } - - return WiFi.status(); -} - -const char mqttTab[] PROGMEM = " MQTT"; -const char ftpTab[] PROGMEM = " FTP"; - -// Used for substitution of some variables/templates of html-files. Is called by webserver's template-engine -String templateProcessor(const String& templ) { - if (templ == "FTP_USER") { - return prefsSettings.getString("ftpuser", "-1"); - } else if (templ == "FTP_PWD") { - return prefsSettings.getString("ftppassword", "-1"); - } else if (templ == "FTP_USER_LENGTH") { - return String(ftpUserLength-1); - } else if (templ == "FTP_PWD_LENGTH") { - return String(ftpPasswordLength-1); - } else if (templ == "SHOW_FTP_TAB") { // Only show FTP-tab if FTP-support was compiled - #ifdef FTP_ENABLE - return (String) FPSTR(ftpTab); - #else - return String(); - #endif - } else if (templ == "INIT_LED_BRIGHTNESS") { - return String(prefsSettings.getUChar("iLedBrightness", 0)); - } else if (templ == "NIGHT_LED_BRIGHTNESS") { - return String(prefsSettings.getUChar("nLedBrightness", 0)); - } else if (templ == "MAX_INACTIVITY") { - return String(prefsSettings.getUInt("mInactiviyT", 0)); - } else if (templ == "INIT_VOLUME") { - return String(prefsSettings.getUInt("initVolume", 0)); - } else if (templ == "CURRENT_VOLUME") { - return String(currentVolume); - } else if (templ == "MAX_VOLUME_SPEAKER") { - return String(prefsSettings.getUInt("maxVolumeSp", 0)); - } else if (templ == "MAX_VOLUME_HEADPHONE") { - return String(prefsSettings.getUInt("maxVolumeHp", 0)); - } else if (templ == "WARNING_LOW_VOLTAGE") { - return String(prefsSettings.getFloat("wLowVoltage", warningLowVoltage)); - } else if (templ == "VOLTAGE_INDICATOR_LOW") { - return String(prefsSettings.getFloat("vIndicatorLow", voltageIndicatorLow)); - } else if (templ == "VOLTAGE_INDICATOR_HIGH") { - return String(prefsSettings.getFloat("vIndicatorHigh", voltageIndicatorHigh)); - } else if (templ == "VOLTAGE_CHECK_INTERVAL") { - return String(prefsSettings.getUInt("vCheckIntv", voltageCheckInterval)); - } else if (templ == "MQTT_SERVER") { - return prefsSettings.getString("mqttServer", "-1"); - } else if (templ == "SHOW_MQTT_TAB") { // Only show MQTT-tab if MQTT-support was compiled - #ifdef MQTT_ENABLE - return (String) FPSTR(mqttTab); - #else - return String(); - #endif - } else if (templ == "MQTT_ENABLE") { - if (enableMqtt) { - return String("checked=\"checked\""); - } else { - return String(); - } - } else if (templ == "MQTT_USER") { - return prefsSettings.getString("mqttUser", "-1"); - } else if (templ == "MQTT_PWD") { - return prefsSettings.getString("mqttPassword", "-1"); - } else if (templ == "MQTT_USER_LENGTH") { - return String(mqttUserLength-1); - } else if (templ == "MQTT_PWD_LENGTH") { - return String(mqttPasswordLength-1); - } else if (templ == "MQTT_SERVER_LENGTH") { - return String(mqttServerLength-1); - } else if (templ == "MQTT_PORT") { - return String(mqttPort); - } else if (templ == "IPv4") { - myIP = WiFi.localIP(); - snprintf(logBuf, serialLoglength, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); - return String(logBuf); - } else if (templ == "RFID_TAG_ID") { - return String(currentRfidTagId); - } else if (templ == "HOSTNAME") { - return prefsSettings.getString("Hostname", "-1"); - } - - return String(); -} - - -// Takes inputs from webgui, parses JSON and saves values in NVS -// If operation was successful (NVS-write is verified) true is returned -bool processJsonRequest(char *_serialJson) { - StaticJsonDocument<1000> doc; - DeserializationError error = deserializeJson(doc, _serialJson); - JsonObject object = doc.as(); - - if (error) { - #if (LANGUAGE == 1) - Serial.print(F("deserializeJson() fehlgeschlagen: ")); - #else - Serial.print(F("deserializeJson() failed: ")); - #endif - Serial.println(error.c_str()); - return false; - } - - if (doc.containsKey("general")) { - uint8_t iVol = doc["general"]["iVol"].as(); - uint8_t mVolSpeaker = doc["general"]["mVolSpeaker"].as(); - uint8_t mVolHeadphone = doc["general"]["mVolHeadphone"].as(); - uint8_t iBright = doc["general"]["iBright"].as(); - uint8_t nBright = doc["general"]["nBright"].as(); - uint8_t iTime = doc["general"]["iTime"].as(); - float vWarning = doc["general"]["vWarning"].as(); - float vIndLow = doc["general"]["vIndLow"].as(); - float vIndHi = doc["general"]["vIndHi"].as(); - uint8_t vInt = doc["general"]["vInt"].as(); - - prefsSettings.putUInt("initVolume", iVol); - prefsSettings.putUInt("maxVolumeSp", mVolSpeaker); - prefsSettings.putUInt("maxVolumeHp", mVolHeadphone); - prefsSettings.putUChar("iLedBrightness", iBright); - prefsSettings.putUChar("nLedBrightness", nBright); - prefsSettings.putUInt("mInactiviyT", iTime); - prefsSettings.putFloat("wLowVoltage", vWarning); - prefsSettings.putFloat("vIndicatorLow", vIndLow); - prefsSettings.putFloat("vIndicatorHigh", vIndHi); - prefsSettings.putUInt("vCheckIntv", vInt); - - // Check if settings were written successfully - if (prefsSettings.getUInt("initVolume", 0) != iVol || - prefsSettings.getUInt("maxVolumeSp", 0) != mVolSpeaker || - prefsSettings.getUInt("maxVolumeHp", 0) != mVolHeadphone || - prefsSettings.getUChar("iLedBrightness", 0) != iBright || - prefsSettings.getUChar("nLedBrightness", 0) != nBright || - prefsSettings.getUInt("mInactiviyT", 0) != iTime || - prefsSettings.getFloat("wLowVoltage", 999.99) != vWarning || - prefsSettings.getFloat("vIndicatorLow", 999.99) != vIndLow || - prefsSettings.getFloat("vIndicatorHigh", 999.99) != vIndHi || - prefsSettings.getUInt("vCheckIntv", 17777) != vInt) { - return false; - } - - } else if (doc.containsKey("ftp")) { - const char *_ftpUser = doc["ftp"]["ftpUser"]; - const char *_ftpPwd = doc["ftp"]["ftpPwd"]; - - prefsSettings.putString("ftpuser", (String) _ftpUser); - prefsSettings.putString("ftppassword", (String) _ftpPwd); - - if (!(String(_ftpUser).equals(prefsSettings.getString("ftpuser", "-1")) || - String(_ftpPwd).equals(prefsSettings.getString("ftppassword", "-1")))) { - return false; - } - - } else if (doc.containsKey("mqtt")) { - uint8_t _mqttEnable = doc["mqtt"]["mqttEnable"].as(); - const char *_mqttServer = object["mqtt"]["mqttServer"]; - prefsSettings.putUChar("enableMQTT", _mqttEnable); - prefsSettings.putString("mqttServer", (String) _mqttServer); - const char *_mqttUser = doc["mqtt"]["mqttUser"]; - const char *_mqttPwd = doc["mqtt"]["mqttPwd"]; - uint16_t _mqttPort = doc["mqtt"]["mqttPort"].as(); - - prefsSettings.putUChar("enableMQTT", _mqttEnable); - prefsSettings.putString("mqttServer", (String) _mqttServer); - prefsSettings.putString("mqttServer", (String) _mqttServer); - prefsSettings.putString("mqttUser", (String) _mqttUser); - prefsSettings.putString("mqttPassword", (String) _mqttPwd); - prefsSettings.putUInt("mqttPort", _mqttPort); - - if ((prefsSettings.getUChar("enableMQTT", 99) != _mqttEnable) || - (!String(_mqttServer).equals(prefsSettings.getString("mqttServer", "-1")))) { - return false; - } - - } else if (doc.containsKey("rfidMod")) { - const char *_rfidIdModId = object["rfidMod"]["rfidIdMod"]; - uint8_t _modId = object["rfidMod"]["modId"]; - char rfidString[12]; - if(_modId<=0) { - prefsRfid.remove(_rfidIdModId); - } else { - snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s0%s0%s%u%s0", stringDelimiter, stringDelimiter, stringDelimiter, _modId, stringDelimiter); - prefsRfid.putString(_rfidIdModId, rfidString); - - String s = prefsRfid.getString(_rfidIdModId, "-1"); - if (s.compareTo(rfidString)) { - return false; - } - } - dumpNvsToSd("rfidTags", (char *) FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed - - } else if (doc.containsKey("rfidAssign")) { - const char *_rfidIdAssinId = object["rfidAssign"]["rfidIdMusic"]; - char _fileOrUrlAscii[MAX_FILEPATH_LENTGH]; - convertUtf8ToAscii(object["rfidAssign"]["fileOrUrl"], _fileOrUrlAscii); - uint8_t _playMode = object["rfidAssign"]["playMode"]; - char rfidString[275]; - snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s%s%s0%s%u%s0", stringDelimiter, _fileOrUrlAscii, stringDelimiter, stringDelimiter, _playMode, stringDelimiter); - prefsRfid.putString(_rfidIdAssinId, rfidString); - Serial.println(_rfidIdAssinId); - Serial.println(rfidString); - - String s = prefsRfid.getString(_rfidIdAssinId, "-1"); - if (s.compareTo(rfidString)) { - return false; - } - dumpNvsToSd("rfidTags", (char *) FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed - - } else if (doc.containsKey("wifiConfig")) { - const char *_ssid = object["wifiConfig"]["ssid"]; - const char *_pwd = object["wifiConfig"]["pwd"]; - const char *_hostname = object["wifiConfig"]["hostname"]; - - prefsSettings.putString("SSID", _ssid); - prefsSettings.putString("Password", _pwd); - prefsSettings.putString("Hostname", (String) _hostname); - - String sSsid = prefsSettings.getString("SSID", "-1"); - String sPwd = prefsSettings.getString("Password", "-1"); - String sHostname = prefsSettings.getString("Hostname", "-1"); - - if (sSsid.compareTo(_ssid) || sPwd.compareTo(_pwd)) { - return false; - } - } else if (doc.containsKey("ping")) { - sendWebsocketData(0, 20); - return false; - } else if (doc.containsKey("controls")) { - if (object["controls"].containsKey("set_volume")) { - uint8_t new_vol = doc["controls"]["set_volume"].as(); - volumeToQueueSender(new_vol, true); - } - if (object["controls"].containsKey("action")) { - uint8_t cmd = doc["controls"]["action"].as(); - doCmdAction(cmd); - } - } - - return true; -} - -// Sends JSON-answers via websocket -void sendWebsocketData(uint32_t client, uint8_t code) { - char *jBuf; - jBuf = (char *) x_calloc(255, sizeof(char)); - - const size_t CAPACITY = JSON_OBJECT_SIZE(1) + 20; - StaticJsonDocument doc; - JsonObject object = doc.to(); - - if (code == 1) { - object["status"] = "ok"; - } else if (code == 2) { - object["status"] = "error"; - } else if (code == 10) { - object["rfidId"] = currentRfidTagId; - } else if (code == 20) { - object["pong"] = "pong"; - } - - serializeJson(doc, jBuf, 255); - - if (client == 0) { - ws.printfAll(jBuf); - } else { - ws.printf(client, jBuf); - } - free(jBuf); -} - - -// Processes websocket-requests -void onWebsocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len){ - if (type == WS_EVT_CONNECT){ - //client connected - Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); - //client->printf("Hello Client %u :)", client->id()); - client->ping(); - } else if (type == WS_EVT_DISCONNECT) { - //client disconnected - Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), uint8_t(client->id())); - } else if (type == WS_EVT_ERROR) { - //error was received from the other end - Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); - } else if (type == WS_EVT_PONG) { - //pong message was received (in response to a ping request maybe) - Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); - } else if (type == WS_EVT_DATA) { - //data packet - AwsFrameInfo * info = (AwsFrameInfo*)arg; - if (info->final && info->index == 0 && info->len == len) { - //the whole message is in a single frame and we got all of it's data - Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); - - if (processJsonRequest((char*)data)) { - sendWebsocketData(client->id(), 1); - } - - if (info->opcode == WS_TEXT) { - data[len] = 0; - Serial.printf("%s\n", (char*)data); - } else { - for (size_t i=0; i < info->len; i++){ - Serial.printf("%02x ", data[i]); - } - Serial.printf("\n"); - } - } - } -} - -// Set maxVolume depending on headphone-adjustment is enabled and headphone is/is not connected -void setupVolume(void) { - - #ifndef HEADPHONE_ADJUST_ENABLE - maxVolume = maxVolumeSpeaker; - return; - #else - if (digitalReadFromAll(HP_DETECT)) { - maxVolume = maxVolumeSpeaker; // 1 if headphone is not connected - #ifdef PLAY_MONO_SPEAKER - playProperties.newPlayMono = true; - #else - playProperties.newPlayMono = false; - #endif - } else { - maxVolume = maxVolumeHeadphone; // 0 if headphone is connected (put to GND) - playProperties.newPlayMono = false; // always stereo for headphones! - } - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(maxVolumeSet), maxVolume); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - return; - #endif -} - - -#ifdef HEADPHONE_ADJUST_ENABLE - void headphoneVolumeManager(void) { - bool currentHeadPhoneDetectionState = digitalReadFromAll(HP_DETECT); - - if (headphoneLastDetectionState != currentHeadPhoneDetectionState && (millis() - headphoneLastDetectionTimestamp >= headphoneLastDetectionDebounce)) { - if (currentHeadPhoneDetectionState) { - maxVolume = maxVolumeSpeaker; - #ifdef PLAY_MONO_SPEAKER - playProperties.newPlayMono = true; - #else - playProperties.newPlayMono = false; - #endif - } else { - maxVolume = maxVolumeHeadphone; - playProperties.newPlayMono = false; // Always stereo for headphones - if (currentVolume > maxVolume) { - volumeToQueueSender(maxVolume, true); // Lower volume for headphone if headphone's maxvolume is exceeded by volume set in speaker-mode - } - } - headphoneLastDetectionState = currentHeadPhoneDetectionState; - headphoneLastDetectionTimestamp = millis(); - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(maxVolumeSet), maxVolume); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } - } +#ifdef PLAY_LAST_RFID_AFTER_REBOOT +bool recoverLastRfid = true; #endif +//////////// -bool isNumber(const char *str) { - byte i = 0; - - while (*(str + i) != '\0') { - if (!isdigit(*(str + i++))) { - return false; - } - } - - if (i>0) { - return true; - } else { - return false; - } - -} - -void webserverStart(void) { - if (wifiManager() == WL_CONNECTED && !webserverStarted) { - // attach AsyncWebSocket for Mgmt-Interface - ws.onEvent(onWebsocketEvent); - wServer.addHandler(&ws); - - // attach AsyncEventSource - wServer.addHandler(&events); - - // Default - wServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send_P(200, "text/html", management_HTML, templateProcessor); - }); - - // NVS-backup-upload - wServer.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", backupRecoveryWebsite); - }, handleUpload); - - // ESP-restart - wServer.on("/restart", HTTP_GET, [] (AsyncWebServerRequest *request) { - request->send_P(200, "text/html", restartWebsite); - Serial.flush(); - ESP.restart(); - }); - - // ESP-shutdown - wServer.on("/shutdown", HTTP_GET, [] (AsyncWebServerRequest *request) { - request->send_P(200, "text/html", shutdownWebsite); - gotoSleep = true; - }); - - // Fileexplorer (realtime) - wServer.on("/explorer", HTTP_GET, explorerHandleListRequest); - - wServer.on("/explorer", HTTP_POST, [](AsyncWebServerRequest *request) { - request->send(200); - }, explorerHandleFileUpload); - - wServer.on("/explorer", HTTP_DELETE, explorerHandleDeleteRequest); - - wServer.on("/explorer", HTTP_PUT, explorerHandleCreateRequest); - - wServer.on("/explorer", HTTP_PATCH, explorerHandleRenameRequest); - - wServer.on("/exploreraudio", HTTP_POST, explorerHandleAudioRequest); - - wServer.onNotFound(notFound); - - // allow cors for local debug - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); - wServer.begin(); - webserverStarted = true; - } -} - -// Dumps all RFID-entries from NVS into a file on SD-card - bool dumpNvsToSd(char *_namespace, char *_destFile) { - #ifdef NEOPIXEL_ENABLE - pauseNeopixel = true; // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write - #endif - esp_partition_iterator_t pi; // Iterator for find - const esp_partition_t* nvs; // Pointer to partition struct - esp_err_t result = ESP_OK; - const char* partname = "nvs"; - uint8_t pagenr = 0; // Page number in NVS - uint8_t i; // Index in Entry 0..125 - uint8_t bm; // Bitmap for an entry - uint32_t offset = 0; // Offset in nvs partition - uint8_t namespace_ID; // Namespace ID found - - pi = esp_partition_find ( ESP_PARTITION_TYPE_DATA, // Get partition iterator for - ESP_PARTITION_SUBTYPE_ANY, // this partition - partname ) ; - if (pi) { - nvs = esp_partition_get(pi); // Get partition struct - esp_partition_iterator_release(pi); // Release the iterator - dbgprint ( "Partition %s found, %d bytes", - partname, - nvs->size ) ; - } else { - snprintf(logBuf, serialLoglength, "Partition %s not found!", partname); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - return NULL; - } - namespace_ID = FindNsID (nvs, _namespace) ; // Find ID of our namespace in NVS - File backupFile = FSystem.open(_destFile, FILE_WRITE); - if (!backupFile) { - return false; - } - while (offset < nvs->size) { - result = esp_partition_read (nvs, offset, // Read 1 page in nvs partition - &buf, - sizeof(nvs_page)); - if (result != ESP_OK) { - snprintf(logBuf, serialLoglength, "Error reading NVS!"); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - return false; - } - - i = 0; - - while (i < 126) { - bm = (buf.Bitmap[i/4] >> ((i % 4) * 2 )) & 0x03; // Get bitmap for this entry - if (bm == 2) { - if ((namespace_ID == 0xFF) || // Show all if ID = 0xFF - (buf.Entry[i].Ns == namespace_ID)) { // otherwise just my namespace - if (isNumber(buf.Entry[i].Key)) { - String s = prefsRfid.getString((const char *)buf.Entry[i].Key); - backupFile.printf("%s%s%s%s\n", stringOuterDelimiter, buf.Entry[i].Key, stringOuterDelimiter, s.c_str()); - } - } - i += buf.Entry[i].Span; // Next entry - } else { - i++; - } - } - offset += sizeof(nvs_page); // Prepare to read next page in nvs - pagenr++; - } - - backupFile.close(); - #ifdef NEOPIXEL_ENABLE - pauseNeopixel = false; - #endif - - return true; - } - -// Conversion routine -void convertAsciiToUtf8(String asciiString, char *utf8String) { +#if (HAL == 2) +#include "AC101.h" +static TwoWire i2cBusOne = TwoWire(0); +static AC101 ac(&i2cBusOne); +#endif - int k=0; +// I2C +#if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(PORT_EXPANDER_ENABLE) +TwoWire i2cBusTwo = TwoWire(1); +#endif - for (int i=0; ihasParam("path")) { - AsyncWebParameter *param = request->getParam("path"); - utf8FilePath = param->value() + "/" + filename; - - } else { - utf8FilePath = "/" + filename; - } - - convertUtf8ToAscii(utf8FilePath, filePath); - - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(writingFile), utf8FilePath.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - - // Create Ringbuffer for upload - if(explorerFileUploadRingBuffer == NULL) { - explorerFileUploadRingBuffer = xRingbufferCreate(4096, RINGBUF_TYPE_BYTEBUF); - } - - // Create Queue for receiving a signal from the store task as synchronisation - if(explorerFileUploadStatusQueue == NULL) { - explorerFileUploadStatusQueue = xQueueCreate(1, sizeof(uint8_t)); - } - - // Create Task for handling the storage of the data - xTaskCreate( - explorerHandleFileStorageTask, /* Function to implement the task */ - "fileStorageTask", /* Name of the task */ - 4000, /* Stack size in words */ - filePath, /* Task input parameter */ - 2 | portPRIVILEGE_BIT, /* Priority of the task */ - &fileStorageTaskHandle /* Task handle. */ - ); - - } - - if (len) { - // stream the incoming chunk to the ringbuffer - xRingbufferSend(explorerFileUploadRingBuffer, data, len, portTICK_PERIOD_MS * 1000); - } - - if (final) { - // notify storage task that last data was stored on the ring buffer - xTaskNotify(fileStorageTaskHandle, 1u, eNoAction); - // watit until the storage task is sending the signal to finish - uint8_t signal; - xQueueReceive(explorerFileUploadStatusQueue, &signal, portMAX_DELAY); - - // delete task - vTaskDelete(fileStorageTaskHandle); - } -} - -void explorerHandleFileStorageTask(void *parameter) { - - File uploadFile; - size_t item_size; - uint8_t *item; - uint8_t value = 0; - - BaseType_t uploadFileNotification; - uint32_t uploadFileNotificationValue; - - uploadFile = FSystem.open((char *)parameter, "w"); - - for(;;) { - esp_task_wdt_reset(); - - item = (uint8_t *)xRingbufferReceive(explorerFileUploadRingBuffer, &item_size, portTICK_PERIOD_MS * 100); - if (item != NULL) { - uploadFile.write(item, item_size); - vRingbufferReturnItem(explorerFileUploadRingBuffer, (void *)item); - } else { - // No data in the buffer, check if all data arrived for the file - uploadFileNotification = xTaskNotifyWait(0,0,&uploadFileNotificationValue,0); - if(uploadFileNotification == pdPASS) { - uploadFile.close(); - // done exit loop to terminate - break; - } - vTaskDelay(portTICK_PERIOD_MS * 100); - } - } - // send signal to upload function to terminate - xQueueSend(explorerFileUploadStatusQueue, &value, 0); - vTaskDelete(NULL); -} - -// Sends a list of the content of a directory as JSON file -// requires a GET parameter path for the directory -void explorerHandleListRequest(AsyncWebServerRequest *request) { - DynamicJsonDocument jsonBuffer(16384); - //StaticJsonDocument<4096> jsonBuffer; - String serializedJsonString; - AsyncWebParameter *param; - char filePath[MAX_FILEPATH_LENTGH]; - JsonArray obj = jsonBuffer.createNestedArray(); - File root; - if (request->hasParam("path")){ - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), filePath); - root = FSystem.open(filePath); - } else { - root = FSystem.open("/"); - } - - if (!root) { - snprintf(logBuf, serialLoglength, (char *) FPSTR(failedToOpenDirectory)); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG); - return; - } - - if (!root.isDirectory()) { - snprintf(logBuf, serialLoglength, (char *) FPSTR(notADirectory)); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG); - return; - } - - File file = root.openNextFile(); - - while(file) { - // ignore hidden folders, e.g. MacOS spotlight files - if (!startsWith(file.name(), (char *) "/.")) { - JsonObject entry = obj.createNestedObject(); - convertAsciiToUtf8(file.name(), filePath); - std::string path = filePath; - std::string fileName = path.substr(path.find_last_of("/") + 1); - - entry["name"] = fileName; - entry["dir"].set(file.isDirectory()); - } - file = root.openNextFile(); - - esp_task_wdt_reset(); - - } - - serializeJson(obj, serializedJsonString); - request->send(200, "application/json; charset=utf-8", serializedJsonString); -} - -bool explorerDeleteDirectory(File dir) { - - File file = dir.openNextFile(); - while(file) { - - if(file.isDirectory()) { - explorerDeleteDirectory(file); - } else { - FSystem.remove(file.name()); - } - - file = dir.openNextFile(); - - esp_task_wdt_reset(); - } - - return FSystem.rmdir(dir.name()); - -} - -// Handles delete request of a file or directory -// requires a GET parameter path to the file or directory -void explorerHandleDeleteRequest(AsyncWebServerRequest *request) { - File file; - AsyncWebParameter *param; - char filePath[MAX_FILEPATH_LENTGH]; - if(request->hasParam("path")){ - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), filePath); - if(FSystem.exists(filePath)) { - file = FSystem.open(filePath); - if(file.isDirectory()) { - if(explorerDeleteDirectory(file)) { - snprintf(logBuf, serialLoglength, "DELETE: %s deleted", param->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "DELETE: Cannot delete %s", param->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - } - } else { - if(FSystem.remove(filePath)) { - snprintf(logBuf, serialLoglength, "DELETE: %s deleted", param->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "DELETE: Cannot delete %s", param->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - } - } - } else { - snprintf(logBuf, serialLoglength, "DELETE: Path %s does not exist", param->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - } - } else { - loggerNl(serialDebug, "DELETE: No path variable set", LOGLEVEL_ERROR); - } - request->send(200); - esp_task_wdt_reset(); -} - -// Handles create request of a directory -// requires a GET parameter path to the new directory -void explorerHandleCreateRequest(AsyncWebServerRequest *request) { - AsyncWebParameter *param; - char filePath[MAX_FILEPATH_LENTGH]; - if(request->hasParam("path")){ - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), filePath); - if(FSystem.mkdir(filePath)) { - snprintf(logBuf, serialLoglength, "CREATE: %s created", param->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "CREATE: Cannot create %s", param->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); + if (System_GetOperationMode() == OPMODE_BLUETOOTH) + { // Don't recover if BT-mode is desired + recoverLastRfid = false; + return; } - } else { - loggerNl(serialDebug, "CREATE: No path variable set", LOGLEVEL_ERROR); - } - request->send(200); -} - -// Handles rename request of a file or directory -// requires a GET parameter srcpath to the old file or directory name -// requires a GET parameter dstpath to the new file or directory name -void explorerHandleRenameRequest(AsyncWebServerRequest *request) { - AsyncWebParameter *srcPath; - AsyncWebParameter *dstPath; - char srcFullFilePath[MAX_FILEPATH_LENTGH]; - char dstFullFilePath[MAX_FILEPATH_LENTGH]; - if(request->hasParam("srcpath") && request->hasParam("dstpath")) { - srcPath = request->getParam("srcpath"); - dstPath = request->getParam("dstpath"); - convertUtf8ToAscii(srcPath->value(), srcFullFilePath); - convertUtf8ToAscii(dstPath->value(), dstFullFilePath); - if(FSystem.exists(srcFullFilePath)) { - if(FSystem.rename(srcFullFilePath, dstFullFilePath)) { - snprintf(logBuf, serialLoglength, "RENAME: %s renamed to %s", srcPath->value().c_str(), dstPath->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "RENAME: Cannot rename %s", srcPath->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); - } - } else { - snprintf(logBuf, serialLoglength, "RENAME: Path %s does not exist", srcPath->value().c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_ERROR); + recoverLastRfid = false; + String lastRfidPlayed = gPrefsSettings.getString("lastRfid", "-1"); + if (!lastRfidPlayed.compareTo("-1")) + { + Log_Println((char *)FPSTR(unableToRestoreLastRfidFromNVS), LOGLEVEL_INFO); } - } else { - loggerNl(serialDebug, "RENAME: No path variable set", LOGLEVEL_ERROR); - } - - request->send(200); -} - -// Handles audio play requests -// requires a GET parameter path to the audio file or directory -// requires a GET parameter playmode -void explorerHandleAudioRequest(AsyncWebServerRequest *request) { - AsyncWebParameter *param; - String playModeString; - uint32_t playMode; - char filePath[MAX_FILEPATH_LENTGH]; - if(request->hasParam("path") && request->hasParam("playmode")) { - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), filePath); - param = request->getParam("playmode"); - playModeString = param->value(); - - playMode = atoi(playModeString.c_str()); - trackQueueDispatcher(filePath,0,playMode,0); - } else { - loggerNl(serialDebug, "AUDIO: No path variable set", LOGLEVEL_ERROR); - } - - request->send(200); -} - - -// Handles uploaded backup-file and writes valid entries into NVS -void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - #ifdef NEOPIXEL_ENABLE - pauseNeopixel = true; // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write - #endif - char ebuf[290]; - uint16_t j=0; - char *token; - uint8_t count=0; - nvs_t nvsEntry[1]; - - for (size_t i=0; i %s", (char *) FPSTR(writeEntryToNvs), nvsEntry[0].nvsKey, nvsEntry[0].nvsEntry); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - prefsRfid.putString(nvsEntry[0].nvsKey, nvsEntry[0].nvsEntry); - } + else + { + char *lastRfid = x_strdup(lastRfidPlayed.c_str()); + xQueueSend(gRfidCardQueue, &lastRfid, 0); + snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *)FPSTR(restoredLastRfidFromNVS), lastRfidPlayed.c_str()); + Log_Println(Log_Buffer, LOGLEVEL_INFO); } } - #ifdef NEOPIXEL_ENABLE - pauseNeopixel = false; - #endif } +#endif // Print the wake-up reason why ESP32 is awake now -void printWakeUpReason() { +void printWakeUpReason() +{ esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason = esp_sleep_get_wakeup_cause(); - switch(wakeup_reason) { - case ESP_SLEEP_WAKEUP_EXT0 : Serial.println(F("Wakeup caused by push button")); break; - case ESP_SLEEP_WAKEUP_EXT1 : Serial.println(F("Wakeup caused by low power card detection")); break; - case ESP_SLEEP_WAKEUP_TIMER : Serial.println(F("Wakeup caused by timer")); break; - case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println(F("Wakeup caused by touchpad")); break; - case ESP_SLEEP_WAKEUP_ULP : Serial.println(F("Wakeup caused by ULP program")); break; - default : Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break; - } -} - -#ifdef PN5180_ENABLE_LPCD - // wake up from LPCD, check card is present. This works only for ISO-14443 compatible cards - void checkCardIsPresentLPCD() { - static PN5180ISO14443 nfc14443(RFID_CS, RFID_BUSY, RFID_RST); - nfc14443.begin(); - nfc14443.reset(); - nfc14443.setupRF(); - if (!nfc14443.isCardPresent()) { - nfc14443.clearIRQStatus(0xffffffff); - Serial.print(F("Logic level at PN5180' IRQ-PIN: ")); Serial.println(digitalReadFromAll(RFID_IRQ)); - // turn on LPCD - uint16_t wakeupCounterInMs = 0x3FF; // needs to be in the range of 0x0 - 0xA82. max wake-up time is 2960 ms. - if (nfc14443.switchToLPCD(wakeupCounterInMs)) { - loggerNl(serialDebug, (char *) FPSTR(lowPowerCardSuccess), LOGLEVEL_INFO); - // configure wakeup pin for deep-sleep wake-up, use ext1 - esp_sleep_enable_ext1_wakeup((1ULL<<(RFID_IRQ)), ESP_EXT1_WAKEUP_ANY_HIGH); - // freeze pin states in deep sleep - gpio_hold_en(gpio_num_t(RFID_CS)); // CS/NSS - gpio_hold_en(gpio_num_t(RFID_RST)); // RST - gpio_deep_sleep_hold_en(); - loggerNl(serialDebug, (char *) FPSTR(wakeUpRfidNoIso14443), LOGLEVEL_ERROR); - esp_deep_sleep_start(); - } else { - Serial.println(F("switchToLPCD failed")); - } - } - } -#endif - -#ifdef IR_CONTROL_ENABLE - void handleIrReceiver() { - static uint8_t lastVolume = 0; - - if (IrReceiver.decode()) { - - // Print a short summary of received data - IrReceiver.printIRResultShort(&Serial); - Serial.println(); - IrReceiver.resume(); // Enable receiving of the next value - bool rcActionOk = false; - if (millis() - lastRcCmdTimestamp >= IR_DEBOUNCE) { - rcActionOk = true; // not used for volume up/down - lastRcCmdTimestamp = millis(); - } - - switch (IrReceiver.decodedIRData.command) { - case RC_PLAY: { - if (rcActionOk) { - doCmdAction(CMD_PLAYPAUSE); - Serial.println(F("RC: Play")); - } - break; - } - case RC_PAUSE: { - if (rcActionOk) { - doCmdAction(CMD_PLAYPAUSE); - Serial.println(F("RC: Pause")); - } - break; - } - case RC_NEXT: { - if (rcActionOk) { - doCmdAction(CMD_NEXTTRACK); - Serial.println(F("RC: Next")); - } - break; - } - case RC_PREVIOUS: { - if (rcActionOk) { - doCmdAction(CMD_PREVTRACK); - Serial.println(F("RC: Previous")); - } - break; - } - case RC_FIRST: { - if (rcActionOk) { - doCmdAction(CMD_FIRSTTRACK); - Serial.println(F("RC: First")); - } - break; - } - case RC_LAST: { - if (rcActionOk) { - doCmdAction(CMD_LASTTRACK); - Serial.println(F("RC: Last")); - } - break; - } - case RC_MUTE: { - if (rcActionOk) { - if (currentVolume > 0) { - lastVolume = currentVolume; - currentVolume = 0; - } else { - currentVolume = lastVolume; // Remember last volume if mute is pressed again - } - xQueueSend(volumeQueue, ¤tVolume, 0); - Serial.println(F("RC: Mute")); - } - break; - } - case RC_BLUETOOTH: { - if (rcActionOk) { - doCmdAction(TOGGLE_BLUETOOTH_MODE); - Serial.println(F("RC: Bluetooth")); - } - break; - } - case RC_FTP: { - if (rcActionOk) { - doCmdAction(ENABLE_FTP_SERVER); - Serial.println(F("RC: FTP")); - } - break; - } - case RC_SHUTDOWN: { - if (rcActionOk) { - gotoSleep = true; - Serial.println(F("RC: Shutdown")); - } - break; - } - case RC_VOL_DOWN: { - doCmdAction(CMD_VOLUMEDOWN); - Serial.println(F("RC: Volume down")); - break; - } - case RC_VOL_UP: { - doCmdAction(CMD_VOLUMEUP); - Serial.println(F("RC: Volume up")); - break; - } - default: { - if (rcActionOk) { - Serial.println(F("RC: unknown")); - } - } - } - } - } + switch (wakeup_reason) + { + case ESP_SLEEP_WAKEUP_EXT0: + Serial.println(F("Wakeup caused by push button")); + break; + case ESP_SLEEP_WAKEUP_EXT1: + Serial.println(F("Wakeup caused by low power card detection")); + break; + case ESP_SLEEP_WAKEUP_TIMER: + Serial.println(F("Wakeup caused by timer")); + break; + case ESP_SLEEP_WAKEUP_TOUCHPAD: + Serial.println(F("Wakeup caused by touchpad")); + break; + case ESP_SLEEP_WAKEUP_ULP: + Serial.println(F("Wakeup caused by ULP program")); + break; + default: + Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); + break; + } +} + +void setup() +{ + Log_Init(); + Rfid_WakeupCheck(); + System_Init(); + + memset(&gPlayProperties, 0, sizeof(gPlayProperties)); + gPlayProperties.playlistFinished = true; + +#ifdef PLAY_MONO_SPEAKER + gPlayProperties.newPlayMono = true; + gPlayProperties.currentPlayMono = true; #endif -void setup() { - Serial.begin(115200); - logBuf = (char*) x_calloc(serialLoglength, sizeof(char)); // Buffer for all log-messages - #if (WAKEUP_BUTTON <= 39) - esp_sleep_enable_ext0_wakeup((gpio_num_t) WAKEUP_BUTTON, 0); - #endif - - #ifdef NEOPIXEL_ENABLE // Try to find button that is used for shutdown via longpress-action (only necessary for Neopixel) - #if defined(BUTTON_0_ENABLE) || defined(EXPANDER_0_ENABLE) - #if (BUTTON_0_LONG == CMD_SLEEPMODE) - shutdownButton = 0; - #endif - #endif - #if defined(BUTTON_1_ENABLE) || defined(EXPANDER_1_ENABLE) - #if (BUTTON_1_LONG == CMD_SLEEPMODE) - shutdownButton = 1; - #endif - #endif - #if defined(BUTTON_2_ENABLE) || defined(EXPANDER_2_ENABLE) - #if (BUTTON_2_LONG == CMD_SLEEPMODE) - shutdownButton = 2; - #endif - #endif - #if defined(BUTTON_3_ENABLE) || defined(EXPANDER_3_ENABLE) - #if (BUTTON_3_LONG == CMD_SLEEPMODE) - shutdownButton = 3; - #endif - #endif - #if defined(BUTTON_4_ENABLE) || defined(EXPANDER_4_ENABLE) - #if (BUTTON_4_LONG == CMD_SLEEPMODE) - shutdownButton = 4; - #endif - #endif - #if defined(BUTTON_5_ENABLE) || defined(EXPANDER_5_ENABLE) - #if (BUTTON_5_LONG == CMD_SLEEPMODE) - shutdownButton = 5; - #endif - #endif - #endif - - #ifdef PN5180_ENABLE_LPCD - // disable pin hold from deep sleep (LPCD) - gpio_deep_sleep_hold_dis(); - gpio_hold_dis(gpio_num_t(RFID_CS)); // NSS - gpio_hold_dis(gpio_num_t(RFID_RST)); // RST - pinMode(RFID_IRQ, INPUT); - // check wakeup reason is a card detection - esp_sleep_wakeup_cause_t wakeup_reason; - wakeup_reason = esp_sleep_get_wakeup_cause(); - if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT1) { - checkCardIsPresentLPCD(); - } - #endif - srand(esp_random()); - pinMode(POWER, OUTPUT); - digitalWrite(POWER, HIGH); - prefsRfid.begin((char *) FPSTR(prefsRfidNamespace)); - prefsSettings.begin((char *) FPSTR(prefsSettingsNamespace)); - - playProperties.playMode = NO_PLAYLIST; - playProperties.playlist = NULL; - playProperties.repeatCurrentTrack = false; - playProperties.repeatPlaylist = false; - playProperties.currentTrackNumber = 0; - playProperties.numberOfTracks = 0; - playProperties.startAtFilePos = 0; - playProperties.currentRelPos = 0; - playProperties.sleepAfterCurrentTrack = false; - playProperties.sleepAfterPlaylist = false; - playProperties.saveLastPlayPosition = false; - playProperties.pausePlay = false; - playProperties.trackFinished = NULL; - playProperties.playlistFinished = true; - playProperties.seekmode = SEEK_NORMAL; - #ifdef PLAY_MONO_SPEAKER - playProperties.newPlayMono = true; - playProperties.currentPlayMono = true; - #else - playProperties.newPlayMono = false; - playProperties.currentPlayMono = false; - #endif - // Examples for serialized RFID-actions that are stored in NVS // #### // Please note: There's no need to do this manually (unless you want to) - /*prefsRfid.putString("215123125075", "#/mp3/Kinderlieder#0#6#0"); - prefsRfid.putString("169239075184", "#http://radio.koennmer.net/evosonic.mp3#0#8#0"); - prefsRfid.putString("244105171042", "#0#0#111#0"); // modification-card (repeat track) - prefsRfid.putString("228064156042", "#0#0#110#0"); // modification-card (repeat playlist) - prefsRfid.putString("212130160042", "#/mp3/Hoerspiele/Yakari/Sammlung2#0#3#0");*/ + /*gPrefsRfid.putString("215123125075", "#/mp3/Kinderlieder#0#6#0"); + gPrefsRfid.putString("169239075184", "#http://radio.koennmer.net/evosonic.mp3#0#8#0"); + gPrefsRfid.putString("244105171042", "#0#0#111#0"); // modification-card (repeat track) + gPrefsRfid.putString("228064156042", "#0#0#110#0"); // modification-card (repeat playlist) + gPrefsRfid.putString("212130160042", "#/mp3/Hoerspiele/Yakari/Sammlung2#0#3#0");*/ - #ifdef NEOPIXEL_ENABLE - xTaskCreatePinnedToCore( - showLed, /* Function to implement the task */ - "LED", /* Name of the task */ - 2000, /* Stack size in words */ - NULL, /* Task input parameter */ - 1 | portPRIVILEGE_BIT, /* Priority of the task */ - &LED, /* Task handle. */ - 0 /* Core where the task should run */ - ); - #endif + Led_Init(); - #if (HAL == 2) - i2cBusOne.begin(IIC_DATA, IIC_CLK, 40000); +#if (HAL == 2) + i2cBusOne.begin(IIC_DATA, IIC_CLK, 40000); - while (not ac.begin()) { - Serial.println(F("AC101 Failed!")); - delay(1000); - } - Serial.println(F("AC101 via I2C - OK!")); + while (not ac.begin()) + { + Serial.println(F("AC101 Failed!")); + delay(1000); + } + Serial.println(F("AC101 via I2C - OK!")); - pinMode(22, OUTPUT); - digitalWrite(22, HIGH); + pinMode(22, OUTPUT); + digitalWrite(22, HIGH); - pinMode(GPIO_PA_EN, OUTPUT); - digitalWrite(GPIO_PA_EN, HIGH); - Serial.println(F("Built-in amplifier enabled\n")); - #endif + pinMode(GPIO_PA_EN, OUTPUT); + digitalWrite(GPIO_PA_EN, HIGH); + Serial.println(F("Built-in amplifier enabled\n")); +#endif - #ifdef RFID_READER_TYPE_MFRC522_SPI - SPI.begin(RFID_SCK, RFID_MISO, RFID_MOSI, RFID_CS); - SPI.setFrequency(1000000); - #endif + SdCard_Init(); - #ifndef SINGLE_SPI_ENABLE - #ifdef SD_MMC_1BIT_MODE - pinMode(2, INPUT_PULLUP); - while (!SD_MMC.begin("/sdcard", true)) { - #else - pinMode(SPISD_CS, OUTPUT); - digitalWrite(SPISD_CS, HIGH); - spiSD.begin(SPISD_SCK, SPISD_MISO, SPISD_MOSI, SPISD_CS); - spiSD.setFrequency(1000000); - while (!SD.begin(SPISD_CS, spiSD)) { - #endif - #else - #ifdef SD_MMC_1BIT_MODE - pinMode(2, INPUT_PULLUP); - while (!SD_MMC.begin("/sdcard", true)) { - #else - while (!SD.begin(SPISD_CS)) { - #endif - #endif - loggerNl(serialDebug, (char *) FPSTR(unableToMountSd), LOGLEVEL_ERROR); - delay(500); - #ifdef SHUTDOWN_IF_SD_BOOT_FAILS - if (millis() >= deepsleepTimeAfterBootFails*1000) { - loggerNl(serialDebug, (char *) FPSTR(sdBootFailedDeepsleep), LOGLEVEL_ERROR); - esp_deep_sleep_start(); - } - #endif - } +// Init 2nd i2c-bus if RC522 is used with i2c or if port-expander is enabled +#if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(PORT_EXPANDER_ENABLE) + i2cBusTwo.begin(ext_IIC_DATA, ext_IIC_CLK, 40000); + delay(50); + Log_Println((char *)FPSTR(rfidScannerReady), LOGLEVEL_DEBUG); +#endif - // Init 2nd i2c-bus if RC522 is used with i2c or if port-expander is enabled - #if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(PORT_EXPANDER_ENABLE) - i2cBusTwo.begin(ext_IIC_DATA, ext_IIC_CLK, 40000); - delay(50); - loggerNl(serialDebug, (char *) FPSTR(rfidScannerReady), LOGLEVEL_DEBUG); - #endif + // welcome message + Serial.println(F("")); + Serial.println(F(" _____ ____ ____ _ ")); + Serial.println(F(" | ____| / ___| | _ \\ _ _ (_) _ __ ___ ")); + Serial.println(F(" | _| \\__ \\ | |_) | | | | | | | | '_ \\ / _ \\")); + Serial.println(F(" | |___ ___) | | __/ | |_| | | | | | | | | (_) |")); + Serial.println(F(" |_____| |____/ |_| \\__,_| |_| |_| |_| \\___/ ")); + Serial.println(F(" Rfid-controlled musicplayer\n")); + Serial.println(F(" Rev 20210402-2\n")); - // Init RC522 Card-Reader - #if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(RFID_READER_TYPE_MFRC522_SPI) - mfrc522.PCD_Init(); - mfrc522.PCD_SetAntennaGain(rfidGain); - delay(50); - loggerNl(serialDebug, (char *) FPSTR(rfidScannerReady), LOGLEVEL_DEBUG); - #endif + // print wake-up reason + printWakeUpReason(); - // welcome message - Serial.println(F("")); - Serial.println(F(" _____ ____ ____ _ ")); - Serial.println(F(" | ____| / ___| | _ \\ _ _ (_) _ __ ___ ")); - Serial.println(F(" | _| \\__ \\ | |_) | | | | | | | | '_ \\ / _ \\")); - Serial.println(F(" | |___ ___) | | __/ | |_| | | | | | | | | (_) |")); - Serial.println(F(" |_____| |____/ |_| \\__,_| |_| |_| |_| \\___/ ")); - Serial.println(F(" Rfid-controlled musicplayer\n")); - Serial.println(F(" Rev 20210402-2\n")); - // print wake-up reason - printWakeUpReason(); - #ifdef PN5180_ENABLE_LPCD - // disable pin hold from deep sleep - gpio_deep_sleep_hold_dis(); - gpio_hold_dis(gpio_num_t(RFID_CS)); // NSS - gpio_hold_dis(gpio_num_t(RFID_RST)); // RST - #endif // show SD card type - #ifdef SD_MMC_1BIT_MODE - loggerNl(serialDebug, (char *) FPSTR(sdMountedMmc1BitMode), LOGLEVEL_NOTICE); - uint8_t cardType = SD_MMC.cardType(); - #else - loggerNl(serialDebug, (char *) FPSTR(sdMountedSpiMode), LOGLEVEL_NOTICE); - uint8_t cardType = SD.cardType(); - #endif + sdcard_type_t cardType = SdCard_GetType(); Serial.print(F("SD card type: ")); - if (cardType == CARD_MMC) { + if (cardType == CARD_MMC) + { Serial.println(F("MMC")); - } else if(cardType == CARD_SD){ - Serial.println(F("SDSC")); - } else if(cardType == CARD_SDHC){ - Serial.println(F("SDHC")); - } else { - Serial.println(F("UNKNOWN")); - } - - #ifdef HEADPHONE_ADJUST_ENABLE - pinMode(HP_DETECT, INPUT); - headphoneLastDetectionState = digitalReadFromAll(HP_DETECT); - #endif - - // Create queues - volumeQueue = xQueueCreate(1, sizeof(int)); - if (volumeQueue == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToCreateVolQ), LOGLEVEL_ERROR); - } - - rfidCardQueue = xQueueCreate(1, (cardIdSize + 1) * sizeof(char)); - if (rfidCardQueue == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToCreateRfidQ), LOGLEVEL_ERROR); - } - - trackControlQueue = xQueueCreate(1, sizeof(uint8_t)); - if (trackControlQueue == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToCreateMgmtQ), LOGLEVEL_ERROR); - } - - char **playlistArray; - trackQueue = xQueueCreate(1, sizeof(playlistArray)); - if (trackQueue == NULL) { - loggerNl(serialDebug, (char *) FPSTR(unableToCreatePlayQ), LOGLEVEL_ERROR); - } - - // Get some stuff from NVS... - // Get initial LED-brightness from NVS - uint8_t nvsILedBrightness = prefsSettings.getUChar("iLedBrightness", 0); - if (nvsILedBrightness) { - initialLedBrightness = nvsILedBrightness; - ledBrightness = nvsILedBrightness; - snprintf(logBuf, serialLoglength, "%s: %d", (char *) FPSTR(initialBrightnessfromNvs), nvsILedBrightness); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putUChar("iLedBrightness", initialLedBrightness); - loggerNl(serialDebug, (char *) FPSTR(wroteInitialBrightnessToNvs), LOGLEVEL_ERROR); } - - // Get night LED-brightness from NVS - uint8_t nvsNLedBrightness = prefsSettings.getUChar("nLedBrightness", 0); - if (nvsNLedBrightness) { - nightLedBrightness = nvsNLedBrightness; - snprintf(logBuf, serialLoglength, "%s: %d", (char *) FPSTR(restoredInitialBrightnessForNmFromNvs), nvsNLedBrightness); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putUChar("nLedBrightness", nightLedBrightness); - loggerNl(serialDebug, (char *) FPSTR(wroteNmBrightnessToNvs), LOGLEVEL_ERROR); - } - - // Get FTP-user from NVS - String nvsFtpUser = prefsSettings.getString("ftpuser", "-1"); - if (!nvsFtpUser.compareTo("-1")) { - prefsSettings.putString("ftpuser", (String) ftpUser); - loggerNl(serialDebug, (char *) FPSTR(wroteFtpUserToNvs), LOGLEVEL_ERROR); - } else { - strncpy(ftpUser, nvsFtpUser.c_str(), ftpUserLength); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredFtpUserFromNvs), nvsFtpUser.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } - - // Get FTP-password from NVS - String nvsFtpPassword = prefsSettings.getString("ftppassword", "-1"); - if (!nvsFtpPassword.compareTo("-1")) { - prefsSettings.putString("ftppassword", (String) ftpPassword); - loggerNl(serialDebug, (char *) FPSTR(wroteFtpPwdToNvs), LOGLEVEL_ERROR); - } else { - strncpy(ftpPassword, nvsFtpPassword.c_str(), ftpPasswordLength); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredFtpPwdFromNvs), nvsFtpPassword.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } - - // Get maximum inactivity-time from NVS - uint32_t nvsMInactivityTime = prefsSettings.getUInt("mInactiviyT", 0); - if (nvsMInactivityTime) { - maxInactivityTime = nvsMInactivityTime; - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMaxInactivityFromNvs), nvsMInactivityTime); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putUInt("mInactiviyT", maxInactivityTime); - loggerNl(serialDebug, (char *) FPSTR(wroteMaxInactivityToNvs), LOGLEVEL_ERROR); - } - - #ifndef USE_LAST_VOLUME_AFTER_REBOOT - // Get initial volume from NVS - uint32_t nvsInitialVolume = prefsSettings.getUInt("initVolume", 0); - #else - // Get volume used at last shutdown - uint32_t nvsInitialVolume = prefsSettings.getUInt("previousVolume", 999); - if (nvsInitialVolume == 999) { - prefsSettings.putUInt("previousVolume", initVolume); - nvsInitialVolume = initVolume; - } else { - loggerNl(serialDebug, (char *) FPSTR(rememberLastVolume), LOGLEVEL_ERROR); - } - #endif - if (nvsInitialVolume) { - initVolume = nvsInitialVolume; - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredInitialLoudnessFromNvs), nvsInitialVolume); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putUInt("initVolume", initVolume); - loggerNl(serialDebug, (char *) FPSTR(wroteInitialLoudnessToNvs), LOGLEVEL_ERROR); - } - - // Get maximum volume for speaker from NVS - uint32_t nvsMaxVolumeSpeaker = prefsSettings.getUInt("maxVolumeSp", 0); - if (nvsMaxVolumeSpeaker) { - maxVolumeSpeaker = nvsMaxVolumeSpeaker; - maxVolume = maxVolumeSpeaker; - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMaxLoudnessForSpeakerFromNvs), nvsMaxVolumeSpeaker); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putUInt("maxVolumeSp", nvsMaxVolumeSpeaker); - loggerNl(serialDebug, (char *) FPSTR(wroteMaxLoudnessForSpeakerToNvs), LOGLEVEL_ERROR); - } - - #ifdef HEADPHONE_ADJUST_ENABLE - // Get maximum volume for headphone from NVS - uint32_t nvsMaxVolumeHeadphone = prefsSettings.getUInt("maxVolumeHp", 0); - if (nvsMaxVolumeHeadphone) { - maxVolumeHeadphone = nvsMaxVolumeHeadphone; - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMaxLoudnessForHeadphoneFromNvs), nvsMaxVolumeHeadphone); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putUInt("maxVolumeHp", nvsMaxVolumeHeadphone); - loggerNl(serialDebug, (char *) FPSTR(wroteMaxLoudnessForHeadphoneToNvs), LOGLEVEL_ERROR); - } - #endif - - // Adjust volume depending on headphone is connected and volume-adjustment is enabled - setupVolume(); - - // Get MQTT-enable from NVS - uint8_t nvsEnableMqtt = prefsSettings.getUChar("enableMQTT", 99); - switch (nvsEnableMqtt) { - case 99: - prefsSettings.putUChar("enableMQTT", enableMqtt); - loggerNl(serialDebug, (char *) FPSTR(wroteMqttFlagToNvs), LOGLEVEL_ERROR); - break; - case 1: - //prefsSettings.putUChar("enableMQTT", enableMqtt); - enableMqtt = nvsEnableMqtt; - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMqttActiveFromNvs), nvsEnableMqtt); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - break; - case 0: - enableMqtt = nvsEnableMqtt; - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMqttDeactiveFromNvs), nvsEnableMqtt); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - break; - } - - // Get MQTT-server from NVS - String nvsMqttServer = prefsSettings.getString("mqttServer", "-1"); - if (!nvsMqttServer.compareTo("-1")) { - prefsSettings.putString("mqttServer", (String) mqtt_server); - loggerNl(serialDebug, (char*) FPSTR(wroteMqttServerToNvs), LOGLEVEL_ERROR); - } else { - strncpy(mqtt_server, nvsMqttServer.c_str(), mqttServerLength); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttServerFromNvs), nvsMqttServer.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } - - // Get MQTT-user from NVS - String nvsMqttUser = prefsSettings.getString("mqttUser", "-1"); - if (!nvsMqttUser.compareTo("-1")) { - prefsSettings.putString("mqttUser", (String) mqttUser); - loggerNl(serialDebug, (char *) FPSTR(wroteMqttUserToNvs), LOGLEVEL_ERROR); - } else { - strncpy(mqttUser, nvsMqttUser.c_str(), mqttUserLength); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttUserFromNvs), nvsMqttUser.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); + else if (cardType == CARD_SD) + { + Serial.println(F("SDSC")); } - - // Get MQTT-password from NVS - String nvsMqttPassword = prefsSettings.getString("mqttPassword", "-1"); - if (!nvsMqttPassword.compareTo("-1")) { - prefsSettings.putString("mqttPassword", (String) mqttPassword); - loggerNl(serialDebug, (char *) FPSTR(wroteMqttPwdToNvs), LOGLEVEL_ERROR); - } else { - strncpy(mqttPassword, nvsMqttPassword.c_str(), mqttPasswordLength); - snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttPwdFromNvs), nvsMqttPassword.c_str()); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); + else if (cardType == CARD_SDHC) + { + Serial.println(F("SDHC")); } - - // Get MQTT-password from NVS - uint32_t nvsMqttPort = prefsSettings.getUInt("mqttPort", 99999); - if (nvsMqttPort == 99999) { - prefsSettings.putUInt("mqttPort", mqttPort); - } else { - mqttPort = nvsMqttPort; - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMqttPortFromNvs), mqttPort); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); + else + { + Serial.println(F("UNKNOWN")); } - #ifdef MEASURE_BATTERY_VOLTAGE - // Get voltages from NVS for Neopixel - float vLowIndicator = prefsSettings.getFloat("vIndicatorLow", 999.99); - if (vLowIndicator <= 999) { - voltageIndicatorLow = vLowIndicator; - snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(voltageIndicatorLowFromNVS), vLowIndicator); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { // preseed if not set - prefsSettings.putFloat("vIndicatorLow", voltageIndicatorLow); - } - - float vHighIndicator = prefsSettings.getFloat("vIndicatorHigh", 999.99); - if (vHighIndicator <= 999) { - voltageIndicatorHigh = vHighIndicator; - snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(voltageIndicatorHighFromNVS), vHighIndicator); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putFloat("vIndicatorHigh", voltageIndicatorHigh); - } - - float vLowWarning = prefsSettings.getFloat("wLowVoltage", 999.99); - if (vLowWarning <= 999) { - warningLowVoltage = vLowWarning; - snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(warningLowVoltageFromNVS), vLowWarning); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putFloat("wLowVoltage", warningLowVoltage); - } - - uint32_t vInterval = prefsSettings.getUInt("vCheckIntv", 17777); - if (vInterval != 17777) { - voltageCheckInterval = vInterval; - snprintf(logBuf, serialLoglength, "%s: %u Minuten", (char *) FPSTR(voltageCheckIntervalFromNVS), vInterval); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - } else { - prefsSettings.putUInt("vCheckIntv", voltageCheckInterval); - } - #endif - - // Create 1000Hz-HW-Timer (currently only used for buttons) - timerSemaphore = xSemaphoreCreateBinary(); - timer = timerBegin(0, 240, true); // Prescaler: CPU-clock in MHz - timerAttachInterrupt(timer, &onTimer, true); - timerAlarmWrite(timer, 10000, true); // 100 Hz - timerAlarmEnable(timer); - - #ifdef RFID_READER_TYPE_PN5180 - // Create task for rfid - xTaskCreatePinnedToCore( - rfidScanner, /* Function to implement the task */ - "rfidhandling", /* Name of the task */ - 1500, /* Stack size in words */ - NULL, /* Task input parameter */ - 1, /* Priority of the task */ - &rfid, /* Task handle. */ - 0 /* Core where the task should run */ - ); - #endif - - // Activate internal pullups for all enabled buttons - #ifdef BUTTON_0_ENABLE - pinMode(NEXT_BUTTON, INPUT_PULLUP); - #endif - #ifdef BUTTON_1_ENABLE - pinMode(PREVIOUS_BUTTON, INPUT_PULLUP); - #endif - #ifdef BUTTON_2_ENABLE - pinMode(PAUSEPLAY_BUTTON, INPUT_PULLUP); - #endif - #ifdef BUTTON_3_ENABLE - pinMode(DREHENCODER_BUTTON, INPUT_PULLUP); - #endif - #ifdef BUTTON_4_ENABLE - pinMode(BUTTON_4, INPUT_PULLUP); - #endif - #ifdef BUTTON_5_ENABLE - pinMode(BUTTON_5, INPUT_PULLUP); - #endif - - unsigned long currentTimestamp = millis(); + Queues_Init(); + Ftp_Init(); + AudioPlayer_Init(); + Mqtt_Init(); + Battery_Init(); + Button_Init(); + Rfid_Init(); + RotaryEncoder_Init(); + Wlan_Init(); + Bluetooth_Init(); - // Init rotary encoder - #ifdef USEROTARY_ENABLE - encoder.attachHalfQuad(DREHENCODER_CLK, DREHENCODER_DT); - encoder.clearCount(); - encoder.setCount(initVolume*2); // Ganzes Raster ist immer +2, daher initiale Lautstärke mit 2 multiplizieren - #endif - - // Only enable MQTT if requested - #ifdef MQTT_ENABLE - if (enableMqtt) { - MQTTclient.setServer(mqtt_server, mqttPort); - MQTTclient.setCallback(callback); - } - #endif - - operationMode = readOperationModeFromNVS(); - wifiEnabled = getWifiEnableStatusFromNVS(); - - #ifdef BLUETOOTH_ENABLE - if (operationMode == OPMODE_BLUETOOTH) { - a2dp_sink = new BluetoothA2DPSink(); - i2s_pin_config_t pin_config = { - .bck_io_num = I2S_BCLK, - .ws_io_num = I2S_LRC, - .data_out_num = I2S_DOUT, - .data_in_num = I2S_PIN_NO_CHANGE - }; - a2dp_sink->set_pin_config(pin_config); - a2dp_sink->start((char *) FPSTR(nameBluetoothDevice)); - } else { - esp_bt_mem_release(ESP_BT_MODE_BTDM); - #endif - xTaskCreatePinnedToCore( - playAudio, /* Function to implement the task */ - "mp3play", /* Name of the task */ - 11000, /* Stack size in words */ - NULL, /* Task input parameter */ - 2 | portPRIVILEGE_BIT, /* Priority of the task */ - &mp3Play, /* Task handle. */ - 1 /* Core where the task should run */ - ); - wifiManager(); - #ifdef BLUETOOTH_ENABLE + if (OPMODE_NORMAL == System_GetOperationMode()) + { + Wlan_Cyclic(); } - #endif - #ifdef IR_CONTROL_ENABLE - IrReceiver.begin(IRLED_PIN); - #endif + IrReceiver_Init(); - lastTimeActiveTimestamp = millis(); // initial set after boot + System_UpdateActivityTimer(); // initial set after boot - bootComplete = true; + Led_Indicate(LedIndicatorType::BootComplete); - snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(freeHeapAfterSetup), ESP.getFreeHeap()); - loggerNl(serialDebug, logBuf, LOGLEVEL_DEBUG); + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *)FPSTR(freeHeapAfterSetup), ESP.getFreeHeap()); + Log_Println(Log_Buffer, LOGLEVEL_DEBUG); Serial.printf("PSRAM: %u bytes\n", ESP.getPsramSize()); } -#ifdef BLUETOOTH_ENABLE -void bluetoothHandler(void) { - esp_a2d_audio_state_t state = a2dp_sink->get_audio_state(); - // Reset Sleep Timer when audio is playing - if(state == ESP_A2D_AUDIO_STATE_STARTED) { - lastTimeActiveTimestamp = millis(); - } -} -#endif - -void loop() { - #ifndef RFID_READER_TYPE_PN5180 - rfidScanner(); // PN5180 runs as task; RC522 as function - #endif +void loop() +{ + Rfid_Cyclic(); - #ifdef BLUETOOTH_ENABLE - if(operationMode == OPMODE_BLUETOOTH) { - bluetoothHandler(); - } else { - #endif - webserverStart(); - #ifdef FTP_ENABLE - ftpManager(); - #endif - #ifdef USEROTARY_ENABLE - rotaryVolumeHandler(minVolume, maxVolume); - #endif - if (wifiManager() == WL_CONNECTED) { - #ifdef MQTT_ENABLE - if (enableMqtt) { - reconnect(); - MQTTclient.loop(); - postHeartbeatViaMqtt(); - } - #endif - #ifdef FTP_ENABLE - if (ftpEnableLastStatus && ftpEnableCurrentStatus) { - ftpSrv->handleFTP(); - } - #endif - } - #ifdef FTP_ENABLE - if (ftpEnableLastStatus && ftpEnableCurrentStatus) { - if (ftpSrv->isConnected()) { - lastTimeActiveTimestamp = millis(); // Re-adjust timer while client is connected to avoid ESP falling asleep - } - } - #endif - ws.cleanupClients(); - #ifdef BLUETOOTH_ENABLE + if (OPMODE_BLUETOOTH == System_GetOperationMode()) + { + Bluetooth_Cyclic(); + } + else + { + Wlan_Cyclic(); + Web_Cyclic(); + Ftp_Cyclic(); + RotaryEncoder_Cyclic(); + Mqtt_Cyclic(); } - #endif - - #ifdef HEADPHONE_ADJUST_ENABLE - headphoneVolumeManager(); - #endif - #ifdef MEASURE_BATTERY_VOLTAGE - batteryVoltageTester(); - #endif - buttonHandler(); - sleepHandler(); - deepSleepManager(); - rfidPreferenceLookupHandler(); + AudioPlayer_Cyclic(); + Battery_Cyclic(); + Port_Cyclic(); + Button_Cyclic(); + System_Cyclic(); + Rfid_PreferenceLookupHandler(); - #ifdef PLAY_LAST_RFID_AFTER_REBOOT - recoverLastRfidPlayed(); - #endif - #ifdef IR_CONTROL_ENABLE - handleIrReceiver(); - #endif -} +#ifdef PLAY_LAST_RFID_AFTER_REBOOT + recoverLastRfidPlayed(); +#endif + IrReceiver_Cyclic(); -// Some mp3-lib-stuff (slightly changed from default) -void audio_info(const char *info) { - snprintf(logBuf, serialLoglength, "info : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); -} -void audio_id3data(const char *info) { //id3 metadata - snprintf(logBuf, serialLoglength, "id3data : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); -} -void audio_eof_mp3(const char *info) { //end of file - snprintf(logBuf, serialLoglength, "eof_mp3 : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); - playProperties.trackFinished = true; -} -void audio_showstation(const char *info) { - snprintf(logBuf, serialLoglength, "station : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_NOTICE); - char buf[255]; - snprintf(buf, sizeof(buf)/sizeof(buf[0]), "Webradio: %s", info); - #ifdef MQTT_ENABLE - publishMqtt((char *) FPSTR(topicTrackState), buf, false); - #endif -} -void audio_showstreamtitle(const char *info) { - snprintf(logBuf, serialLoglength, "streamtitle : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); -} -void audio_bitrate(const char *info) { - snprintf(logBuf, serialLoglength, "bitrate : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); -} -void audio_commercial(const char *info) { //duration in sec - snprintf(logBuf, serialLoglength, "commercial : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); -} -void audio_icyurl(const char *info) { //homepage - snprintf(logBuf, serialLoglength, "icyurl : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); + vTaskDelay(5u); } -void audio_lasthost(const char *info) { //stream URL played - snprintf(logBuf, serialLoglength, "lasthost : %s", info); - loggerNl(serialDebug, logBuf, LOGLEVEL_INFO); -} \ No newline at end of file diff --git a/src/settings-custom.h b/src/settings-custom.h index 5b367b6..b88dd40 100644 --- a/src/settings-custom.h +++ b/src/settings-custom.h @@ -84,14 +84,14 @@ // (optional) Monitoring of battery-voltage via ADC #ifdef MEASURE_BATTERY_VOLTAGE #define VOLTAGE_READ_PIN 33 // GPIO used to monitor battery-voltage. Change to 35 if you're using Lolin D32 or Lolin D32 pro as it's hard-wired there! - float referenceVoltage = 3.35; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) - float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here + constexpr float referenceVoltage = 3.35; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) + constexpr float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here #endif // (optional) For measuring battery-voltage a voltage-divider is necessary. Their values need to be configured here. #ifdef MEASURE_BATTERY_VOLTAGE - uint8_t rdiv1 = 129; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) - uint16_t rdiv2 = 129; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! + constexpr uint8_t rdiv1 = 129; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) + constexpr uint16_t rdiv2 = 129; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! #endif // (Optional) remote control via infrared diff --git a/src/settings-espa1s.h b/src/settings-espa1s.h index 1463a9e..41d48c3 100644 --- a/src/settings-espa1s.h +++ b/src/settings-espa1s.h @@ -90,14 +90,14 @@ // (optional) Monitoring of battery-voltage via ADC #ifdef MEASURE_BATTERY_VOLTAGE #define VOLTAGE_READ_PIN 33 // GPIO used to monitor battery-voltage. Change to 35 if you're using Lolin D32 or Lolin D32 pro as it's hard-wired there! - float referenceVoltage = 3.30; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) - float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here + constexpr float referenceVoltage = 3.30; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) + constexpr float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here #endif // (optional) For measuring battery-voltage a voltage-divider is necessary. Their values need to be configured here. #ifdef MEASURE_BATTERY_VOLTAGE - uint8_t rdiv1 = 129; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) - uint16_t rdiv2 = 389; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! + constexpr uint8_t rdiv1 = 129; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) + constexpr uint16_t rdiv2 = 389; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! #endif // (Optional) remote control via infrared diff --git a/src/settings-lolin32.h b/src/settings-lolin32.h index 4baf11d..5e5d1a1 100644 --- a/src/settings-lolin32.h +++ b/src/settings-lolin32.h @@ -89,14 +89,14 @@ // (optional) Monitoring of battery-voltage via ADC #ifdef MEASURE_BATTERY_VOLTAGE #define VOLTAGE_READ_PIN 33 // GPIO used to monitor battery-voltage. Change to 35 if you're using Lolin D32 or Lolin D32 pro as it's hard-wired there! - float referenceVoltage = 3.35; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) - float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here + constexpr float referenceVoltage = 3.35; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) + constexpr float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here #endif // (optional) For measuring battery-voltage a voltage-divider is necessary. Their values need to be configured here. #ifdef MEASURE_BATTERY_VOLTAGE - uint8_t rdiv1 = 129; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) - uint16_t rdiv2 = 129; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! + constexpr uint8_t rdiv1 = 129; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) + constexpr uint16_t rdiv2 = 129; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! #endif // (Optional) remote control via infrared diff --git a/src/settings-lolin_d32.h b/src/settings-lolin_d32.h index f964926..592fbb3 100644 --- a/src/settings-lolin_d32.h +++ b/src/settings-lolin_d32.h @@ -91,13 +91,13 @@ // (optional) Monitoring of battery-voltage via ADC #ifdef MEASURE_BATTERY_VOLTAGE #define VOLTAGE_READ_PIN 35 // Cannot be changed, it's built in - float referenceVoltage = 3.30; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) - float offsetVoltage = 0.2; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here + constexpr float referenceVoltage = 3.30; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) + constexpr float offsetVoltage = 0.2; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here #endif #ifdef MEASURE_BATTERY_VOLTAGE - uint8_t rdiv1 = 100; // Cannot be changed, it's built in - uint16_t rdiv2 = 100; // Cannot be changed, it's built in + constexpr uint8_t rdiv1 = 100; // Cannot be changed, it's built in + constexpr uint16_t rdiv2 = 100; // Cannot be changed, it's built in #endif // (Optional) remote control via infrared diff --git a/src/settings-lolin_d32_pro.h b/src/settings-lolin_d32_pro.h index 8b94eab..fdfd68b 100644 --- a/src/settings-lolin_d32_pro.h +++ b/src/settings-lolin_d32_pro.h @@ -86,14 +86,14 @@ // (optional) Monitoring of battery-voltage via ADC #ifdef MEASURE_BATTERY_VOLTAGE #define VOLTAGE_READ_PIN 35 // GPIO used to monitor battery-voltage. Cannot be changed, it's built in - float referenceVoltage = 3.30; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) - float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here + constexpr float referenceVoltage = 3.30; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) + constexpr float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here #endif // (optional) For measuring battery-voltage a voltage-divider is already onboard. Connect a LiPo and use it! #ifdef MEASURE_BATTERY_VOLTAGE - uint8_t rdiv1 = 100; // Cannot be changed, it's built in - uint16_t rdiv2 = 100; // Cannot be changed, it's built in + constexpr uint8_t rdiv1 = 100; // Cannot be changed, it's built in + constexpr uint16_t rdiv2 = 100; // Cannot be changed, it's built in #endif // (Optional) remote control via infrared diff --git a/src/settings-ttgo_t8.h b/src/settings-ttgo_t8.h index e57a9ef..dd27fbd 100644 --- a/src/settings-ttgo_t8.h +++ b/src/settings-ttgo_t8.h @@ -86,14 +86,14 @@ // (optional) Monitoring of battery-voltage via ADC #ifdef MEASURE_BATTERY_VOLTAGE #define VOLTAGE_READ_PIN 35 // GPIO used to monitor battery-voltage. Change to 35 if you're using Lolin D32 or Lolin D32 pro as it's hard-wired there! - float referenceVoltage = 3.35; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) - float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here + constexpr float referenceVoltage = 3.35; // Voltage between 3.3V and GND-pin at the develboard in battery-mode (disconnect USB!) + constexpr float offsetVoltage = 0.1; // If voltage measured by ESP isn't 100% accurate, you can add an correction-value here #endif // (optional) For measuring battery-voltage a voltage-divider is necessary. Their values need to be configured here. #ifdef MEASURE_BATTERY_VOLTAGE - uint8_t rdiv1 = 100; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) - uint16_t rdiv2 = 100; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! + constexpr uint8_t rdiv1 = 100; // Rdiv1 of voltage-divider (kOhms) (measure exact value with multimeter!) + constexpr uint16_t rdiv2 = 100; // Rdiv2 of voltage-divider (kOhms) (measure exact value with multimeter!) => used to measure voltage via ADC! #endif // (Optional) remote control via infrared diff --git a/src/settings.h b/src/settings.h index 4786432..dc51994 100644 --- a/src/settings.h +++ b/src/settings.h @@ -61,14 +61,14 @@ #endif #if defined(RFID_READER_TYPE_MFRC522_I2C) || defined(RFID_READER_TYPE_MFRC522_SPI) - uint8_t rfidGain = 0x07 << 4; // Sensitivity of RC522. For possible values see reference: https://forum.espuino.de/uploads/default/original/1X/9de5f8d35cbc123c1378cad1beceb3f51035cec0.png + constexpr uint8_t rfidGain = 0x07 << 4; // Sensitivity of RC522. For possible values see reference: https://forum.espuino.de/uploads/default/original/1X/9de5f8d35cbc123c1378cad1beceb3f51035cec0.png #endif //############# Port-expander-configuration ###################### #ifdef PORT_EXPANDER_ENABLE - const uint8_t portsToRead = 2; // PCA9555 has two ports à 8 channels. If 8 channels are sufficient, set to 1 and only use the first port! - uint8_t expanderI2cAddress = 0x20; // I2C-address of PCA9555 + constexpr const uint8_t portsToRead = 2; // PCA9555 has two ports à 8 channels. If 8 channels are sufficient, set to 1 and only use the first port! + constexpr uint8_t expanderI2cAddress = 0x20; // I2C-address of PCA9555 #endif //################## BUTTON-Layout ################################## @@ -130,33 +130,28 @@ #define BUTTON_MULTI_45 CMD_NOTHING //#################### Various settings ############################## - // Loglevels available (don't change!) - #define LOGLEVEL_ERROR 1 // only errors - #define LOGLEVEL_NOTICE 2 // errors + important messages - #define LOGLEVEL_INFO 3 // infos + errors + important messages - #define LOGLEVEL_DEBUG 4 // almost everything // Serial-logging-configuration - const uint8_t serialDebug = LOGLEVEL_DEBUG; // Current loglevel for serial console + #define SERIAL_LOGLEVEL LOGLEVEL_DEBUG // Current loglevel for serial console // Static ip-configuration #ifdef STATIC_IP_ENABLE - IPAddress local_IP(192, 168, 2, 100); // ESPuino's IP - IPAddress gateway(192, 168, 2, 1); // IP of the gateway/router - IPAddress subnet(255, 255, 255, 0); // Netmask of your network (/24 => 255.255.255.0) - IPAddress primaryDNS(192, 168, 2, 1); // DNS-server of your network; in private networks it's usually the gatewy's IP + #define LOCAL_IP 192,168,2,100 // ESPuino's IP + #define GATEWAY_IP 192,168,2,1 // IP of the gateway/router + #define SUBNET_IP 255,255,255,0 // Netmask of your network (/24 => 255.255.255.0) + #define DNS_IP 192,168,2,1 // DNS-server of your network; in private networks it's usually the gatewy's IP #endif // Buttons (better leave unchanged if in doubts :-)) - uint8_t buttonDebounceInterval = 50; // Interval in ms to software-debounce buttons - uint16_t intervalToLongPress = 700; // Interval in ms to distinguish between short and long press of previous/next-button + constexpr uint8_t buttonDebounceInterval = 50; // Interval in ms to software-debounce buttons + constexpr uint16_t intervalToLongPress = 700; // Interval in ms to distinguish between short and long press of previous/next-button // RFID #define RFID_SCAN_INTERVAL 100 // Interval-time in ms (how often is RFID read?) // Automatic restart #ifdef SHUTDOWN_IF_SD_BOOT_FAILS - uint32_t deepsleepTimeAfterBootFails = 20; // Automatic restart takes place if boot was not successful after this period (in seconds) + constexpr uint32_t deepsleepTimeAfterBootFails = 20; // Automatic restart takes place if boot was not successful after this period (in seconds) #endif // FTP @@ -164,11 +159,11 @@ // Default user/password is esp32/esp32 but can be changed via webgui // ESPuino will create a WiFi if joing existing WiFi was not possible. Name can be configured here. - static const char accessPointNetworkSSID[] PROGMEM = "ESPuino"; // Access-point's SSID - static const char nameBluetoothDevice[] PROGMEM = "ESPuino"; // Name of your ESPuino as Bluetooth-device + constexpr const char accessPointNetworkSSID[] PROGMEM = "ESPuino"; // Access-point's SSID + constexpr const char nameBluetoothDevice[] PROGMEM = "ESPuino"; // Name of your ESPuino as Bluetooth-device // Where to store the backup-file for NVS-records - static const char backupFile[] PROGMEM = "/backup.txt"; // File is written every time a (new) RFID-assignment via GUI is done + constexpr const char backupFile[] PROGMEM = "/backup.txt"; // File is written every time a (new) RFID-assignment via GUI is done //#################### Settings for optional Modules############################## @@ -180,45 +175,61 @@ #endif // (optional) Default-voltages for battery-monitoring via Neopixel - float warningLowVoltage = 3.4; // If battery-voltage is >= this value, a cyclic warning will be indicated by Neopixel (can be changed via GUI!) - uint8_t voltageCheckInterval = 10; // How of battery-voltage is measured (in minutes) (can be changed via GUI!) - float voltageIndicatorLow = 3.0; // Lower range for Neopixel-voltage-indication (0 leds) (can be changed via GUI!) - float voltageIndicatorHigh = 4.2; // Upper range for Neopixel-voltage-indication (all leds) (can be changed via GUI!) + constexpr float s_warningLowVoltage = 3.4; // If battery-voltage is >= this value, a cyclic warning will be indicated by Neopixel (can be changed via GUI!) + constexpr uint8_t s_voltageCheckInterval = 10; // How of battery-voltage is measured (in minutes) (can be changed via GUI!) + constexpr float s_voltageIndicatorLow = 3.0; // Lower range for Neopixel-voltage-indication (0 leds) (can be changed via GUI!) + constexpr float s_voltageIndicatorHigh = 4.2; // Upper range for Neopixel-voltage-indication (all leds) (can be changed via GUI!) // (optinal) Headphone-detection (leave unchanged if in doubts...) #ifdef HEADPHONE_ADJUST_ENABLE - uint16_t headphoneLastDetectionDebounce = 1000; // Debounce-interval in ms when plugging in headphone + constexpr uint16_t headphoneLastDetectionDebounce = 1000; // Debounce-interval in ms when plugging in headphone #endif // Seekmode-configuration - uint8_t jumpOffset = 30; // Offset in seconds to jump for commands CMD_SEEK_FORWARDS / CMD_SEEK_BACKWARDS + constexpr uint8_t jumpOffset = 30; // Offset in seconds to jump for commands CMD_SEEK_FORWARDS / CMD_SEEK_BACKWARDS // (optional) Topics for MQTT #ifdef MQTT_ENABLE - uint16_t mqttRetryInterval = 60; // Try to reconnect to MQTT-server every (n) seconds if connection is broken - uint8_t mqttMaxRetriesPerInterval = 1; // Number of retries per time-interval (mqttRetryInterval). mqttRetryInterval 60 / mqttMaxRetriesPerInterval 1 => once every 60s + constexpr uint16_t mqttRetryInterval = 60; // Try to reconnect to MQTT-server every (n) seconds if connection is broken + constexpr uint8_t mqttMaxRetriesPerInterval = 1; // Number of retries per time-interval (mqttRetryInterval). mqttRetryInterval 60 / mqttMaxRetriesPerInterval 1 => once every 60s #define DEVICE_HOSTNAME "ESP32-ESPuino" // Name that is used for MQTT - static const char topicSleepCmnd[] PROGMEM = "Cmnd/ESPuino/Sleep"; - static const char topicSleepState[] PROGMEM = "State/ESPuino/Sleep"; - static const char topicRfidCmnd[] PROGMEM = "Cmnd/ESPuino/Rfid"; - static const char topicRfidState[] PROGMEM = "State/ESPuino/Rfid"; - static const char topicTrackState[] PROGMEM = "State/ESPuino/Track"; - static const char topicTrackControlCmnd[] PROGMEM = "Cmnd/ESPuino/TrackControl"; - static const char topicLoudnessCmnd[] PROGMEM = "Cmnd/ESPuino/Loudness"; - static const char topicLoudnessState[] PROGMEM = "State/ESPuino/Loudness"; - static const char topicSleepTimerCmnd[] PROGMEM = "Cmnd/ESPuino/SleepTimer"; - static const char topicSleepTimerState[] PROGMEM = "State/ESPuino/SleepTimer"; - static const char topicState[] PROGMEM = "State/ESPuino/State"; - static const char topicCurrentIPv4IP[] PROGMEM = "State/ESPuino/IPv4"; - static const char topicLockControlsCmnd[] PROGMEM ="Cmnd/ESPuino/LockControls"; - static const char topicLockControlsState[] PROGMEM ="State/ESPuino/LockControls"; - static const char topicPlaymodeState[] PROGMEM = "State/ESPuino/Playmode"; - static const char topicRepeatModeCmnd[] PROGMEM = "Cmnd/ESPuino/RepeatMode"; - static const char topicRepeatModeState[] PROGMEM = "State/ESPuino/RepeatMode"; - static const char topicLedBrightnessCmnd[] PROGMEM = "Cmnd/ESPuino/LedBrightness"; - static const char topicLedBrightnessState[] PROGMEM = "State/ESPuino/LedBrightness"; + constexpr const char topicSleepCmnd[] PROGMEM = "Cmnd/ESPuino/Sleep"; + constexpr const char topicSleepState[] PROGMEM = "State/ESPuino/Sleep"; + constexpr const char topicRfidCmnd[] PROGMEM = "Cmnd/ESPuino/Rfid"; + constexpr const char topicRfidState[] PROGMEM = "State/ESPuino/Rfid"; + constexpr const char topicTrackState[] PROGMEM = "State/ESPuino/Track"; + constexpr const char topicTrackControlCmnd[] PROGMEM = "Cmnd/ESPuino/TrackControl"; + constexpr const char topicLoudnessCmnd[] PROGMEM = "Cmnd/ESPuino/Loudness"; + constexpr const char topicLoudnessState[] PROGMEM = "State/ESPuino/Loudness"; + constexpr const char topicSleepTimerCmnd[] PROGMEM = "Cmnd/ESPuino/SleepTimer"; + constexpr const char topicSleepTimerState[] PROGMEM = "State/ESPuino/SleepTimer"; + constexpr const char topicState[] PROGMEM = "State/ESPuino/State"; + constexpr const char topicCurrentIPv4IP[] PROGMEM = "State/ESPuino/IPv4"; + constexpr const char topicLockControlsCmnd[] PROGMEM ="Cmnd/ESPuino/LockControls"; + constexpr const char topicLockControlsState[] PROGMEM ="State/ESPuino/LockControls"; + constexpr const char topicPlaymodeState[] PROGMEM = "State/ESPuino/Playmode"; + constexpr const char topicRepeatModeCmnd[] PROGMEM = "Cmnd/ESPuino/RepeatMode"; + constexpr const char topicRepeatModeState[] PROGMEM = "State/ESPuino/RepeatMode"; + constexpr const char topicLedBrightnessCmnd[] PROGMEM = "Cmnd/ESPuino/LedBrightness"; + constexpr const char topicLedBrightnessState[] PROGMEM = "State/ESPuino/LedBrightness"; #ifdef MEASURE_BATTERY_VOLTAGE - static const char topicBatteryVoltage[] PROGMEM = "State/ESPuino/Voltage"; + constexpr const char topicBatteryVoltage[] PROGMEM = "State/ESPuino/Voltage"; #endif #endif + + // !!! MAKE SURE TO EDIT PLATFORM SPECIFIC settings-****.h !!! + #if (HAL == 1) + #include "settings-lolin32.h" // Contains all user-relevant settings for Wemos Lolin32 + #elif (HAL == 2) + #include "settings-espa1s.h" // Contains all user-relevant settings for ESP32-A1S Audiokit + #elif (HAL == 3) + #include "settings-lolin_d32.h" // Contains all user-relevant settings for Wemos Lolin D32 + #elif (HAL == 4) + #include "settings-lolin_d32_pro.h" // Contains all user-relevant settings for Wemos Lolin D32 pro + #elif (HAL == 5) + #include "settings-ttgo_t8.h" // Contains all user-relevant settings for Lilygo TTGO T8 1.7 + #elif (HAL == 99) + #include "settings-custom.h" // Contains all user-relevant settings custom-board + #endif + #endif \ No newline at end of file