diff --git a/yoRadio/data/www/options.html.gz b/yoRadio/data/www/options.html.gz index fabd443..5249457 100644 Binary files a/yoRadio/data/www/options.html.gz and b/yoRadio/data/www/options.html.gz differ diff --git a/yoRadio/data/www/player.html.gz b/yoRadio/data/www/player.html.gz index 66c47f5..b4f92f1 100644 Binary files a/yoRadio/data/www/player.html.gz and b/yoRadio/data/www/player.html.gz differ diff --git a/yoRadio/data/www/script.js.gz b/yoRadio/data/www/script.js.gz index 656b931..4d47b38 100644 Binary files a/yoRadio/data/www/script.js.gz and b/yoRadio/data/www/script.js.gz differ diff --git a/yoRadio/data/www/style.css.gz b/yoRadio/data/www/style.css.gz index 210bbf3..b4cb834 100644 Binary files a/yoRadio/data/www/style.css.gz and b/yoRadio/data/www/style.css.gz differ diff --git a/yoRadio/data/www/theme.css b/yoRadio/data/www/theme.css index 185a6c4..6ce043e 100644 --- a/yoRadio/data/www/theme.css +++ b/yoRadio/data/www/theme.css @@ -13,4 +13,5 @@ --playlist-hover: #323232; --section-gradient: #111111; --section-border: #555555; +--heapbar-color: #3ea220; } diff --git a/yoRadio/src/AsyncWebServer/AsyncTCP.h b/yoRadio/src/AsyncWebServer/AsyncTCP.h index c898bf0..ed381a6 100644 --- a/yoRadio/src/AsyncWebServer/AsyncTCP.h +++ b/yoRadio/src/AsyncWebServer/AsyncTCP.h @@ -40,20 +40,23 @@ extern "C" { #endif #ifndef XTASK_MEM_SIZE - #define XTASK_MEM_SIZE 6144 // 8192 / 2 + //#define XTASK_MEM_SIZE 6144 // 8192 / 2 + #define XTASK_MEM_SIZE 1024*5 #endif #ifndef XTASK_PRIOTITY - #define XTASK_PRIOTITY 5 //3 + #define XTASK_PRIOTITY 3 //3 #endif #ifndef ATCP_TASK_DELAY #define ATCP_TASK_DELAY 2 #endif #ifndef XQUEUE_SIZE - #define XQUEUE_SIZE 128 // (32) + //#define XQUEUE_SIZE 128 // (32) + #define XQUEUE_SIZE 32 #endif #ifndef SEND_ASYNC_EVENT_DELAY - #define SEND_ASYNC_EVENT_DELAY portMAX_DELAY + //#define SEND_ASYNC_EVENT_DELAY portMAX_DELAY + #define SEND_ASYNC_EVENT_DELAY pdMS_TO_TICKS(1000) #endif class AsyncClient; diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index e444487..d3799ff 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -61,9 +61,9 @@ size_t AudioBuffer::init() { if(m_buffer == NULL) { // PSRAM not found, not configured or not enough available m_f_psram = false; - m_buffSize = m_buffSizeRAM; + m_buffSize = m_buffSizeRAM * config.store.abuff; m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); - m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + m_buffSize = m_buffSizeRAM * config.store.abuff - m_resBuffSizeRAM; } if(!m_buffer) return 0; @@ -183,7 +183,7 @@ Audio::Audio(bool internalDAC /* = false */, uint8_t channelEnabled /* = I2S_DAC m_i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; m_i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; // interrupt priority #ifdef OLD_DMABUF_PARAMS - m_i2s_config.dma_buf_count = 16; // 4×512×16=32768 + m_i2s_config.dma_buf_count = 16; // 4×512×16=32768 #else m_i2s_config.dma_buf_count = psramInit()?16:DMA_BUFCOUNT; #endif @@ -370,6 +370,20 @@ void Audio::setConnectionTimeout(uint16_t timeout_ms, uint16_t timeout_ms_ssl){ if(timeout_ms_ssl) m_timeout_ms_ssl = timeout_ms_ssl; } +void Audio::connectTask(void* pvParams) { + ConnectParams* params = static_cast(pvParams); + Audio* self = params->instance; + if(self->_client){ + self->_connectionResult = self->_client->connect(params->hostwoext, params->port/*, self->m_f_ssl ? self->m_timeout_ms_ssl : self->m_timeout_ms*/); + }else{ + self->_connectionResult = false; + } + free((void*)params->hostwoext); + delete params; + self->_connectTaskHandle = nullptr; + vTaskDelete(nullptr); +} + //--------------------------------------------------------------------------------------------------------------------- bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { // user and pwd for authentification only, can be empty @@ -480,7 +494,22 @@ bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { uint32_t t = millis(); if(m_f_Log) AUDIO_INFO("connect to %s on port %d path %s", hostwoext, port, extension); - res = _client->connect(hostwoext, port, m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms); + if(!config.store.watchdog){ + res = _client->connect(hostwoext, port, m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms); + }else{ + ConnectParams* params = new ConnectParams{ strdup(hostwoext), port, this }; _connectionResult = false; + xTaskCreatePinnedToCore(connectTask, "ConnectTask", WATCHDOG_TASK_SIZE, params, WATCHDOG_TASK_PRIORITY, &_connectTaskHandle, WATCHDOG_TASK_CORE_ID); + for(;;){ + if(millis()-t>(m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms) || _connectionResult) break; + vTaskDelay(10); + } + res = _connectionResult; + if (_connectTaskHandle!=nullptr) { + vTaskDelete(_connectTaskHandle); + _connectTaskHandle = nullptr; + AUDIO_INFO("WATCH DOG HAS FINISHED A WORK, BYE!"); + } + } if(res){ uint32_t dt = millis() - t; strcpy(m_lastHost, l_host); diff --git a/yoRadio/src/audioI2S/AudioEx.h b/yoRadio/src/audioI2S/AudioEx.h index c25184c..0051d9f 100644 --- a/yoRadio/src/audioI2S/AudioEx.h +++ b/yoRadio/src/audioI2S/AudioEx.h @@ -32,10 +32,6 @@ #include #endif // SDFATFS_USED -#ifndef AUDIOBUFFER_MULTIPLIER2 -#define AUDIOBUFFER_MULTIPLIER2 8 -#endif - #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) #include "hal/gpio_ll.h" #endif @@ -149,7 +145,7 @@ public: protected: size_t m_buffSizePSRAM = 300000; // most webstreams limit the advance to 100...300Kbytes //size_t m_buffSizeRAM = 1600 * 5; - size_t m_buffSizeRAM = 1600 * AUDIOBUFFER_MULTIPLIER2; + size_t m_buffSizeRAM = 1600; size_t m_buffSize = 0; size_t m_freeSpace = 0; size_t m_writeSpace = 0; @@ -287,6 +283,7 @@ private: void IIR_calculateCoefficients(int8_t G1, int8_t G2, int8_t G3); bool ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packetLength); void _computeVUlevel(int16_t sample[2]); + static void connectTask(void* pvParams); // implement several function with respect to the index of string void trim(char *s) { //fb trim in place @@ -425,11 +422,11 @@ private: if(str == NULL) return 0; uint32_t hash = 0; for(int i=0; i m_playlistContent; // m3u8 playlist buffer std::vector m_playlistURL; // m3u8 streamURLs buffer std::vector m_hashQueue; - + + struct ConnectParams { + char *hostwoext = NULL; + uint16_t port = 80; + Audio* instance; + }; + volatile bool _connectionResult; + TaskHandle_t _connectTaskHandle = nullptr; + const size_t m_frameSizeWav = 1600; const size_t m_frameSizeMP3 = 1600; const size_t m_frameSizeAAC = 1600; diff --git a/yoRadio/src/audioVS1053/audioVS1053Ex.cpp b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp index 199dd7e..e6dea96 100644 --- a/yoRadio/src/audioVS1053/audioVS1053Ex.cpp +++ b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp @@ -42,9 +42,9 @@ size_t AudioBuffer::init() { } } } else { // no PSRAM available, use ESP32 Flash Memory" - m_buffSize = m_buffSizeRAM; + m_buffSize = m_buffSizeRAM * config.store.abuff; m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); - m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + m_buffSize = m_buffSizeRAM * config.store.abuff - m_resBuffSizeRAM; } if(!m_buffer) return 0; @@ -1713,6 +1713,22 @@ void Audio::setConnectionTimeout(uint16_t timeout_ms, uint16_t timeout_ms_ssl){ if(timeout_ms) m_timeout_ms = timeout_ms; if(timeout_ms_ssl) m_timeout_ms_ssl = timeout_ms_ssl; } + +void Audio::connectTask(void* pvParams) { + ConnectParams* params = static_cast(pvParams); + Audio* self = params->instance; + bool res = true; + if(self->_client){ + self->_connectionResult = self->_client->connect(params->hostwoext, params->port/*, self->m_f_ssl ? self->m_timeout_ms_ssl : self->m_timeout_ms*/); + }else{ + self->_connectionResult = false; + } + free((void*)params->hostwoext); + delete params; + self->_connectTaskHandle = nullptr; + vTaskDelete(nullptr); +} + //--------------------------------------------------------------------------------------------------------------------- bool Audio::connecttohost(String host){ return connecttohost(host.c_str()); @@ -1827,8 +1843,24 @@ bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { uint32_t t = millis(); if(m_f_Log) AUDIO_INFO("connect to %s on port %d path %s", hostwoext, port, extension); - res = _client->connect(hostwoext, port, m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms); - + //res = _client->connect(hostwoext, port, m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms); + if(!config.store.watchdog){ + res = _client->connect(hostwoext, port, m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms); + }else{ + ConnectParams* params = new ConnectParams{ strdup(hostwoext), port, this }; _connectionResult = false; + xTaskCreatePinnedToCore(connectTask, "ConnectTask", WATCHDOG_TASK_SIZE, params, WATCHDOG_TASK_PRIORITY, &_connectTaskHandle, WATCHDOG_TASK_CORE_ID); + for(;;){ + if(millis()-t>(m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms) || _connectionResult) break; + vTaskDelay(10); + } + res = _connectionResult; + if (_connectTaskHandle!=nullptr) { + vTaskDelete(_connectTaskHandle); + _connectTaskHandle = nullptr; + AUDIO_INFO("WATCH DOG HAS FINISHED A WORK, BYE!"); + } + } + if(res){ uint32_t dt = millis() - t; strcpy(m_lastHost, l_host); diff --git a/yoRadio/src/audioVS1053/audioVS1053Ex.h b/yoRadio/src/audioVS1053/audioVS1053Ex.h index 9640c3e..441ad4c 100644 --- a/yoRadio/src/audioVS1053/audioVS1053Ex.h +++ b/yoRadio/src/audioVS1053/audioVS1053Ex.h @@ -9,11 +9,7 @@ #ifndef _vs1053_ext #define _vs1053_ext -#ifndef AUDIOBUFFER_MULTIPLIER2 -#define AUDIOBUFFER_MULTIPLIER2 10 -#endif - -#define VS1053VOLM 128 // 128 or 96 only +#define VS1053VOLM 128 // 128 or 96 only #define VS1053VOL(v) (VS1053VOLM==128?log10(((float)v+1)) * 50.54571334 + 128:log10(((float)v+1)) * 64.54571334 + 96) @@ -107,7 +103,7 @@ public: protected: const size_t m_buffSizePSRAM = 300000; // most webstreams limit the advance to 100...300Kbytes //const size_t m_buffSizeRAM = 1600 * 10; - const size_t m_buffSizeRAM = 1600 * AUDIOBUFFER_MULTIPLIER2; + const size_t m_buffSizeRAM = 1600; size_t m_buffSize = 0; size_t m_freeSpace = 0; size_t m_writeSpace = 0; @@ -136,6 +132,15 @@ private: std::vector m_playlistURL; // m3u8 streamURLs buffer std::vector m_hashQueue; + struct ConnectParams { + char *hostwoext = NULL; + uint16_t port = 80; + Audio* instance; + }; + volatile bool _connectionResult; + TaskHandle_t _connectTaskHandle = nullptr; + static void connectTask(void* pvParams); + private: enum : int { AUDIO_NONE, HTTP_RESPONSE_HEADER , AUDIO_DATA, AUDIO_LOCALFILE, AUDIO_METADATA, AUDIO_PLAYLISTINIT, AUDIO_PLAYLISTHEADER, AUDIO_PLAYLISTDATA, VS1053_SWM, VS1053_OGG}; @@ -146,10 +151,10 @@ private: enum : int { ST_NONE = 0, ST_WEBFILE = 1, ST_WEBSTREAM = 2}; private: - uint8_t cs_pin ; // Pin where CS line is connected - uint8_t dcs_pin ; // Pin where DCS line is connected - uint8_t dreq_pin ; // Pin where DREQ line is connected - uint8_t curvol ; // Current volume setting 0..100% + uint8_t cs_pin ; // Pin where CS line is connected + uint8_t dcs_pin ; // Pin where DCS line is connected + uint8_t dreq_pin ; // Pin where DREQ line is connected + uint8_t curvol ; // Current volume setting 0..100% const uint8_t vs1053_chunk_size = 32 ; int8_t m_balance = 0; // -16 (mute left) ... +16 (mute right) @@ -171,11 +176,11 @@ private: const uint8_t SCI_AICTRL2 = 0xE ; const uint8_t SCI_AICTRL3 = 0xF ; // SCI_MODE bits - const uint8_t SM_SDINEW = 11 ; // Bitnumber in SCI_MODE always on - const uint8_t SM_RESET = 2 ; // Bitnumber in SCI_MODE soft reset - const uint8_t SM_CANCEL = 3 ; // Bitnumber in SCI_MODE cancel song - const uint8_t SM_TESTS = 5 ; // Bitnumber in SCI_MODE for tests - const uint8_t SM_LINE1 = 14 ; // Bitnumber in SCI_MODE for Line input + const uint8_t SM_SDINEW = 11 ; // Bitnumber in SCI_MODE always on + const uint8_t SM_RESET = 2 ; // Bitnumber in SCI_MODE soft reset + const uint8_t SM_CANCEL = 3 ; // Bitnumber in SCI_MODE cancel song + const uint8_t SM_TESTS = 5 ; // Bitnumber in SCI_MODE for tests + const uint8_t SM_LINE1 = 14 ; // Bitnumber in SCI_MODE for Line input SPIClass* spi_VS1053 = NULL; SPISettings VS1053_SPI_DATA; // SPI settings normal speed @@ -242,7 +247,7 @@ protected: inline void DCS_LOW() {(dcs_pin&0x20) ? GPIO.out1_w1tc.data = 1 << (dcs_pin - 32) : GPIO.out_w1tc = 1 << dcs_pin;} inline void CS_HIGH() {( cs_pin&0x20) ? GPIO.out1_w1ts.data = 1 << ( cs_pin - 32) : GPIO.out_w1ts = 1 << cs_pin;} inline void CS_LOW() {( cs_pin&0x20) ? GPIO.out1_w1tc.data = 1 << ( cs_pin - 32) : GPIO.out_w1tc = 1 << cs_pin;} - inline void await_data_request() {while(!digitalRead(dreq_pin)) NOP();} // Very short delay + inline void await_data_request() {while(!digitalRead(dreq_pin)) NOP();} // Very short delay inline bool data_request() {return(digitalRead(dreq_pin) == HIGH);} void initInBuff(); @@ -319,8 +324,8 @@ public: size_t bufferFree(); size_t inBufferFilled(){ return bufferFilled(); } size_t inBufferFree(){ return bufferFree(); } - void setBalance(int8_t bal = 0); - void setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass); + void setBalance(int8_t bal = 0); + void setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass); void setDefaults(); void forceMono(bool m) {} // TODO /* VU METER */ diff --git a/yoRadio/src/core/audiohandlers.h b/yoRadio/src/core/audiohandlers.h index eb9231f..737648d 100644 --- a/yoRadio/src/core/audiohandlers.h +++ b/yoRadio/src/core/audiohandlers.h @@ -61,7 +61,7 @@ void audio_showstation(const char *info) { void audio_showstreamtitle(const char *info) { DBGH(); - if (strstr(info, "Account already in use") != NULL || strstr(info, "HTTP/1.0 401") != NULL) player.setError(info); + if (strstr(info, "Account already in use") != NULL || strstr(info, "HTTP/1.0 401") != NULL || strstr(info, "HTTP/1.1 401") != NULL) player.setError(info); bool p = printable(info) && (strlen(info) > 0); #ifdef DEBUG_TITLES config.setTitle(DEBUG_TITLES); @@ -73,7 +73,7 @@ void audio_showstreamtitle(const char *info) { void audio_error(const char *info) { //config.setTitle(info); player.setError(info); - telnet.printf("##ERROR#:\t%s\n", info); + //telnet.printf("##ERROR#:\t%s\n", info); } void audio_id3artist(const char *info){ @@ -88,11 +88,11 @@ void audio_id3album(const char *info){ if(strlen(config.station.title)==0){ config.setTitle(info); }else{ - char out[BUFLEN]= {0}; - strlcat(out, config.station.title, BUFLEN); - strlcat(out, " - ", BUFLEN); - strlcat(out, info, BUFLEN); - config.setTitle(out); + size_t tbs = sizeof(config.tmpBuf); + strlcat(config.tmpBuf, config.station.title, tbs); + strlcat(config.tmpBuf, " - ", tbs); + strlcat(config.tmpBuf, info, tbs); + config.setTitle(config.tmpBuf); } } } diff --git a/yoRadio/src/core/commandhandler.cpp b/yoRadio/src/core/commandhandler.cpp index 57c7969..deea5b4 100644 --- a/yoRadio/src/core/commandhandler.cpp +++ b/yoRadio/src/core/commandhandler.cpp @@ -5,6 +5,7 @@ #include "config.h" #include "controls.h" #include "options.h" +#include "telnet.h" CommandHandler cmd; @@ -65,11 +66,16 @@ bool CommandHandler::exec(const char *command, const char *value, uint8_t cid) { if (strEquals(command, "screensaverplayingenabled")){ config.setScreensaverPlayingEnabled(static_cast(atoi(value))); return true; } if (strEquals(command, "screensaverplayingtimeout")){ config.setScreensaverPlayingTimeout(static_cast(atoi(value))); return true; } if (strEquals(command, "screensaverplayingblank")) { config.setScreensaverPlayingBlank(static_cast(atoi(value))); return true; } + if (strEquals(command, "abuff")){ config.saveValue(&config.store.abuff, static_cast(atoi(value))); return true; } + if (strEquals(command, "telnet")){ config.saveValue(&config.store.telnet, static_cast(atoi(value))); telnet.toggle(); return true; } + if (strEquals(command, "watchdog")){ config.saveValue(&config.store.watchdog, static_cast(atoi(value))); return true; } - if (strEquals(command, "tzh")) { config.saveValue(&config.store.tzHour, static_cast(atoi(value))); return true; } - if (strEquals(command, "tzm")) { config.saveValue(&config.store.tzMin, static_cast(atoi(value))); return true; } - if (strEquals(command, "sntp2")) { config.saveValue(config.store.sntp2, value, 35, false); return true; } - if (strEquals(command, "sntp1")) { config.setSntpOne(value); return true; } + if (strEquals(command, "tzh")) { config.saveValue(&config.store.tzHour, static_cast(atoi(value))); return true; } + if (strEquals(command, "tzm")) { config.saveValue(&config.store.tzMin, static_cast(atoi(value))); return true; } + if (strEquals(command, "sntp2")) { config.saveValue(config.store.sntp2, value, 35, false); return true; } + if (strEquals(command, "sntp1")) { config.setSntpOne(value); return true; } + if (strEquals(command, "timeint")) { config.saveValue(&config.store.timeSyncInterval, static_cast(atoi(value))); return true; } + if (strEquals(command, "timeintrtc")) { config.saveValue(&config.store.timeSyncIntervalRTC, static_cast(atoi(value))); return true; } if (strEquals(command, "volsteps")) { config.saveValue(&config.store.volsteps, static_cast(atoi(value))); return true; } if (strEquals(command, "encacc")) { setEncAcceleration(static_cast(atoi(value))); return true; } @@ -79,32 +85,33 @@ bool CommandHandler::exec(const char *command, const char *value, uint8_t cid) { if (strEquals(command, "lat")) { config.saveValue(config.store.weatherlat, value, 10, false); return true; } if (strEquals(command, "lon")) { config.saveValue(config.store.weatherlon, value, 10, false); return true; } if (strEquals(command, "key")) { config.setWeatherKey(value); return true; } - //<-----TODO + if (strEquals(command, "wint")) { config.saveValue(&config.store.weatherSyncInterval, static_cast(atoi(value))); return true; } + if (strEquals(command, "volume")) { player.setVol(static_cast(atoi(value))); return true; } if (strEquals(command, "sdpos")) { config.setSDpos(static_cast(atoi(value))); return true; } if (strEquals(command, "snuffle")) { config.setSnuffle(strcmp(value, "true") == 0); return true; } if (strEquals(command, "balance")) { config.setBalance(static_cast(atoi(value))); return true; } if (strEquals(command, "reboot")) { ESP.restart(); return true; } + if (strEquals(command, "boot")) { ESP.restart(); return true; } if (strEquals(command, "format")) { SPIFFS.format(); ESP.restart(); return true; } - if (strEquals(command, "submitplaylist")) { return true; } - + if (strEquals(command, "submitplaylist")) { player.sendCommand({PR_STOP, 0}); return true; } + #if IR_PIN!=255 if (strEquals(command, "irbtn")) { config.setIrBtn(atoi(value)); return true; } if (strEquals(command, "chkid")) { config.irchck = static_cast(atoi(value)); return true; } if (strEquals(command, "irclr")) { config.ircodes.irVals[config.irindex][static_cast(atoi(value))] = 0; return true; } #endif if (strEquals(command, "reset")) { config.resetSystem(value, cid); return true; } - + if (strEquals(command, "smartstart")){ uint8_t ss = atoi(value) == 1 ? 1 : 2; if (!player.isRunning() && ss == 1) ss = 0; config.setSmartStart(ss); return true; } if (strEquals(command, "audioinfo")) { config.saveValue(&config.store.audioinfo, static_cast(atoi(value))); display.putRequest(AUDIOINFO); return true; } if (strEquals(command, "vumeter")) { config.saveValue(&config.store.vumeter, static_cast(atoi(value))); display.putRequest(SHOWVUMETER); return true; } if (strEquals(command, "softap")) { config.saveValue(&config.store.softapdelay, static_cast(atoi(value))); return true; } if (strEquals(command, "mdnsname")) { config.saveValue(config.store.mdnsname, value, MDNS_LENGTH); return true; } if (strEquals(command, "rebootmdns")){ - char buf[MDNS_LENGTH*2]; - if(strlen(config.store.mdnsname)>0) snprintf(buf, MDNS_LENGTH*2, "{\"redirect\": \"http://%s.local\"}", config.store.mdnsname); - else snprintf(buf, MDNS_LENGTH*2, "{\"redirect\": \"http://%s/\"}", WiFi.localIP().toString().c_str()); - websocket.text(cid, buf); delay(500); ESP.restart(); + if(strlen(config.store.mdnsname)>0) snprintf(config.tmpBuf, sizeof(config.tmpBuf), "{\"redirect\": \"http://%s.local/settings.html\"}", config.store.mdnsname); + else snprintf(config.tmpBuf, sizeof(config.tmpBuf), "{\"redirect\": \"http://%s/settings.html\"}", config.ipToStr(WiFi.localIP())); + websocket.text(cid, config.tmpBuf); delay(500); ESP.restart(); return true; } diff --git a/yoRadio/src/core/config.cpp b/yoRadio/src/core/config.cpp index d3c520b..21bba5f 100644 --- a/yoRadio/src/core/config.cpp +++ b/yoRadio/src/core/config.cpp @@ -6,6 +6,7 @@ #include "network.h" #include "netserver.h" #include "controls.h" +#include "timekeeper.h" #ifdef USE_SD #include "sdmanager.h" #endif @@ -13,6 +14,19 @@ Config config; +#ifdef HEAP_DBG +void printHeapFragmentationInfo(const char* title){ + size_t freeHeap = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + size_t largestBlock = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT); + float fragmentation = 100.0 * (1.0 - ((float)largestBlock / (float)freeHeap)); + Serial.printf("\n****** %s ******\n", title); + Serial.printf("* Free heap: %u bytes\n", freeHeap); + Serial.printf("* Largest free block: %u bytes\n", largestBlock); + Serial.printf("* Fragmentation: %.2f%%\n", fragmentation); + Serial.printf("*************************************\n\n"); +} +#endif + void u8fix(char *src){ char last = src[strlen(src)-1]; if ((uint8_t)last >= 0xC2) src[strlen(src)-1]='\0'; @@ -46,7 +60,8 @@ void Config::init() { screensaverPlayingTicks = 0; newConfigMode = 0; isScreensaver = false; - bootInfo(); + memset(tmpBuf, 0, BUFLEN); + //bootInfo(); #if RTCSUPPORTED _rtcFound = false; BOOTLOG("RTC begin(SDA=%d,SCL=%d)", RTC_SDA, RTC_SCL); @@ -69,7 +84,7 @@ void Config::init() { #endif #endif eepromRead(EEPROM_START, store); - + bootInfo(); // https://github.com/e2002/yoradio/pull/149 if (store.config_set != 4262) { setDefaults(); } @@ -93,6 +108,7 @@ void Config::init() { _SDplaylistFS = &SPIFFS; #endif _bootDone=false; + setTimeConf(); } void Config::_setupVersion(){ @@ -103,9 +119,8 @@ void Config::_setupVersion(){ saveValue(&store.screensaverTimeout, (uint16_t)20); break; case 2: - char buf[MDNS_LENGTH]; - snprintf(buf, MDNS_LENGTH, "yoradio-%x", getChipId()); - saveValue(store.mdnsname, buf, MDNS_LENGTH); + snprintf(tmpBuf, MDNS_LENGTH, "yoradio-%x", (unsigned int)getChipId()); + saveValue(store.mdnsname, tmpBuf, MDNS_LENGTH); saveValue(&store.skipPlaylistUpDown, false); break; case 3: @@ -114,6 +129,13 @@ void Config::_setupVersion(){ saveValue(&store.screensaverPlayingTimeout, (uint16_t)5); saveValue(&store.screensaverPlayingBlank, false); break; + case 4: + saveValue(&store.abuff, (uint16_t)(VS1053_CS==255?7:10)); + saveValue(&store.telnet, true); + saveValue(&store.watchdog, true); + saveValue(&store.timeSyncInterval, (uint16_t)60); //min + saveValue(&store.timeSyncIntervalRTC, (uint16_t)24); //hours + saveValue(&store.weatherSyncInterval, (uint16_t)30); // min default: break; } @@ -200,6 +222,44 @@ bool Config::spiffsCleanup(){ return ret; } +char * Config::ipToStr(IPAddress ip){ + snprintf(ipBuf, 16, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]); + return ipBuf; +} +bool Config::prepareForPlaying(uint16_t stationId){ + setDspOn(1); + vuThreshold = 0; + screensaverTicks=SCREENSAVERSTARTUPDELAY; + screensaverPlayingTicks=SCREENSAVERSTARTUPDELAY; + if(getMode()!=PM_SDCARD) { + display.putRequest(PSTOP); + } + + if(!loadStation(stationId)) return false; + setTitle(getMode()==PM_WEB?const_PlConnect:"[next track]"); + station.bitrate=0; + setBitrateFormat(BF_UNCNOWN); + display.putRequest(DBITRATE); + display.putRequest(NEWSTATION); + display.putRequest(NEWMODE, PLAYER); + netserver.requestOnChange(STATION, 0); + netserver.requestOnChange(MODE, 0); + netserver.loop(); + netserver.loop(); + if(store.smartstart!=2) + setSmartStart(0); + return true; +} +void Config::configPostPlaying(uint16_t stationId){ + if(getMode()==PM_SDCARD) { + sdResumePos = 0; + saveValue(&store.lastSdStation, stationId); + } + if(store.smartstart!=2) setSmartStart(1); + netserver.requestOnChange(MODE, 0); + //display.putRequest(NEWMODE, PLAYER); + display.putRequest(PSTART); +} void Config::initPlaylistMode(){ uint16_t _lastStation = 0; uint16_t cs = playlistLength(); @@ -369,19 +429,17 @@ void Config::setSntpOne(const char *val){ tzdone = true; } if (tzdone) { - network.forceTimeSync = true; + timekeeper.forceTimeSync = true; saveValue(config.store.sntp1, val, 35); } } void Config::setShowweather(bool val){ config.saveValue(&config.store.showweather, val); - network.trueWeather=false; - network.forceWeather = true; + timekeeper.forceWeather = true; display.putRequest(SHOWWEATHER); } void Config::setWeatherKey(const char *val){ saveValue(store.weatherkey, val, WEATHERKEY_LENGTH); - network.trueWeather=false; display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); } @@ -411,7 +469,10 @@ void Config::resetSystem(const char *val, uint8_t clientId){ saveValue(&store.audioinfo, false, false); saveValue(&store.vumeter, false, false); saveValue(&store.softapdelay, (uint8_t)0, false); - snprintf(store.mdnsname, MDNS_LENGTH, "yoradio-%x", getChipId()); + saveValue(&store.abuff, (uint16_t)(VS1053_CS==255?7:10), false); + saveValue(&store.telnet, true); + saveValue(&store.watchdog, true); + snprintf(store.mdnsname, MDNS_LENGTH, "yoradio-%x", (unsigned int)getChipId()); saveValue(store.mdnsname, store.mdnsname, MDNS_LENGTH, true, true); display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); netserver.requestOnChange(GETSYSTEM, clientId); @@ -443,8 +504,10 @@ void Config::resetSystem(const char *val, uint8_t clientId){ saveValue(&store.tzMin, (int8_t)0, false); saveValue(store.sntp1, "pool.ntp.org", 35, false); saveValue(store.sntp2, "0.ru.pool.ntp.org", 35); + saveValue(&store.timeSyncInterval, (uint16_t)60); + saveValue(&store.timeSyncIntervalRTC, (uint16_t)24); configTime(store.tzHour * 3600 + store.tzMin * 60, getTimezoneOffset(), store.sntp1, store.sntp2); - network.forceTimeSync = true; + timekeeper.forceTimeSync = true; netserver.requestOnChange(GETTIMEZONE, clientId); return; } @@ -453,7 +516,8 @@ void Config::resetSystem(const char *val, uint8_t clientId){ saveValue(store.weatherlat, "55.7512", 10, false); saveValue(store.weatherlon, "37.6184", 10, false); saveValue(store.weatherkey, "", WEATHERKEY_LENGTH); - network.trueWeather=false; + saveValue(&store.weatherSyncInterval, (uint16_t)30); + //network.trueWeather=false; display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); netserver.requestOnChange(GETWEATHER, clientId); return; @@ -530,11 +594,17 @@ void Config::setDefaults() { store.screensaverEnabled = false; store.screensaverTimeout = 20; store.screensaverBlank = false; - snprintf(store.mdnsname, MDNS_LENGTH, "yoradio-%x", getChipId()); + snprintf(store.mdnsname, MDNS_LENGTH, "yoradio-%x", (unsigned int)getChipId()); store.skipPlaylistUpDown = false; store.screensaverPlayingEnabled = false; store.screensaverPlayingTimeout = 5; store.screensaverPlayingBlank = false; + store.abuff = VS1053_CS==255?7:10; + store.telnet = true; + store.watchdog = true; + store.timeSyncInterval = 60; //min + store.timeSyncIntervalRTC = 24; //hour + store.weatherSyncInterval = 30; //min eepromWrite(EEPROM_START, store); } @@ -627,12 +697,11 @@ void Config::indexPlaylist() { if (!playlist) { return; } - char sName[BUFLEN], sUrl[BUFLEN]; int sOvol; File index = SPIFFS.open(INDEX_PATH, "w"); while (playlist.available()) { uint32_t pos = playlist.position(); - if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + if (parseCSV(playlist.readStringUntil('\n').c_str(), tmpBuf, tmpBuf2, sOvol)) { index.write((uint8_t *) &pos, 4); } } @@ -661,7 +730,6 @@ uint16_t Config::playlistLength(){ return out; } bool Config::loadStation(uint16_t ls) { - char sName[BUFLEN], sUrl[BUFLEN]; int sOvol; uint16_t cs = playlistLength(); if (cs == 0) { @@ -681,11 +749,11 @@ bool Config::loadStation(uint16_t ls) { index.readBytes((char *) &pos, 4); index.close(); playlist.seek(pos, SeekSet); - if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + if (parseCSV(playlist.readStringUntil('\n').c_str(), tmpBuf, tmpBuf2, sOvol)) { memset(station.url, 0, BUFLEN); memset(station.name, 0, BUFLEN); - strncpy(station.name, sName, BUFLEN); - strncpy(station.url, sUrl, BUFLEN); + strncpy(station.name, tmpBuf, BUFLEN); + strncpy(station.url, tmpBuf2, BUFLEN); station.ovol = sOvol; setLastStation(ls); } @@ -698,11 +766,11 @@ char * Config::stationByNum(uint16_t num){ File index = SDPLFS()->open(REAL_INDEX, "r"); index.seek((num - 1) * 4, SeekSet); uint32_t pos; - memset(_stationBuf, 0, BUFLEN/2); + memset(_stationBuf, 0, sizeof(_stationBuf)); index.readBytes((char *) &pos, 4); index.close(); playlist.seek(pos, SeekSet); - strncpy(_stationBuf, playlist.readStringUntil('\t').c_str(), BUFLEN/2); + strncpy(_stationBuf, playlist.readStringUntil('\t').c_str(), sizeof(_stationBuf)); playlist.close(); return _stationBuf; } @@ -880,6 +948,14 @@ bool Config::saveWifi() { return true; } +void Config::setTimeConf(){ + if(strlen(store.sntp1)>0 && strlen(store.sntp2)>0){ + configTime(store.tzHour * 3600 + store.tzMin * 60, getTimezoneOffset(), store.sntp1, store.sntp2); + }else if(strlen(store.sntp1)>0){ + configTime(store.tzHour * 3600 + store.tzMin * 60, getTimezoneOffset(), store.sntp1); + } +} + bool Config::initNetwork() { File file = SPIFFS.open(SSIDS_PATH, "r"); if (!file || file.isDirectory()) { @@ -973,7 +1049,7 @@ void Config::doSleepW(){ void Config::sleepForAfter(uint16_t sf, uint16_t sa){ sleepfor = sf; - if(sa > 0) _sleepTimer.attach(sa * 60, doSleep); + if(sa > 0) timekeeper.waitAndDo(sa * 60, doSleep); else doSleep(); } diff --git a/yoRadio/src/core/config.h b/yoRadio/src/core/config.h index 819e659..bbd577c 100644 --- a/yoRadio/src/core/config.h +++ b/yoRadio/src/core/config.h @@ -1,7 +1,6 @@ #ifndef config_h #define config_h #include "Arduino.h" -#include #include #include #include @@ -47,13 +46,23 @@ #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) #define ESP_ARDUINO_3 1 #endif -#define CONFIG_VERSION 4 + +#ifdef HEAP_DBG + #define HEAP_INFO() printHeapFragmentationInfo(__PRETTY_FUNCTION__) + void printHeapFragmentationInfo(const char* title); +#else + #define HEAP_INFO() +#endif + +#define CONFIG_VERSION 5 enum playMode_e : uint8_t { PM_WEB=0, PM_SDCARD=1 }; enum BitrateFormat { BF_UNCNOWN, BF_MP3, BF_AAC, BF_FLAC, BF_OGG, BF_WAV }; void u8fix(char *src); +void checkAllTasksStack(); + struct theme_t { uint16_t background; uint16_t meta; @@ -143,6 +152,12 @@ struct config_t bool screensaverPlayingBlank; char mdnsname[24]; bool skipPlaylistUpDown; + uint16_t abuff; + bool telnet; + bool watchdog; + uint16_t timeSyncInterval; + uint16_t timeSyncIntervalRTC; + uint16_t weatherSyncInterval; }; #if IR_PIN!=255 @@ -189,6 +204,10 @@ class Config { uint16_t screensaverPlayingTicks; bool isScreensaver; int newConfigMode; + char tmpBuf[BUFLEN]; + char tmpBuf2[BUFLEN]; + char ipBuf[16]; + char _stationBuf[BUFLEN/2]; public: Config() {}; //void save(); @@ -214,6 +233,7 @@ class Config { bool loadStation(uint16_t station); bool initNetwork(); bool saveWifi(); + void setTimeConf(); bool saveWifiFromNextion(const char* post); void setSmartStart(uint8_t ss); void setBitrateFormat(BitrateFormat fmt) { configFmt = fmt; } @@ -259,8 +279,10 @@ class Config { void setIrBtn(int val); #endif void resetSystem(const char *val, uint8_t clientId); - bool spiffsCleanup(); + char * ipToStr(IPAddress ip); + bool prepareForPlaying(uint16_t stationId); + void configPostPlaying(uint16_t stationId); FS* SDPLFS(){ return _SDplaylistFS; } #if RTCSUPPORTED bool isRTCFound(){ return _rtcFound; }; @@ -303,7 +325,6 @@ class Config { #endif FS* _SDplaylistFS; void setDefaults(); - Ticker _sleepTimer; static void doSleep(); uint16_t color565(uint8_t r, uint8_t g, uint8_t b); void _setupVersion(); @@ -314,7 +335,6 @@ class Config { uint16_t station = random(1, store.countStation); return station; } - char _stationBuf[BUFLEN/2]; }; extern Config config; diff --git a/yoRadio/src/core/controls.cpp b/yoRadio/src/core/controls.cpp index 7eb5803..2175c5d 100644 --- a/yoRadio/src/core/controls.cpp +++ b/yoRadio/src/core/controls.cpp @@ -56,12 +56,11 @@ constexpr uint8_t nrOfButtons = sizeof(button) / sizeof(button[0]); #include "../IRremoteESP8266/IRtext.h" #include "../IRremoteESP8266/IRutils.h" uint8_t irVolRepeat = 0; -const uint16_t kCaptureBufferSize = 1024; -const uint8_t kTimeout = IR_TIMEOUT; +//const uint16_t kCaptureBufferSize = 1024; const uint16_t kMinUnknownSize = 12; #define LEGACY_TIMING_INFO false -IRrecv irrecv(IR_PIN, kCaptureBufferSize, kTimeout, true); +IRrecv irrecv(IR_PIN, IR_BUFSIZE, IR_TIMEOUT, true); decode_results irResults; #endif diff --git a/yoRadio/src/core/display.cpp b/yoRadio/src/core/display.cpp index bba7421..fd866f3 100644 --- a/yoRadio/src/core/display.cpp +++ b/yoRadio/src/core/display.cpp @@ -5,13 +5,48 @@ #include "display.h" #include "player.h" #include "network.h" - +#include "netserver.h" +#include "timekeeper.h" Display display; #ifdef USE_NEXTION Nextion nextion; #endif +#ifndef CORE_STACK_SIZE + #define CORE_STACK_SIZE 1024*4 +#endif +#ifndef DSP_TASK_PRIORITY + #define DSP_TASK_PRIORITY 2 +#endif +#ifndef DSP_TASK_CORE_ID + #define DSP_TASK_CORE_ID 0 +#endif + +QueueHandle_t displayQueue; + +static void loopDspTask(void * pvParameters){ + while(true){ + #ifndef DUMMYDISPLAY + if(displayQueue==NULL) break; + netserver.loop(); + if(timekeeper.loop0()) + display.loop(); + // will NOT delay here, would use message dequeue timeout instead + //vTaskDelay(DSP_TASK_DELAY); + #else + netserver.loop(); + timekeeper.loop0(); + vTaskDelay(10); + #endif + } + vTaskDelete( NULL ); +} + +void Display::_createDspTask(){ + xTaskCreatePinnedToCore(loopDspTask, "DspTask", CORE_STACK_SIZE, NULL, DSP_TASK_PRIORITY, NULL, DSP_TASK_CORE_ID); +} + #ifndef DUMMYDISPLAY //============================================================================================================================ DspCore dsp; @@ -19,40 +54,27 @@ DspCore dsp; Page *pages[] = { new Page(), new Page(), new Page(), new Page() }; #ifndef DSQ_SEND_DELAY - #define DSQ_SEND_DELAY portMAX_DELAY + //#define DSQ_SEND_DELAY portMAX_DELAY + #define DSQ_SEND_DELAY pdMS_TO_TICKS(200) #endif -#ifndef CORE_STACK_SIZE - #define CORE_STACK_SIZE 1024*3 -#endif #ifndef DSP_TASK_DELAY - #define DSP_TASK_DELAY pdMS_TO_TICKS(10) + #define DSP_TASK_DELAY pdMS_TO_TICKS(20) // cap for 50 fps #endif + +// will use DSP_QUEUE_TICKS as delay interval for display task runner when there are no msgs in a queue to process +#define DSP_QUEUE_TICKS DSP_TASK_DELAY + #if !((DSP_MODEL==DSP_ST7735 && DTYPE==INITR_BLACKTAB) || DSP_MODEL==DSP_ST7789 || DSP_MODEL==DSP_ST7796 || DSP_MODEL==DSP_ILI9488 || DSP_MODEL==DSP_ILI9486 || DSP_MODEL==DSP_ILI9341 || DSP_MODEL==DSP_ILI9225) #undef BITRATE_FULL #define BITRATE_FULL false #endif -TaskHandle_t DspTask; -QueueHandle_t displayQueue; + void returnPlayer(){ display.putRequest(NEWMODE, PLAYER); } -void Display::_createDspTask(){ - xTaskCreatePinnedToCore(loopDspTask, "DspTask", CORE_STACK_SIZE, NULL, 4, &DspTask, !xPortGetCoreID()); -} - -void loopDspTask(void * pvParameters){ - while(true){ - if(displayQueue==NULL) break; - display.loop(); - vTaskDelay(DSP_TASK_DELAY); - } - vTaskDelete( NULL ); - DspTask=NULL; -} - void Display::init() { Serial.print("##[BOOT]#\tdisplay.init\t"); #ifdef USE_NEXTION @@ -117,7 +139,7 @@ void Display::_buildPager(){ _volbar = new SliderWidget(volbarConf, config.theme.volbarin, config.theme.background, 254, config.theme.volbarout); #endif #ifndef HIDE_HEAPBAR - _heapbar = new SliderWidget(heapbarConf, config.theme.buffer, config.theme.background, psramInit()?300000:1600 * AUDIOBUFFER_MULTIPLIER2); + _heapbar = new SliderWidget(heapbarConf, config.theme.buffer, config.theme.background, psramInit()?300000:1600 * config.store.abuff); #endif #ifndef HIDE_VOL _voltxt = new TextWidget(voltxtConf, 10, false, config.theme.vol, config.theme.background); @@ -197,7 +219,7 @@ void Display::_apScreen() { TextWidget *appass2 = (TextWidget*) &_boot->addWidget(new TextWidget(apPass2Conf, 30, false, config.theme.clock, config.theme.background)); appass2->setText(apPassword); ScrollWidget *bootSett = (ScrollWidget*) &_boot->addWidget(new ScrollWidget("*", apSettConf, config.theme.title2, config.theme.background)); - bootSett->setText(WiFi.softAPIP().toString().c_str(), apSettFmt); + bootSett->setText(config.ipToStr(WiFi.softAPIP()), apSettFmt); _pager.addPage(_boot); _pager.setPage(_boot); #else @@ -234,7 +256,7 @@ void Display::_start() { if(_vuwidget) _vuwidget->lock(); if(_rssi) _setRSSI(WiFi.RSSI()); #ifndef HIDE_IP - if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt); + if(_volip) _volip->setText(config.ipToStr(WiFi.localIP()), iptxtFmt); #endif _pager.setPage( pages[PG_PLAYER]); _volume(); @@ -254,11 +276,6 @@ void Display::_showDialog(const char *title){ _meta.setText(title); } -void Display::_setReturnTicker(uint8_t time_s){ - _returnTicker.detach(); - _returnTicker.once(time_s, returnPlayer); -} - void Display::_swichMode(displayMode_e newmode) { #ifdef USE_NEXTION //nextion.swichMode(newmode); @@ -276,7 +293,6 @@ void Display::_swichMode(displayMode_e newmode) { dsp.clearDsp(); #endif numOfNextStation = 0; - _returnTicker.detach(); #ifdef META_MOVE _meta.moveBack(); #endif @@ -304,7 +320,7 @@ void Display::_swichMode(displayMode_e newmode) { #ifndef HIDE_IP _showDialog(const_DlgVolume); #else - _showDialog(WiFi.localIP().toString().c_str()); + _showDialog(config.ipToStr(WiFi.localIP())); #endif _nums.setText(config.store.volume, numtxtFmt); } @@ -329,11 +345,11 @@ void Display::resetQueue(){ void Display::_drawPlaylist() { dsp.drawPlaylist(currentPlItem); - _setReturnTicker(30); + timekeeper.waitAndReturnPlayer(30); } void Display::_drawNextStationNum(uint16_t num) { - _setReturnTicker(30); + timekeeper.waitAndReturnPlayer(30); _meta.setText(config.stationByNum(num)); _nums.setText(num, "%d"); } @@ -374,9 +390,7 @@ void Display::_layoutChange(bool played){ } } } -#ifndef DSP_QUEUE_TICKS - #define DSP_QUEUE_TICKS 0 -#endif + void Display::loop() { if(_bootStep==0) { _pager.begin(); @@ -429,7 +443,7 @@ void Display::loop() { if(_weather) _weather->lock(!config.store.showweather); if(!config.store.showweather){ #ifndef HIDE_IP - if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt); + if(_volip) _volip->setText(config.ipToStr(WiFi.localIP()), iptxtFmt); #endif }else{ if(_weather) _weather->setText(const_getWeather); @@ -463,13 +477,19 @@ void Display::loop() { case DSP_START: _start(); break; case NEWIP: { #ifndef HIDE_IP - if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt); + if(_volip) _volip->setText(config.ipToStr(WiFi.localIP()), iptxtFmt); #endif break; } default: break; + + // check if there are more messages waiting in the Q, in this case break the loop() and go + // for another round to evict next message, do not waste time to redraw the screen, etc... + if (uxQueueMessagesWaiting(displayQueue)) + return; } } + dsp.loop(); #if I2S_DOUT==255 player.computeVUlevel(); @@ -561,7 +581,7 @@ void Display::_volume() { if(_voltxt) _voltxt->setText(config.store.volume, voltxtFmt); #endif if(_mode==VOL) { - _setReturnTicker(3); + timekeeper.waitAndReturnPlayer(3); _nums.setText(config.store.volume, numtxtFmt); } /*#ifdef USE_NEXTION diff --git a/yoRadio/src/core/display.h b/yoRadio/src/core/display.h index 6d2247a..cb3b14c 100644 --- a/yoRadio/src/core/display.h +++ b/yoRadio/src/core/display.h @@ -3,20 +3,18 @@ #include "options.h" #include "Arduino.h" -#include #include "config.h" #include "common.h" #include "../displays/dspcore.h" - - #if NEXTION_RX!=255 && NEXTION_TX!=255 #define USE_NEXTION #include "../displays/nextion.h" #endif +//static void loopDspTask(void * pvParameters); + #ifndef DUMMYDISPLAY - void loopDspTask(void * pvParameters); class Display { public: @@ -54,7 +52,6 @@ class Display { ClockWidget _clock; Page *_boot; TextWidget *_bootstring, *_volip, *_voltxt, *_rssi, *_bitrate; - Ticker _returnTicker; uint8_t _bootStep; void _time(bool redraw = false); void _apScreen(); @@ -68,7 +65,6 @@ class Display { void _showDialog(const char *title); void _buildPager(); void _bootScreen(); - void _setReturnTicker(uint8_t time_s); void _layoutChange(bool played); void _setRSSI(int rssi); }; @@ -98,6 +94,8 @@ class Display { bool deepsleep(){return true;} void wakeup(){} void printPLitem(uint8_t pos, const char* item){} + private: + void _createDspTask(); }; #endif diff --git a/yoRadio/src/core/mqtt.cpp b/yoRadio/src/core/mqtt.cpp index 497c83a..1eda8dd 100644 --- a/yoRadio/src/core/mqtt.cpp +++ b/yoRadio/src/core/mqtt.cpp @@ -9,7 +9,7 @@ AsyncMqttClient mqttClient; TimerHandle_t mqttReconnectTimer; -char topic[140], status[BUFLEN*3], vol[5], buf[20]; +char topic[100], status[BUFLEN+50]; void connectToMqtt() { mqttClient.connect(); @@ -25,8 +25,10 @@ void mqttInit() { connectToMqtt(); } +void zeroBuffer(){ memset(topic, 0, sizeof(topic)); memset(status, 0, sizeof(status)); } + void onMqttConnect(bool sessionPresent) { - memset(topic, 0, 140); + zeroBuffer(); sprintf(topic, "%s%s", MQTT_ROOT_TOPIC, "command"); mqttClient.subscribe(topic, 2); mqttPublishStatus(); @@ -36,13 +38,12 @@ void onMqttConnect(bool sessionPresent) { void mqttPublishStatus() { if(mqttClient.connected()){ - memset(topic, 0, 140); - memset(status, 0, BUFLEN*3); + zeroBuffer(); sprintf(topic, "%s%s", MQTT_ROOT_TOPIC, "status"); - char name[BUFLEN*2]; - char title[BUFLEN*2]; - config.escapeQuotes(config.station.name, name, sizeof(name)); - config.escapeQuotes(config.station.title, title, sizeof(name)); + char name[BUFLEN/2]; + char title[BUFLEN/2]; + config.escapeQuotes(config.station.name, name, sizeof(name)-10); + config.escapeQuotes(config.station.title, title, sizeof(title)-10); sprintf(status, "{\"status\": %d, \"station\": %d, \"name\": \"%s\", \"title\": \"%s\", \"on\": %d}", player.status()==PLAYING?1:0, config.lastStation(), name, title, config.store.dspon); mqttClient.publish(topic, 0, true, status); } @@ -50,17 +51,17 @@ void mqttPublishStatus() { void mqttPublishPlaylist() { if(mqttClient.connected()){ - memset(topic, 0, 140); - memset(status, 0, BUFLEN*3); + zeroBuffer(); sprintf(topic, "%s%s", MQTT_ROOT_TOPIC, "playlist"); - sprintf(status, "http://%s%s", WiFi.localIP().toString().c_str(), PLAYLIST_PATH); + sprintf(status, "http://%s%s", config.ipToStr(WiFi.localIP()), PLAYLIST_PATH); mqttClient.publish(topic, 0, true, status); } } void mqttPublishVolume(){ if(mqttClient.connected()){ - memset(topic, 0, 140); + zeroBuffer(); + char vol[5]; memset(vol, 0, 5); sprintf(topic, "%s%s", MQTT_ROOT_TOPIC, "volume"); sprintf(vol, "%d", config.store.volume); @@ -76,75 +77,64 @@ void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { if (len == 0) return; - memset(buf, 0, 20); - strlcpy(buf, payload, len+1); - if (strcmp(buf, "prev") == 0) { - player.prev(); + if(len<20){ + char buf[len+1]; + strncpy(buf, payload, len); + buf[len]='\0'; + if (strcmp(buf, "prev") == 0) { player.sendCommand({PR_PREV, 0}); return; } + if (strcmp(buf, "next") == 0) { player.sendCommand({PR_NEXT, 0}); return; } + if (strcmp(buf, "toggle") == 0) { player.sendCommand({PR_TOGGLE, 0}); return; } + if (strcmp(buf, "stop") == 0) { player.sendCommand({PR_STOP, 0}); return; } + if (strcmp(buf, "start") == 0 || strcmp(buf, "play") == 0) { player.sendCommand({PR_PLAY, config.lastStation()}); return; } + if (strcmp(buf, "boot") == 0 || strcmp(buf, "reboot") == 0) { ESP.restart(); return; } + if (strcmp(buf, "volm") == 0) { + player.stepVol(false); + return; + } + if (strcmp(buf, "volp") == 0) { + player.stepVol(true); + return; + } + if (strcmp(buf, "turnoff") == 0) { + uint8_t sst = config.store.smartstart; + config.setDspOn(0); + player.sendCommand({PR_STOP, 0}); + delay(100); + config.saveValue(&config.store.smartstart, sst); + return; + } + if (strcmp(buf, "turnon") == 0) { + config.setDspOn(1); + if (config.store.smartstart == 1) player.sendCommand({PR_PLAY, config.lastStation()}); + return; + } + int volume; + if ( sscanf(buf, "vol %d", &volume) == 1) { + if (volume < 0) volume = 0; + if (volume > 254) volume = 254; + player.setVol(volume); + return; + } + int sb; + if (sscanf(buf, "play %d", &sb) == 1 ) { + if (sb < 1) sb = 1; + uint16_t cs = config.playlistLength(); + if (sb >= cs) sb = cs; + player.sendCommand({PR_PLAY, (uint16_t)sb}); + return; + } + }else{ + if(len>MQTT_BURL_SIZE) return; + strncpy(player.burl, payload, len); + player.burl[len]='\0'; + player.sendCommand({PR_BURL, 0}); return; } - if (strcmp(buf, "next") == 0) { - player.next(); - return; - } - if (strcmp(buf, "toggle") == 0) { - player.toggle(); - return; - } - if (strcmp(buf, "stop") == 0) { - player.sendCommand({PR_STOP, 0}); - //telnet.info(); - return; - } - if (strcmp(buf, "start") == 0 || strcmp(buf, "play") == 0) { - player.sendCommand({PR_PLAY, config.lastStation()}); - return; - } - if (strcmp(buf, "boot") == 0 || strcmp(buf, "reboot") == 0) { - ESP.restart(); - return; - } - if (strcmp(buf, "volm") == 0) { - player.stepVol(false); - return; - } - if (strcmp(buf, "volp") == 0) { - player.stepVol(true); - return; - } - if (strcmp(buf, "turnoff") == 0) { - uint8_t sst = config.store.smartstart; - config.setDspOn(0); - player.sendCommand({PR_STOP, 0}); - //telnet.info(); - delay(100); - config.saveValue(&config.store.smartstart, sst); - return; - } - if (strcmp(buf, "turnon") == 0) { - config.setDspOn(1); - if (config.store.smartstart == 1) player.sendCommand({PR_PLAY, config.lastStation()}); - return; - } - int volume; - if ( sscanf(buf, "vol %d", &volume) == 1) { - if (volume < 0) volume = 0; - if (volume > 254) volume = 254; - player.setVol(volume); - return; - } - int sb; - if (sscanf(buf, "play %d", &sb) == 1 ) { - if (sb < 1) sb = 1; - uint16_t cs = config.playlistLength(); - if (sb >= cs) sb = cs; - player.sendCommand({PR_PLAY, (uint16_t)sb}); - return; - } - if (strstr(buf, "http")==buf){ + /*if (strstr(buf, "http")==0){ if(len+1>sizeof(player.burl)) return; strlcpy(player.burl, payload, len+1); return; - } + }*/ } #endif // #ifdef MQTT_ROOT_TOPIC diff --git a/yoRadio/src/core/mqtt.h b/yoRadio/src/core/mqtt.h index 074da59..bc76960 100644 --- a/yoRadio/src/core/mqtt.h +++ b/yoRadio/src/core/mqtt.h @@ -1,11 +1,9 @@ #ifndef mqtt_h #define mqtt_h #include "options.h" -#ifdef MQTT_ROOT_TOPIC -//#if __has_include("../../mqttoptions.h") -//#include "../../mqttoptions.h" -#include "../async-mqtt-client/AsyncMqttClient.h" +#ifdef MQTT_ROOT_TOPIC +#include "../async-mqtt-client/AsyncMqttClient.h" void mqttInit(); void connectToMqtt(); @@ -15,6 +13,7 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties void mqttPublishStatus(); void mqttPublishPlaylist(); void mqttPublishVolume(); +void zeroBuffer(); #endif // #ifdef MQTT_ROOT_TOPIC diff --git a/yoRadio/src/core/netserver.cpp b/yoRadio/src/core/netserver.cpp index e58366e..e808ca3 100644 --- a/yoRadio/src/core/netserver.cpp +++ b/yoRadio/src/core/netserver.cpp @@ -1,7 +1,6 @@ #include "netserver.h" #include -#include "config.h" #include "player.h" #include "telnet.h" #include "display.h" @@ -10,17 +9,10 @@ #include "mqtt.h" #include "controls.h" #include "commandhandler.h" +#include "timekeeper.h" #include #include - -#if USE_OTA -#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) -#include -#else -#include -#endif -#include -#endif +//#include #ifdef USE_SD #include "sdmanager.h" @@ -29,7 +21,7 @@ #define MIN_MALLOC 24112 #endif #ifndef NSQ_SEND_DELAY - #define NSQ_SEND_DELAY (TickType_t)100 //portMAX_DELAY? + #define NSQ_SEND_DELAY pdMS_TO_TICKS(100) //portMAX_DELAY? #endif //#define CORS_DEBUG //Enable CORS policy: 'Access-Control-Allow-Origin' (for testing) @@ -46,20 +38,19 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp bool shouldReboot = false; #ifdef MQTT_ROOT_TOPIC -Ticker mqttplaylistticker; +//Ticker mqttplaylistticker; bool mqttplaylistblock = false; void mqttplaylistSend() { mqttplaylistblock = true; - mqttplaylistticker.detach(); +// mqttplaylistticker.detach(); mqttPublishPlaylist(); mqttplaylistblock = false; } #endif char* updateError() { - static char ret[140] = {0}; - sprintf(ret, "Update failed with error (%d)
%s", (int)Update.getError(), Update.errorString()); - return ret; + sprintf(netserver.nsBuf, "Update failed with error (%d)
%s", (int)Update.getError(), Update.errorString()); + return netserver.nsBuf; } bool NetServer::begin(bool quiet) { @@ -67,6 +58,7 @@ bool NetServer::begin(bool quiet) { if(!quiet) Serial.print("##[BOOT]#\tnetserver.begin\t"); importRequest = IMDONE; irRecordEnable = false; + playerBufMax = psramInit()?300000:1600 * config.store.abuff; nsQueue = xQueueCreate( 20, sizeof( nsRequestParams_t ) ); while(nsQueue==NULL){;} @@ -82,43 +74,8 @@ bool NetServer::begin(bool quiet) { webserver.begin(); if(strlen(config.store.mdnsname)>0) MDNS.begin(config.store.mdnsname); - websocket.onEvent(onWsEvent); webserver.addHandler(&websocket); -#if USE_OTA - if(strlen(config.store.mdnsname)>0) - ArduinoOTA.setHostname(config.store.mdnsname); -#ifdef OTA_PASS - ArduinoOTA.setPassword(OTA_PASS); -#endif - ArduinoOTA - .onStart([]() { - display.putRequest(NEWMODE, UPDATING); - telnet.printf("Start OTA updating %s\n", ArduinoOTA.getCommand() == U_FLASH?"firmware":"filesystem"); - }) - .onEnd([]() { - telnet.printf("\nEnd OTA update, Rebooting...\n"); - }) - .onProgress([](unsigned int progress, unsigned int total) { - telnet.printf("Progress OTA: %u%%\r", (progress / (total / 100))); - }) - .onError([](ota_error_t error) { - telnet.printf("Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) { - telnet.printf("Auth Failed\n"); - } else if (error == OTA_BEGIN_ERROR) { - telnet.printf("Begin Failed\n"); - } else if (error == OTA_CONNECT_ERROR) { - telnet.printf("Connect Failed\n"); - } else if (error == OTA_RECEIVE_ERROR) { - telnet.printf("Receive Failed\n"); - } else if (error == OTA_END_ERROR) { - telnet.printf("End Failed\n"); - } - }); - ArduinoOTA.begin(); -#endif - if(!quiet) Serial.println("done"); return true; } @@ -152,6 +109,7 @@ void NetServer::chunkedHtmlPage(const String& contentType, AsyncWebServerRequest strlcpy(chunkedPathBuffer, path, sizeof(chunkedPathBuffer)-1); AsyncWebServerResponse *response; response = request->beginChunkedResponse(contentType, chunkedHtmlPageCallback); + response->addHeader("Cache-Control","max-age=31536000"); request->send(response); } @@ -181,13 +139,12 @@ const char *getFormat(BitrateFormat _format) { } } -char wsbuf[BUFLEN * 2]; void NetServer::processQueue(){ if(nsQueue==NULL) return; nsRequestParams_t request; if(xQueueReceive(nsQueue, &request, NS_QUEUE_TICKS)){ - memset(wsbuf, 0, BUFLEN * 2); uint8_t clientId = request.clientId; + wsBuf[0]='\0'; switch (request.type) { case PLAYLIST: getPlaylist(clientId); break; case PLAYLISTSAVED: { @@ -205,36 +162,46 @@ void NetServer::processQueue(){ } case GETACTIVE: { bool dbgact = false, nxtn=false; - String act = F("\"group_wifi\","); + //String act = F("\"group_wifi\","); + nsBuf[0]='\0'; + APPEND_GROUP("group_wifi"); if (network.status == CONNECTED) { - act += F("\"group_system\","); - if (BRIGHTNESS_PIN != 255 || DSP_CAN_FLIPPED || DSP_MODEL == DSP_NOKIA5110 || dbgact) act += F("\"group_display\","); + //act += F("\"group_system\","); + APPEND_GROUP("group_system"); + if (BRIGHTNESS_PIN != 255 || DSP_CAN_FLIPPED || DSP_MODEL == DSP_NOKIA5110 || dbgact) APPEND_GROUP("group_display"); #ifdef USE_NEXTION - act += F("\"group_nextion\","); - if (!SHOW_WEATHER || dbgact) act += F("\"group_weather\","); + APPEND_GROUP("group_nextion"); + if (!SHOW_WEATHER || dbgact) APPEND_GROUP("group_weather"); nxtn=true; #endif #if defined(LCD_I2C) || defined(DSP_OLED) - act += F("\"group_oled\","); + APPEND_GROUP("group_oled"); #endif - #ifndef HIDE_VU - act += F("\"group_vu\","); + #if !defined(HIDE_VU) && !defined(DUMMYDISPLAY) + APPEND_GROUP("group_vu"); + #endif + if (BRIGHTNESS_PIN != 255 || nxtn || dbgact) APPEND_GROUP("group_brightness"); + if (DSP_CAN_FLIPPED || dbgact) APPEND_GROUP("group_tft"); + if (TS_MODEL != TS_MODEL_UNDEFINED || dbgact) APPEND_GROUP("group_touch"); + if (DSP_MODEL == DSP_NOKIA5110) APPEND_GROUP("group_nokia"); + APPEND_GROUP("group_timezone"); + if (SHOW_WEATHER || dbgact) APPEND_GROUP("group_weather"); + APPEND_GROUP("group_controls"); + if (ENC_BTNL != 255 || ENC2_BTNL != 255 || dbgact) APPEND_GROUP("group_encoder"); + if (IR_PIN != 255 || dbgact) APPEND_GROUP("group_ir"); + if (!psramInit()) APPEND_GROUP("group_buffer"); + #if RTCSUPPORTED + APPEND_GROUP("group_rtc"); + #else + APPEND_GROUP("group_wortc"); #endif - if (BRIGHTNESS_PIN != 255 || nxtn || dbgact) act += F("\"group_brightness\","); - if (DSP_CAN_FLIPPED || dbgact) act += F("\"group_tft\","); - if (TS_MODEL != TS_MODEL_UNDEFINED || dbgact) act += F("\"group_touch\","); - if (DSP_MODEL == DSP_NOKIA5110) act += F("\"group_nokia\","); - act += F("\"group_timezone\","); - if (SHOW_WEATHER || dbgact) act += F("\"group_weather\","); - act += F("\"group_controls\","); - if (ENC_BTNL != 255 || ENC2_BTNL != 255 || dbgact) act += F("\"group_encoder\","); - if (IR_PIN != 255 || dbgact) act += F("\"group_ir\","); } - act = act.substring(0, act.length() - 1); - sprintf (wsbuf, "{\"act\":[%s]}", act.c_str()); + size_t len = strlen(nsBuf); + if (len > 0 && nsBuf[len - 1] == ',') nsBuf[len - 1] = '\0'; + + snprintf(wsBuf, sizeof(wsBuf), "{\"act\":[%s]}", nsBuf); break; } - //case STARTUP: sprintf (wsbuf, "{\"command\":\"startup\", \"payload\": {\"mode\":\"%s\", \"version\":\"%s\"}}", network.status == CONNECTED ? "player" : "ap", YOVERSION); break; case GETINDEX: { requestOnChange(STATION, clientId); requestOnChange(TITLE, clientId); @@ -249,15 +216,19 @@ void NetServer::processQueue(){ return; break; } - case GETSYSTEM: sprintf (wsbuf, "{\"sst\":%d,\"aif\":%d,\"vu\":%d,\"softr\":%d,\"vut\":%d,\"mdns\":\"%s\"}", + case GETSYSTEM: sprintf (wsBuf, "{\"sst\":%d,\"aif\":%d,\"vu\":%d,\"softr\":%d,\"vut\":%d,\"mdns\":\"%s\",\"ipaddr\":\"%s\", \"abuff\": %d, \"telnet\": %d, \"watchdog\": %d }", config.store.smartstart != 2, config.store.audioinfo, config.store.vumeter, config.store.softapdelay, config.vuThreshold, - config.store.mdnsname); + config.store.mdnsname, + config.ipToStr(WiFi.localIP()), + config.store.abuff, + config.store.telnet, + config.store.watchdog); break; - case GETSCREEN: sprintf (wsbuf, "{\"flip\":%d,\"inv\":%d,\"nump\":%d,\"tsf\":%d,\"tsd\":%d,\"dspon\":%d,\"br\":%d,\"con\":%d,\"scre\":%d,\"scrt\":%d,\"scrb\":%d,\"scrpe\":%d,\"scrpt\":%d,\"scrpb\":%d}", + case GETSCREEN: sprintf (wsBuf, "{\"flip\":%d,\"inv\":%d,\"nump\":%d,\"tsf\":%d,\"tsd\":%d,\"dspon\":%d,\"br\":%d,\"con\":%d,\"scre\":%d,\"scrt\":%d,\"scrb\":%d,\"scrpe\":%d,\"scrpt\":%d,\"scrpb\":%d}", config.store.flipscreen, config.store.invertdisplay, config.store.numplaylist, @@ -273,52 +244,55 @@ void NetServer::processQueue(){ config.store.screensaverPlayingTimeout, config.store.screensaverPlayingBlank); break; - case GETTIMEZONE: sprintf (wsbuf, "{\"tzh\":%d,\"tzm\":%d,\"sntp1\":\"%s\",\"sntp2\":\"%s\"}", + case GETTIMEZONE: sprintf (wsBuf, "{\"tzh\":%d,\"tzm\":%d,\"sntp1\":\"%s\",\"sntp2\":\"%s\", \"timeint\":%d,\"timeintrtc\":%d}", config.store.tzHour, config.store.tzMin, config.store.sntp1, - config.store.sntp2); + config.store.sntp2, + config.store.timeSyncInterval, + config.store.timeSyncIntervalRTC); break; - case GETWEATHER: sprintf (wsbuf, "{\"wen\":%d,\"wlat\":\"%s\",\"wlon\":\"%s\",\"wkey\":\"%s\"}", + case GETWEATHER: sprintf (wsBuf, "{\"wen\":%d,\"wlat\":\"%s\",\"wlon\":\"%s\",\"wkey\":\"%s\",\"wint\":%d}", config.store.showweather, config.store.weatherlat, config.store.weatherlon, - config.store.weatherkey); + config.store.weatherkey, + config.store.weatherSyncInterval); break; - case GETCONTROLS: sprintf (wsbuf, "{\"vols\":%d,\"enca\":%d,\"irtl\":%d,\"skipup\":%d}", + case GETCONTROLS: sprintf (wsBuf, "{\"vols\":%d,\"enca\":%d,\"irtl\":%d,\"skipup\":%d}", config.store.volsteps, config.store.encacc, config.store.irtlp, config.store.skipPlaylistUpDown); break; - case DSPON: sprintf (wsbuf, "{\"dspontrue\":%d}", 1); break; + case DSPON: sprintf (wsBuf, "{\"dspontrue\":%d}", 1); break; case STATION: requestOnChange(STATIONNAME, clientId); requestOnChange(ITEM, clientId); break; - case STATIONNAME: sprintf (wsbuf, "{\"payload\":[{\"id\":\"nameset\", \"value\": \"%s\"}]}", config.station.name); break; - case ITEM: sprintf (wsbuf, "{\"current\": %d}", config.lastStation()); break; - case TITLE: sprintf (wsbuf, "{\"payload\":[{\"id\":\"meta\", \"value\": \"%s\"}]}", config.station.title); telnet.printf("##CLI.META#: %s\n> ", config.station.title); break; - case VOLUME: sprintf (wsbuf, "{\"payload\":[{\"id\":\"volume\", \"value\": %d}]}", config.store.volume); telnet.printf("##CLI.VOL#: %d\n", config.store.volume); break; - case NRSSI: sprintf (wsbuf, "{\"payload\":[{\"id\":\"rssi\", \"value\": %d}]}", rssi); /*rssi = 255;*/ break; - case SDPOS: sprintf (wsbuf, "{\"sdpos\": %d,\"sdend\": %d,\"sdtpos\": %d,\"sdtend\": %d}", + case STATIONNAME: sprintf (wsBuf, "{\"payload\":[{\"id\":\"nameset\", \"value\": \"%s\"}]}", config.station.name); break; + case ITEM: sprintf (wsBuf, "{\"current\": %d}", config.lastStation()); break; + case TITLE: sprintf (wsBuf, "{\"payload\":[{\"id\":\"meta\", \"value\": \"%s\"}]}", config.station.title); telnet.printf("##CLI.META#: %s\n> ", config.station.title); break; + case VOLUME: sprintf (wsBuf, "{\"payload\":[{\"id\":\"volume\", \"value\": %d}]}", config.store.volume); telnet.printf("##CLI.VOL#: %d\n", config.store.volume); break; + case NRSSI: sprintf (wsBuf, "{\"payload\":[{\"id\":\"rssi\", \"value\": %d}, {\"id\":\"heap\", \"value\": %d}]}", rssi, (player.isRunning() && config.store.audioinfo)?(int)(100*player.inBufferFilled()/playerBufMax):0); /*rssi = 255;*/ break; + case SDPOS: sprintf (wsBuf, "{\"sdpos\": %lu,\"sdend\": %lu,\"sdtpos\": %lu,\"sdtend\": %lu}", player.getFilePos(), player.getFileSize(), player.getAudioCurrentTime(), player.getAudioFileDuration()); break; - case SDLEN: sprintf (wsbuf, "{\"sdmin\": %d,\"sdmax\": %d}", player.sd_min, player.sd_max); break; - case SDSNUFFLE: sprintf (wsbuf, "{\"snuffle\": %d}", config.store.sdsnuffle); break; - case BITRATE: sprintf (wsbuf, "{\"payload\":[{\"id\":\"bitrate\", \"value\": %d}, {\"id\":\"fmt\", \"value\": \"%s\"}]}", config.station.bitrate, getFormat(config.configFmt)); break; - case MODE: sprintf (wsbuf, "{\"payload\":[{\"id\":\"playerwrap\", \"value\": \"%s\"}]}", player.status() == PLAYING ? "playing" : "stopped"); telnet.info(); break; - case EQUALIZER: sprintf (wsbuf, "{\"payload\":[{\"id\":\"bass\", \"value\": %d}, {\"id\": \"middle\", \"value\": %d}, {\"id\": \"trebble\", \"value\": %d}]}", config.store.bass, config.store.middle, config.store.trebble); break; - case BALANCE: sprintf (wsbuf, "{\"payload\":[{\"id\": \"balance\", \"value\": %d}]}", config.store.balance); break; - case SDINIT: sprintf (wsbuf, "{\"sdinit\": %d}", SDC_CS!=255); break; - case GETPLAYERMODE: sprintf (wsbuf, "{\"playermode\": \"%s\"}", config.getMode()==PM_SDCARD?"modesd":"modeweb"); break; + case SDLEN: sprintf (wsBuf, "{\"sdmin\": %lu,\"sdmax\": %lu}", player.sd_min, player.sd_max); break; + case SDSNUFFLE: sprintf (wsBuf, "{\"snuffle\": %d}", config.store.sdsnuffle); break; + case BITRATE: sprintf (wsBuf, "{\"payload\":[{\"id\":\"bitrate\", \"value\": %d}, {\"id\":\"fmt\", \"value\": \"%s\"}]}", config.station.bitrate, getFormat(config.configFmt)); break; + case MODE: sprintf (wsBuf, "{\"payload\":[{\"id\":\"playerwrap\", \"value\": \"%s\"}]}", player.status() == PLAYING ? "playing" : "stopped"); telnet.info(); break; + case EQUALIZER: sprintf (wsBuf, "{\"payload\":[{\"id\":\"bass\", \"value\": %d}, {\"id\": \"middle\", \"value\": %d}, {\"id\": \"trebble\", \"value\": %d}]}", config.store.bass, config.store.middle, config.store.trebble); break; + case BALANCE: sprintf (wsBuf, "{\"payload\":[{\"id\": \"balance\", \"value\": %d}]}", config.store.balance); break; + case SDINIT: sprintf (wsBuf, "{\"sdinit\": %d}", SDC_CS!=255); break; + case GETPLAYERMODE: sprintf (wsBuf, "{\"playermode\": \"%s\"}", config.getMode()==PM_SDCARD?"modesd":"modeweb"); break; #ifdef USE_SD case CHANGEMODE: config.changeMode(config.newConfigMode); return; break; #endif default: break; } - if (strlen(wsbuf) > 0) { - if (clientId == 0) { websocket.textAll(wsbuf); }else{ websocket.text(clientId, wsbuf); } + if (strlen(wsBuf) > 0) { + if (clientId == 0) { websocket.textAll(wsBuf); }else{ websocket.text(clientId, wsBuf); } #ifdef MQTT_ROOT_TOPIC if (clientId == 0 && (request.type == STATION || request.type == ITEM || request.type == TITLE || request.type == MODE)) mqttPublishStatus(); if (clientId == 0 && request.type == VOLUME) mqttPublishVolume(); @@ -341,22 +315,19 @@ void NetServer::loop() { default: break; } processQueue(); -#if USE_OTA - ArduinoOTA.handle(); -#endif } #if IR_PIN!=255 void NetServer::irToWs(const char* protocol, uint64_t irvalue) { - char buf[BUFLEN] = { 0 }; - sprintf (buf, "{\"ircode\": %llu, \"protocol\": \"%s\"}", irvalue, protocol); - websocket.textAll(buf); + wsBuf[0]='\0'; + sprintf (wsBuf, "{\"ircode\": %llu, \"protocol\": \"%s\"}", irvalue, protocol); + websocket.textAll(wsBuf); } void NetServer::irValsToWs() { if (!irRecordEnable) return; - char buf[BUFLEN] = { 0 }; - sprintf (buf, "{\"irvals\": [%llu, %llu, %llu]}", config.ircodes.irVals[config.irindex][0], config.ircodes.irVals[config.irindex][1], config.ircodes.irVals[config.irindex][2]); - websocket.textAll(buf); + wsBuf[0]='\0'; + sprintf (wsBuf, "{\"irvals\": [%llu, %llu, %llu]}", config.ircodes.irVals[config.irindex][0], config.ircodes.irVals[config.irindex][1], config.ircodes.irVals[config.irindex][2]); + websocket.textAll(wsBuf); } #endif @@ -364,32 +335,36 @@ void NetServer::onWsMessage(void *arg, uint8_t *data, size_t len, uint8_t client AwsFrameInfo *info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { data[len] = 0; - char comnd[65], val[65]; - if (config.parseWsCommand((const char*)data, comnd, val, 65)) { - if (strcmp(comnd, "trebble") == 0) { - int8_t valb = atoi(val); + if (config.parseWsCommand((const char*)data, _wscmd, _wsval, 65)) { + if (strcmp(_wscmd, "ping") == 0) { + websocket.text(clientId, "{\"pong\": 1}"); + return; + } + if (strcmp(_wscmd, "trebble") == 0) { + int8_t valb = atoi(_wsval); config.setTone(config.store.bass, config.store.middle, valb); return; } - if (strcmp(comnd, "middle") == 0) { - int8_t valb = atoi(val); + if (strcmp(_wscmd, "middle") == 0) { + int8_t valb = atoi(_wsval); config.setTone(config.store.bass, valb, config.store.trebble); return; } - if (strcmp(comnd, "bass") == 0) { - int8_t valb = atoi(val); + if (strcmp(_wscmd, "bass") == 0) { + int8_t valb = atoi(_wsval); config.setTone(valb, config.store.middle, config.store.trebble); return; } - if (strcmp(comnd, "submitplaylistdone") == 0) { + if (strcmp(_wscmd, "submitplaylistdone") == 0) { #ifdef MQTT_ROOT_TOPIC - mqttplaylistticker.attach(5, mqttplaylistSend); + //mqttplaylistticker.attach(5, mqttplaylistSend); + timekeeper.waitAndDo(5, mqttplaylistSend); #endif if (player.isRunning()) player.sendCommand({PR_PLAY, -config.lastStation()}); return; } - if(cmd.exec(comnd, val, clientId)){ + if(cmd.exec(_wscmd, _wsval, clientId)){ return; } } @@ -397,9 +372,8 @@ void NetServer::onWsMessage(void *arg, uint8_t *data, size_t len, uint8_t client } void NetServer::getPlaylist(uint8_t clientId) { - char buf[160] = {0}; - sprintf(buf, "{\"file\": \"http://%s%s\"}", WiFi.localIP().toString().c_str(), PLAYLIST_PATH); - if (clientId == 0) { websocket.textAll(buf); } else { websocket.text(clientId, buf); } + sprintf(nsBuf, "{\"file\": \"http://%s%s\"}", config.ipToStr(WiFi.localIP()), PLAYLIST_PATH); + if (clientId == 0) { websocket.textAll(nsBuf); } else { websocket.text(clientId, nsBuf); } } int NetServer::_readPlaylistLine(File &file, char * line, size_t size){ @@ -413,27 +387,28 @@ int NetServer::_readPlaylistLine(File &file, char * line, size_t size){ bool NetServer::importPlaylist() { if(config.getMode()==PM_SDCARD) return false; + //player.sendCommand({PR_STOP, 0}); File tempfile = SPIFFS.open(TMP_PATH, "r"); if (!tempfile) { return false; } - char sName[BUFLEN], sUrl[BUFLEN], linePl[BUFLEN*3];; + char linePl[BUFLEN*3]; int sOvol; _readPlaylistLine(tempfile, linePl, sizeof(linePl)-1); - if (config.parseCSV(linePl, sName, sUrl, sOvol)) { + if (config.parseCSV(linePl, nsBuf, nsBuf2, sOvol)) { tempfile.close(); SPIFFS.rename(TMP_PATH, PLAYLIST_PATH); requestOnChange(PLAYLISTSAVED, 0); return true; } - if (config.parseJSON(linePl, sName, sUrl, sOvol)) { + if (config.parseJSON(linePl, nsBuf, nsBuf2, sOvol)) { File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w"); - snprintf(linePl, sizeof(linePl)-1, "%s\t%s\t%d", sName, sUrl, 0); + snprintf(linePl, sizeof(linePl)-1, "%s\t%s\t%d", nsBuf, nsBuf2, 0); playlistfile.println(linePl); while (tempfile.available()) { _readPlaylistLine(tempfile, linePl, sizeof(linePl)-1); - if (config.parseJSON(linePl, sName, sUrl, sOvol)) { - snprintf(linePl, sizeof(linePl)-1, "%s\t%s\t%d", sName, sUrl, 0); + if (config.parseJSON(linePl, nsBuf, nsBuf2, sOvol)) { + snprintf(linePl, sizeof(linePl)-1, "%s\t%s\t%d", nsBuf, nsBuf2, 0); playlistfile.println(linePl); } } @@ -461,11 +436,12 @@ void NetServer::resetQueue(){ if(nsQueue!=NULL) xQueueReset(nsQueue); } -int freeSpace; void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + static int freeSpace = 0; if(request->url()=="/upload"){ if (!index) { if(filename!="tempwifi.csv"){ + //player.sendCommand({PR_STOP, 0}); if(SPIFFS.exists(PLAYLIST_PATH)) SPIFFS.remove(PLAYLIST_PATH); if(SPIFFS.exists(INDEX_PATH)) SPIFFS.remove(INDEX_PATH); if(SPIFFS.exists(PLAYLIST_SD_PATH)) SPIFFS.remove(PLAYLIST_SD_PATH); @@ -473,6 +449,8 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, } freeSpace = (float)SPIFFS.totalBytes()/100*68-SPIFFS.usedBytes(); request->_tempFile = SPIFFS.open(TMP_PATH , "w"); + }else{ + } if (len) { if(freeSpace>index+len){ @@ -481,6 +459,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, } if (final) { request->_tempFile.close(); + freeSpace = 0; } }else if(request->url()=="/update"){ if (!index) { @@ -510,6 +489,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, }else{ // "/webboard" DBGVB("File: %s, size:%u bytes, index: %u, final: %s\n", filename.c_str(), len, index, final?"true":"false"); if (!index) { + player.sendCommand({PR_STOP, 0}); String spath = "/www/"; if(filename=="playlist.csv" || filename=="wifi.csv") spath = "/data/"; request->_tempFile = SPIFFS.open(spath + filename , "w"); @@ -526,8 +506,8 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { - case WS_EVT_CONNECT: /*netserver.requestOnChange(STARTUP, client->id()); */if (config.store.audioinfo) Serial.printf("[WEBSOCKET] client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); break; - case WS_EVT_DISCONNECT: if (config.store.audioinfo) Serial.printf("[WEBSOCKET] client #%u disconnected\n", client->id()); break; + case WS_EVT_CONNECT: /*netserver.requestOnChange(STARTUP, client->id()); */if (config.store.audioinfo) Serial.printf("[WEBSOCKET] client #%lu connected from %s\n", client->id(), config.ipToStr(client->remoteIP())); break; + case WS_EVT_DISCONNECT: if (config.store.audioinfo) Serial.printf("[WEBSOCKET] client #%lu disconnected\n", client->id()); break; case WS_EVT_DATA: netserver.onWsMessage(arg, data, len, client->id()); break; case WS_EVT_PONG: case WS_EVT_ERROR: @@ -548,7 +528,7 @@ void handleNotFound(AsyncWebServerRequest * request) { if(request->url()=="/emergency") { request->send_P(200, "text/html", emergency_form); return; } if(request->method() == HTTP_POST && request->url()=="/webboard" && config.emptyFS) { request->redirect("/"); ESP.restart(); return; } if (request->method() == HTTP_GET) { - DBGVB("[%s] client ip=%s request of %s", __func__, request->client()->remoteIP().toString().c_str(), request->url().c_str()); + DBGVB("[%s] client ip=%s request of %s", __func__, config.ipToStr(request->client()->remoteIP()), request->url().c_str()); if (strcmp(request->url().c_str(), PLAYLIST_PATH) == 0 || strcmp(request->url().c_str(), SSIDS_PATH) == 0 || strcmp(request->url().c_str(), INDEX_PATH) == 0 || @@ -595,13 +575,15 @@ void handleNotFound(AsyncWebServerRequest * request) { return; } if (request->url() == "/variables.js") { - char varjsbuf[BUFLEN]; - sprintf (varjsbuf, "var yoVersion='%s';\nvar formAction='%s';\nvar playMode='%s';\n", YOVERSION, (network.status == CONNECTED && !config.emptyFS)?"webboard":"", (network.status == CONNECTED)?"player":"ap"); - request->send(200, "text/html", varjsbuf); + sprintf (netserver.nsBuf, "var yoVersion='%s';\nvar formAction='%s';\nvar playMode='%s';\n", YOVERSION, (network.status == CONNECTED && !config.emptyFS)?"webboard":"", (network.status == CONNECTED)?"player":"ap"); + request->send(200, "text/html", netserver.nsBuf); return; } if (strcmp(request->url().c_str(), "/settings.html") == 0 || strcmp(request->url().c_str(), "/update.html") == 0 || strcmp(request->url().c_str(), "/ir.html") == 0){ - request->send_P(200, "text/html", index_html); + //request->send_P(200, "text/html", index_html); + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html); + response->addHeader("Cache-Control","max-age=31536000"); + request->send(response); return; } if (request->method() == HTTP_GET && request->url() == "/webboard") { @@ -618,11 +600,10 @@ void handleIndex(AsyncWebServerRequest * request) { if(request->url()=="/" && request->method() == HTTP_GET ) { request->send_P(200, "text/html", emptyfs_html); return; } if(request->url()=="/" && request->method() == HTTP_POST) { if(request->arg("ssid")!="" && request->arg("pass")!=""){ - char buf[BUFLEN]; - memset(buf, 0, BUFLEN); - snprintf(buf, BUFLEN, "%s\t%s", request->arg("ssid").c_str(), request->arg("pass").c_str()); + netserver.nsBuf[0]='\0'; + snprintf(netserver.nsBuf, sizeof(netserver.nsBuf), "%s\t%s", request->arg("ssid").c_str(), request->arg("pass").c_str()); request->redirect("/"); - config.saveWifiFromNextion(buf); + config.saveWifiFromNextion(netserver.nsBuf); return; } request->redirect("/"); @@ -641,7 +622,12 @@ void handleIndex(AsyncWebServerRequest * request) { } #endif if (strcmp(request->url().c_str(), "/") == 0 && request->params() == 0) { - if(network.status == CONNECTED) request->send_P(200, "text/html", index_html); else request->redirect("/settings.html"); + if(network.status == CONNECTED) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html); + response->addHeader("Cache-Control","max-age=31536000"); + request->send(response); + //request->send_P(200, "text/html", index_html); + } else request->redirect("/settings.html"); return; } if(network.status == CONNECTED){ diff --git a/yoRadio/src/core/netserver.h b/yoRadio/src/core/netserver.h index a6bbba0..1ab21a6 100644 --- a/yoRadio/src/core/netserver.h +++ b/yoRadio/src/core/netserver.h @@ -1,9 +1,11 @@ #ifndef netserver_h #define netserver_h #include "Arduino.h" - +#include "config.h" #include "../AsyncWebServer/ESPAsyncWebServer.h" +#define APPEND_GROUP(name) strcat(nsBuf, "\"" name "\",") + enum requestType_e : uint8_t { PLAYLIST=1, STATION=2, STATIONNAME=3, ITEM=4, TITLE=5, VOLUME=6, NRSSI=7, BITRATE=8, MODE=9, EQUALIZER=10, BALANCE=11, PLAYLISTSAVED=12, STARTUP=13, GETINDEX=14, GETACTIVE=15, GETSYSTEM=16, GETSCREEN=17, GETTIMEZONE=18, GETWEATHER=19, GETCONTROLS=20, DSPON=21, SDPOS=22, SDLEN=23, SDSNUFFLE=24, SDINIT=25, GETPLAYERMODE=26, CHANGEMODE=27 }; enum import_e : uint8_t { IMDONE=0, IMPL=1, IMWIFI=2 }; const char emptyfs_html[] PROGMEM = R"( @@ -74,6 +76,7 @@ const char index_html[] PROGMEM = R"(
+
)"; @@ -96,6 +99,7 @@ class NetServer { import_e importRequest; bool resumePlay; char chunkedPathBuffer[40]; + char nsBuf[BUFLEN], nsBuf2[BUFLEN]; public: NetServer() {}; bool begin(bool quiet=false); @@ -110,11 +114,14 @@ class NetServer { void irToWs(const char* protocol, uint64_t irvalue); void irValsToWs(); #endif - void resetQueue(); + void resetQueue(); private: requestType_e request; QueueHandle_t nsQueue; + char _wscmd[65], _wsval[65]; + char wsBuf[BUFLEN*2]; int rssi; + uint32_t playerBufMax; void getPlaylist(uint8_t clientId); bool importPlaylist(); static size_t chunkedHtmlPageCallback(uint8_t* buffer, size_t maxLen, size_t index); diff --git a/yoRadio/src/core/network.cpp b/yoRadio/src/core/network.cpp index 20d4b56..028112b 100644 --- a/yoRadio/src/core/network.cpp +++ b/yoRadio/src/core/network.cpp @@ -6,98 +6,17 @@ #include "netserver.h" #include "player.h" #include "mqtt.h" +#include "timekeeper.h" #ifndef WIFI_ATTEMPTS #define WIFI_ATTEMPTS 16 #endif +#ifndef SEARCH_WIFI_CORE_ID + #define SEARCH_WIFI_CORE_ID 0 +#endif MyNetwork network; -TaskHandle_t syncTaskHandle; -//TaskHandle_t reconnectTaskHandle; - -bool getWeather(char *wstr); -void doSync(void * pvParameters); - -void ticks() { - if(!display.ready()) return; //waiting for SD is ready - pm.on_ticker(); - static const uint16_t weatherSyncInterval=1800; - //static const uint16_t weatherSyncIntervalFail=10; -#if RTCSUPPORTED - static const uint32_t timeSyncInterval=86400; - static uint32_t timeSyncTicks = 0; -#else - static const uint16_t timeSyncInterval=3600; - static uint16_t timeSyncTicks = 0; -#endif - static uint16_t weatherSyncTicks = 0; - static bool divrssi; - timeSyncTicks++; - weatherSyncTicks++; - divrssi = !divrssi; - if(network.status == CONNECTED){ - if(network.forceTimeSync || network.forceWeather){ - xTaskCreatePinnedToCore(doSync, "doSync", 1024 * 4, NULL, 0, &syncTaskHandle, 0); - } - if(timeSyncTicks >= timeSyncInterval){ - timeSyncTicks=0; - network.forceTimeSync = true; - } - if(weatherSyncTicks >= weatherSyncInterval){ - weatherSyncTicks=0; - network.forceWeather = true; - } - } -#ifndef DSP_LCD - if(config.store.screensaverEnabled && display.mode()==PLAYER && !player.isRunning()){ - config.screensaverTicks++; - if(config.screensaverTicks > config.store.screensaverTimeout+SCREENSAVERSTARTUPDELAY){ - if(config.store.screensaverBlank){ - display.putRequest(NEWMODE, SCREENBLANK); - }else{ - display.putRequest(NEWMODE, SCREENSAVER); - } - } - } - if(config.store.screensaverPlayingEnabled && display.mode()==PLAYER && player.isRunning()){ - config.screensaverPlayingTicks++; - if(config.screensaverPlayingTicks > config.store.screensaverPlayingTimeout*60+SCREENSAVERSTARTUPDELAY){ - if(config.store.screensaverPlayingBlank){ - display.putRequest(NEWMODE, SCREENBLANK); - }else{ - display.putRequest(NEWMODE, SCREENSAVER); - } - } - } -#endif -#if RTCSUPPORTED - if(config.isRTCFound()){ - rtc.getTime(&network.timeinfo); - mktime(&network.timeinfo); - display.putRequest(CLOCK); - } -#else - if(network.timeinfo.tm_year>100 || network.status == SDREADY) { - network.timeinfo.tm_sec++; - mktime(&network.timeinfo); - display.putRequest(CLOCK); - } -#endif - if(player.isRunning() && config.getMode()==PM_SDCARD) netserver.requestOnChange(SDPOS, 0); - if(divrssi) { - if(network.status == CONNECTED){ - netserver.setRSSI(WiFi.RSSI()); - netserver.requestOnChange(NRSSI, 0); - display.putRequest(DSPRSSI, netserver.getRSSI()); - } -#ifdef USE_SD - if(display.mode()!=SDCHANGE) player.sendCommand({PR_CHECKSD, 0}); -#endif - player.sendCommand({PR_VUTONUS, 0}); - } -} - void MyNetwork::WiFiReconnected(WiFiEvent_t event, WiFiEventInfo_t info){ network.beginReconnect = false; player.lockOutput = false; @@ -152,6 +71,8 @@ bool MyNetwork::wifiBegin(bool silent){ Serial.print("##[BOOT]#\t"); display.putRequest(BOOTSTRING, ls); } + WiFi.disconnect(true, true); //disconnect & erase internal credentials https://github.com/e2002/yoradio/pull/164/commits/89d8b4450dde99cd7930b84bb14d81dab920b879 + delay(100); WiFi.begin(config.ssids[ls].ssid, config.ssids[ls].password); while (WiFi.status() != WL_CONNECTED) { if(!silent) Serial.print("."); @@ -180,7 +101,7 @@ bool MyNetwork::wifiBegin(bool silent){ void searchWiFi(void * pvParameters){ if(!network.wifiBegin(true)){ delay(10000); - xTaskCreatePinnedToCore(searchWiFi, "searchWiFi", 1024 * 4, NULL, 0, NULL, 0); + xTaskCreatePinnedToCore(searchWiFi, "searchWiFi", 1024 * 4, NULL, 0, NULL, SEARCH_WIFI_CORE_ID); }else{ network.status = CONNECTED; netserver.begin(true); @@ -196,8 +117,6 @@ void searchWiFi(void * pvParameters){ void MyNetwork::begin() { BOOTLOG("network.begin"); config.initNetwork(); - ctimer.detach(); - forceTimeSync = forceWeather = true; if (config.ssidsCount == 0 || DBGAP) { raiseSoftAP(); return; @@ -213,7 +132,7 @@ void MyNetwork::begin() { setWifiParams(); }else{ status = SDREADY; - xTaskCreatePinnedToCore(searchWiFi, "searchWiFi", 1024 * 4, NULL, 0, NULL, 0); + xTaskCreatePinnedToCore(searchWiFi, "searchWiFi", 1024 * 4, NULL, 0, NULL, SEARCH_WIFI_CORE_ID); } Serial.println("##[BOOT]#\tdone"); @@ -226,7 +145,6 @@ void MyNetwork::begin() { display.putRequest(CLOCK); } #endif - ctimer.attach(1, ticks); if (network_on_connect) network_on_connect(); pm.on_connect(); } @@ -236,16 +154,11 @@ void MyNetwork::setWifiParams(){ WiFi.onEvent(WiFiReconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); WiFi.onEvent(WiFiLostConnection, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); weatherBuf=NULL; - trueWeather = false; #if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER) weatherBuf = (char *) malloc(sizeof(char) * WEATHER_STRING_L); memset(weatherBuf, 0, WEATHER_STRING_L); #endif - if(strlen(config.store.sntp1)>0 && strlen(config.store.sntp2)>0){ - configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1, config.store.sntp2); - }else if(strlen(config.store.sntp1)>0){ - configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1); - } + //config.setTimeConf(); //?? } void MyNetwork::requestTimeSync(bool withTelnetOutput, uint8_t clientId) { @@ -275,209 +188,9 @@ void MyNetwork::raiseSoftAP() { BOOTLOG("************************************************"); status = SOFT_AP; if(config.store.softapdelay>0) - rtimer.once(config.store.softapdelay*60, rebootTime); + timekeeper.waitAndDo(config.store.softapdelay*60, rebootTime); } void MyNetwork::requestWeatherSync(){ display.putRequest(NEWWEATHER); } - - -void doSync( void * pvParameters ) { - static uint8_t tsFailCnt = 0; - //static uint8_t wsFailCnt = 0; - if(network.forceTimeSync){ - network.forceTimeSync = false; - if(getLocalTime(&network.timeinfo)){ - tsFailCnt = 0; - network.forceTimeSync = false; - mktime(&network.timeinfo); - display.putRequest(CLOCK); - network.requestTimeSync(true); - #if RTCSUPPORTED - if (config.isRTCFound()) rtc.setTime(&network.timeinfo); - #endif - }else{ - if(tsFailCnt<4){ - network.forceTimeSync = true; - tsFailCnt++; - }else{ - network.forceTimeSync = false; - tsFailCnt=0; - } - } - } - if(network.weatherBuf && (strlen(config.store.weatherkey)!=0 && config.store.showweather) && network.forceWeather){ - network.forceWeather = false; - network.trueWeather=getWeather(network.weatherBuf); - } - vTaskDelete( NULL ); -} - -bool getWeather(char *wstr) { -#if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER) - WiFiClient client; - const char* host = "api.openweathermap.org"; - - if (!client.connect(host, 80)) { - Serial.println("##WEATHER###: connection failed"); - return false; - } - char httpget[250] = {0}; - sprintf(httpget, "GET /data/2.5/weather?lat=%s&lon=%s&units=%s&lang=%s&appid=%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", config.store.weatherlat, config.store.weatherlon, weatherUnits, weatherLang, config.store.weatherkey, host); - client.print(httpget); - unsigned long timeout = millis(); - while (client.available() == 0) { - if (millis() - timeout > 2000UL) { - Serial.println("##WEATHER###: client available timeout !"); - client.stop(); - return false; - } - } - timeout = millis(); - String line = ""; - if (client.connected()) { - while (client.available()) - { - line = client.readStringUntil('\n'); - if (strstr(line.c_str(), "\"temp\"") != NULL) { - client.stop(); - break; - } - if ((millis() - timeout) > 500) - { - client.stop(); - Serial.println("##WEATHER###: client read timeout !"); - return false; - } - } - } - if (strstr(line.c_str(), "\"temp\"") == NULL) { - Serial.println("##WEATHER###: weather not found !"); - return false; - } - char *tmpe; - char *tmps; - char *tmpc; - const char* cursor = line.c_str(); - char desc[120], temp[20], hum[20], press[20], icon[5]; - - tmps = strstr(cursor, "\"description\":\""); - if (tmps == NULL) { Serial.println("##WEATHER###: description not found !"); return false;} - tmps += 15; - tmpe = strstr(tmps, "\",\""); - if (tmpe == NULL) { Serial.println("##WEATHER###: description not found !"); return false;} - strlcpy(desc, tmps, tmpe - tmps + 1); - cursor = tmpe + 2; - - // "ясно","icon":"01d"}], - tmps = strstr(cursor, "\"icon\":\""); - if (tmps == NULL) { Serial.println("##WEATHER###: icon not found !"); return false;} - tmps += 8; - tmpe = strstr(tmps, "\"}"); - if (tmpe == NULL) { Serial.println("##WEATHER###: icon not found !"); return false;} - strlcpy(icon, tmps, tmpe - tmps + 1); - cursor = tmpe + 2; - - tmps = strstr(cursor, "\"temp\":"); - if (tmps == NULL) { Serial.println("##WEATHER###: temp not found !"); return false;} - tmps += 7; - tmpe = strstr(tmps, ",\""); - if (tmpe == NULL) { Serial.println("##WEATHER###: temp not found !"); return false;} - strlcpy(temp, tmps, tmpe - tmps + 1); - cursor = tmpe + 1; - float tempf = atof(temp); - - tmps = strstr(cursor, "\"feels_like\":"); - if (tmps == NULL) { Serial.println("##WEATHER###: feels_like not found !"); return false;} - tmps += 13; - tmpe = strstr(tmps, ",\""); - if (tmpe == NULL) { Serial.println("##WEATHER###: feels_like not found !"); return false;} - strlcpy(temp, tmps, tmpe - tmps + 1); - cursor = tmpe + 2; - float tempfl = atof(temp); (void)tempfl; - - tmps = strstr(cursor, "\"pressure\":"); - if (tmps == NULL) { Serial.println("##WEATHER###: pressure not found !"); return false;} - tmps += 11; - tmpe = strstr(tmps, ",\""); - if (tmpe == NULL) { Serial.println("##WEATHER###: pressure not found !"); return false;} - strlcpy(press, tmps, tmpe - tmps + 1); - cursor = tmpe + 2; - int pressi = (float)atoi(press) / 1.333; - - tmps = strstr(cursor, "humidity\":"); - if (tmps == NULL) { Serial.println("##WEATHER###: humidity not found !"); return false;} - tmps += 10; - tmpe = strstr(tmps, ",\""); - tmpc = strstr(tmps, "}"); - if (tmpe == NULL) { Serial.println("##WEATHER###: humidity not found !"); return false;} - strlcpy(hum, tmps, tmpe - tmps + (tmpc>tmpe?1:0)); - - tmps = strstr(cursor, "\"grnd_level\":"); - bool grnd_level_pr = (tmps != NULL); - if(grnd_level_pr){ - tmps += 13; - tmpe = strstr(tmps, ",\""); - if (tmpe == NULL) { Serial.println("##WEATHER###: grnd_level not found !"); return false;} - strlcpy(press, tmps, tmpe - tmps + 1); - cursor = tmpe + 2; - pressi = (float)atoi(press) / 1.333; - } - - tmps = strstr(cursor, "\"speed\":"); - if (tmps == NULL) { Serial.println("##WEATHER###: wind speed not found !"); return false;} - tmps += 8; - tmpe = strstr(tmps, ",\""); - if (tmpe == NULL) { Serial.println("##WEATHER###: wind speed not found !"); return false;} - strlcpy(temp, tmps, tmpe - tmps + 1); - cursor = tmpe + 1; - float wind_speed = atof(temp); (void)wind_speed; - - tmps = strstr(cursor, "\"deg\":"); - if (tmps == NULL) { Serial.println("##WEATHER###: wind deg not found !"); return false;} - tmps += 6; - tmpe = strstr(tmps, ",\""); - if (tmpe == NULL) { Serial.println("##WEATHER###: wind deg not found !"); return false;} - strlcpy(temp, tmps, tmpe - tmps + 1); - cursor = tmpe + 1; - int wind_deg = atof(temp)/22.5; - if(wind_deg<0) wind_deg = 16+wind_deg; - - - #ifdef USE_NEXTION - nextion.putcmdf("press_txt.txt=\"%dmm\"", pressi); - nextion.putcmdf("hum_txt.txt=\"%d%%\"", atoi(hum)); - char cmd[30]; - snprintf(cmd, sizeof(cmd)-1,"temp_txt.txt=\"%.1f\"", tempf); - nextion.putcmd(cmd); - int iconofset; - if(strstr(icon,"01")!=NULL) iconofset = 0; - else if(strstr(icon,"02")!=NULL) iconofset = 1; - else if(strstr(icon,"03")!=NULL) iconofset = 2; - else if(strstr(icon,"04")!=NULL) iconofset = 3; - else if(strstr(icon,"09")!=NULL) iconofset = 4; - else if(strstr(icon,"10")!=NULL) iconofset = 5; - else if(strstr(icon,"11")!=NULL) iconofset = 6; - else if(strstr(icon,"13")!=NULL) iconofset = 7; - else if(strstr(icon,"50")!=NULL) iconofset = 8; - else iconofset = 9; - nextion.putcmd("cond_img.pic", 50+iconofset); - nextion.weatherVisible(1); - #endif - - Serial.printf("##WEATHER###: description: %s, temp:%.1f C, pressure:%dmmHg, humidity:%s%%\n", desc, tempf, pressi, hum); - #ifdef WEATHER_FMT_SHORT - sprintf(wstr, weatherFmt, tempf, pressi, hum); - #else - #if EXT_WEATHER - sprintf(wstr, weatherFmt, desc, tempf, tempfl, pressi, hum, wind_speed, wind[wind_deg]); - #else - sprintf(wstr, weatherFmt, desc, tempf, pressi, hum); - #endif - #endif - network.requestWeatherSync(); - return true; -#endif // if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER) - return false; -} diff --git a/yoRadio/src/core/network.h b/yoRadio/src/core/network.h index e125d24..020efaf 100644 --- a/yoRadio/src/core/network.h +++ b/yoRadio/src/core/network.h @@ -1,6 +1,5 @@ #ifndef network_h #define network_h -#include #include "time.h" #include "WiFi.h" #include "rtcsupport.h" @@ -17,12 +16,10 @@ class MyNetwork { public: n_Status_e status; struct tm timeinfo; - bool firstRun, forceTimeSync, forceWeather; bool lostPlaying = false, beginReconnect = false; //uint8_t tsFailCnt, wsFailCnt; - Ticker ctimer; char *weatherBuf; - bool trueWeather; + //bool trueWeather; public: MyNetwork() {}; void begin(); @@ -31,7 +28,6 @@ class MyNetwork { void setWifiParams(); bool wifiBegin(bool silent=false); private: - Ticker rtimer; void raiseSoftAP(); static void WiFiLostConnection(WiFiEvent_t event, WiFiEventInfo_t info); static void WiFiReconnected(WiFiEvent_t event, WiFiEventInfo_t info); diff --git a/yoRadio/src/core/options.h b/yoRadio/src/core/options.h index 67a20a3..5e636b3 100644 --- a/yoRadio/src/core/options.h +++ b/yoRadio/src/core/options.h @@ -1,7 +1,7 @@ #ifndef options_h #define options_h -#define YOVERSION "0.9.533" +#define YOVERSION "0.9.550" /******************************************************* DO NOT EDIT THIS FILE. @@ -361,6 +361,9 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #ifndef IR_TIMEOUT #define IR_TIMEOUT 80 // kTimeout, see IRremoteESP8266 documentation #endif +#ifndef IR_BUFSIZE + #define IR_BUFSIZE 128 +#endif /* THEMES */ /* color name R G B */ @@ -488,10 +491,26 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #ifndef USE_OTA #define USE_OTA false #endif +#ifndef WATCHDOG_INTERVAL + #define WATCHDOG_INTERVAL 3 //sec. +#endif //#define OTA_PASS "myotapassword12345" //#define HTTP_USER "user" //#define HTTP_PASS "password" - - +#ifndef WATCHDOG_TASK_SIZE + #define WATCHDOG_TASK_SIZE 1024*6 +#endif +#ifndef WATCHDOG_TASK_PRIORITY + #define WATCHDOG_TASK_PRIORITY 3 +#endif +#ifndef WATCHDOG_TASK_CORE_ID + #define WATCHDOG_TASK_CORE_ID 1 +#endif +#ifndef CONNECTION_TIMEOUT + #define CONNECTION_TIMEOUT 5700 +#endif +#ifndef CONNECTION_TIMEOUT_SSL + #define CONNECTION_TIMEOUT_SSL 5700 +#endif #endif diff --git a/yoRadio/src/core/player.cpp b/yoRadio/src/core/player.cpp index 0436afc..05495c6 100644 --- a/yoRadio/src/core/player.cpp +++ b/yoRadio/src/core/player.cpp @@ -5,6 +5,7 @@ #include "display.h" #include "sdmanager.h" #include "netserver.h" +#include "timekeeper.h" Player player; QueueHandle_t playerQueue; @@ -35,10 +36,10 @@ void Player::init() { Serial.print("##[BOOT]#\tplayer.init\t"); playerQueue=NULL; _resumeFilePos = 0; + _hasError=false; playerQueue = xQueueCreate( 5, sizeof( playerRequestParams_t ) ); setOutputPins(false); delay(50); - memset(_plError, 0, PLERR_LN); #ifdef MQTT_ROOT_TOPIC memset(burl, 0, MQTT_BURL_SIZE); #endif @@ -56,14 +57,13 @@ void Player::init() { setTone(config.store.bass, config.store.middle, config.store.trebble); setVolume(0); _status = STOPPED; - //setOutputPins(false); _volTimer=false; //randomSeed(analogRead(0)); #if PLAYER_FORCE_MONO forceMono(true); #endif _loadVol(config.store.volume); - setConnectionTimeout(1700, 3700); + setConnectionTimeout(CONNECTION_TIMEOUT, CONNECTION_TIMEOUT_SSL); Serial.println("done"); } @@ -73,21 +73,23 @@ void Player::sendCommand(playerRequestParams_t request){ } void Player::resetQueue(){ - if(playerQueue!=NULL) xQueueReset(playerQueue); + if(playerQueue!=NULL) xQueueReset(playerQueue); } void Player::stopInfo() { config.setSmartStart(0); - //telnet.info(); netserver.requestOnChange(MODE, 0); } +void Player::setError(){ + _hasError=true; + config.setTitle(config.tmpBuf); + telnet.printf("##ERROR#:\t%s\n", config.tmpBuf); +} + void Player::setError(const char *e){ - strlcpy(_plError, e, PLERR_LN); - if(hasError()) { - config.setTitle(_plError); - telnet.printf("##ERROR#:\t%s\n", e); - } + strlcpy(config.tmpBuf, e, sizeof(config.tmpBuf)); + setError(); } void Player::_stop(bool alreadyStopped){ @@ -95,17 +97,19 @@ void Player::_stop(bool alreadyStopped){ if(config.getMode()==PM_SDCARD && !alreadyStopped) config.sdResumePos = player.getFilePos(); _status = STOPPED; setOutputPins(false); - if(!hasError()) config.setTitle((display.mode()==LOST || display.mode()==UPDATING)?"":const_PlStopped); + if(!_hasError) config.setTitle((display.mode()==LOST || display.mode()==UPDATING)?"":const_PlStopped); config.station.bitrate = 0; config.setBitrateFormat(BF_UNCNOWN); #ifdef USE_NEXTION nextion.bitrate(config.station.bitrate); #endif + setDefaults(); + if(!alreadyStopped) stopSong(); netserver.requestOnChange(BITRATE, 0); display.putRequest(DBITRATE); display.putRequest(PSTOP); - setDefaults(); - if(!alreadyStopped) stopSong(); + //setDefaults(); + //if(!alreadyStopped) stopSong(); if(!lockOutput) stopInfo(); if (player_on_stop_play) player_on_stop_play(); pm.on_stop_play(); @@ -119,6 +123,12 @@ void Player::initHeaders(const char *file) { //netserver.requestOnChange(SDPOS, 0); setDefaults(); } +void resetPlayer(){ + if(!config.store.watchdog) return; + player.resetQueue(); + player.sendCommand({PR_STOP, 0}); + player.loop(); +} #ifndef PL_QUEUE_TICKS #define PL_QUEUE_TICKS 0 @@ -141,6 +151,10 @@ void Player::loop() { pm.on_station_change(); break; } + case PR_TOGGLE: { + toggle(); + break; + } case PR_VOL: { config.setVolume(requestP.payload); Audio::setVolume(volToI2S(requestP.payload)); @@ -157,8 +171,19 @@ void Player::loop() { break; } #endif - case PR_VUTONUS: + case PR_VUTONUS: { if(config.vuThreshold>10) config.vuThreshold -=10; + break; + } + case PR_BURL: { + #ifdef MQTT_ROOT_TOPIC + if(strlen(burl)>0){ + browseUrl(); + } + #endif + break; + } + default: break; } } @@ -170,11 +195,12 @@ void Player::loop() { _volTimer=false; } } + /* #ifdef MQTT_ROOT_TOPIC if(strlen(burl)>0){ browseUrl(); } -#endif +#endif*/ } void Player::setOutputPins(bool isPlaying) { @@ -185,32 +211,15 @@ void Player::setOutputPins(bool isPlaying) { void Player::_play(uint16_t stationId) { log_i("%s called, stationId=%d", __func__, stationId); - setError(""); + _hasError=false; setDefaults(); - remoteStationName = false; - config.setDspOn(1); - config.vuThreshold = 0; - //display.putRequest(PSTOP); - config.screensaverTicks=SCREENSAVERSTARTUPDELAY; - config.screensaverPlayingTicks=SCREENSAVERSTARTUPDELAY; - if(config.getMode()!=PM_SDCARD) { - display.putRequest(PSTOP); - } + _status = STOPPED; setOutputPins(false); - //config.setTitle(config.getMode()==PM_WEB?const_PlConnect:""); - if(!config.loadStation(stationId)) return; - config.setTitle(config.getMode()==PM_WEB?const_PlConnect:"[next track]"); - config.station.bitrate=0; - config.setBitrateFormat(BF_UNCNOWN); + remoteStationName = false; + if(!config.prepareForPlaying(stationId)) return; _loadVol(config.store.volume); - display.putRequest(DBITRATE); - display.putRequest(NEWSTATION); - netserver.requestOnChange(STATION, 0); - netserver.loop(); - netserver.loop(); - if(config.store.smartstart!=2) - config.setSmartStart(0); + bool isConnected = false; if(config.getMode()==PM_SDCARD && SDC_CS!=255){ isConnected=connecttoFS(sdman,config.station.url,config.sdResumePos==0?_resumeFilePos:config.sdResumePos-player.sd_min); @@ -219,36 +228,25 @@ void Player::_play(uint16_t stationId) { } if(config.getMode()==PM_WEB) isConnected=connecttohost(config.station.url); if(isConnected){ - //if (config.store.play_mode==PM_WEB?connecttohost(config.station.url):connecttoFS(SD,config.station.url,config.sdResumePos==0?_resumeFilePos:config.sdResumePos-player.sd_min)) { _status = PLAYING; - if(config.getMode()==PM_SDCARD) { - config.sdResumePos = 0; - config.saveValue(&config.store.lastSdStation, stationId); - } - //config.setTitle(""); - if(config.store.smartstart!=2) - config.setSmartStart(1); - netserver.requestOnChange(MODE, 0); + config.configPostPlaying(stationId); setOutputPins(true); - display.putRequest(NEWMODE, PLAYER); - display.putRequest(PSTART); if (player_on_start_play) player_on_start_play(); pm.on_start_play(); }else{ - telnet.printf("##ERROR#:\tError connecting to %s\n", config.station.url); - SET_PLAY_ERROR("Error connecting to %s", config.station.url); + telnet.printf("##ERROR#:\tError connecting to %.128s\n", config.station.url); + snprintf(config.tmpBuf, sizeof(config.tmpBuf), "Error connecting to %.128s", config.station.url); setError(); _stop(true); }; } #ifdef MQTT_ROOT_TOPIC void Player::browseUrl(){ - setError(""); + _hasError=false; remoteStationName = true; config.setDspOn(1); resumeAfterUrl = _status==PLAYING; display.putRequest(PSTOP); -// setDefaults(); setOutputPins(false); config.setTitle(const_PlConnect); if (connecttohost(burl)){ @@ -260,16 +258,15 @@ void Player::browseUrl(){ if (player_on_start_play) player_on_start_play(); pm.on_start_play(); }else{ - telnet.printf("##ERROR#:\tError connecting to %s\n", burl); - SET_PLAY_ERROR("Error connecting to %s", burl); + telnet.printf("##ERROR#:\tError connecting to %.128s\n", burl); + snprintf(config.tmpBuf, sizeof(config.tmpBuf), "Error connecting to %.128s", burl); setError(); _stop(true); } - memset(burl, 0, MQTT_BURL_SIZE); + //memset(burl, 0, MQTT_BURL_SIZE); } #endif void Player::prev() { - uint16_t lastStation = config.lastStation(); if(config.getMode()==PM_WEB || !config.store.sdsnuffle){ if (lastStation == 1) config.lastStation(config.playlistLength()); else config.lastStation(lastStation-1); diff --git a/yoRadio/src/core/player.h b/yoRadio/src/core/player.h index a10e6a9..d1fbf9c 100644 --- a/yoRadio/src/core/player.h +++ b/yoRadio/src/core/player.h @@ -13,13 +13,14 @@ #endif #ifndef PLQ_SEND_DELAY - #define PLQ_SEND_DELAY portMAX_DELAY + //#define PLQ_SEND_DELAY portMAX_DELAY + #define PLQ_SEND_DELAY pdMS_TO_TICKS(1000) #endif -#define PLERR_LN 64 -#define SET_PLAY_ERROR(...) {char buff[512 + 64]; sprintf(buff,__VA_ARGS__); setError(buff);} +//#define PLERR_LN 64 +//#define SET_PLAY_ERROR(...) {char buff[512 + 64]; sprintf(buff,__VA_ARGS__); setError(buff);} -enum playerRequestType_e : uint8_t { PR_PLAY = 1, PR_STOP = 2, PR_PREV = 3, PR_NEXT = 4, PR_VOL = 5, PR_CHECKSD = 6, PR_VUTONUS = 7 }; +enum playerRequestType_e : uint8_t { PR_PLAY = 1, PR_STOP = 2, PR_PREV = 3, PR_NEXT = 4, PR_VOL = 5, PR_CHECKSD = 6, PR_VUTONUS = 7, PR_BURL = 8, PR_TOGGLE = 9 }; struct playerRequestParams_t { playerRequestType_e type; @@ -34,11 +35,12 @@ class Player: public Audio { bool _volTimer; /* delayed volume save */ uint32_t _resumeFilePos; plStatus_e _status; - char _plError[PLERR_LN]; + //char _plError[PLERR_LN]; private: void _stop(bool alreadyStopped = false); void _play(uint16_t stationId); void _loadVol(uint8_t volume); + bool _hasError; public: bool lockOutput = true; bool resumeAfterUrl = false; @@ -51,8 +53,9 @@ class Player: public Audio { void init(); void loop(); void initHeaders(const char *file); + void setError(); void setError(const char *e); - bool hasError() { return strlen(_plError)>0; } + //bool hasError() { return strlen(_plError)>0; } void sendCommand(playerRequestParams_t request); void resetQueue(); #ifdef MQTT_ROOT_TOPIC diff --git a/yoRadio/src/core/sdmanager.cpp b/yoRadio/src/core/sdmanager.cpp index fd8cf1d..90a908d 100644 --- a/yoRadio/src/core/sdmanager.cpp +++ b/yoRadio/src/core/sdmanager.cpp @@ -45,10 +45,11 @@ bool SDManager::cardPresent() { } bool SDManager::_checkNoMedia(const char* path){ - char nomedia[BUFLEN]= {0}; - strlcat(nomedia, path, BUFLEN); - strlcat(nomedia, "/.nomedia", BUFLEN); - bool nm = exists(nomedia); + if (path[strlen(path) - 1] == '/') + snprintf(config.tmpBuf, sizeof(config.tmpBuf), "%s%s", path, ".nomedia"); + else + snprintf(config.tmpBuf, sizeof(config.tmpBuf), "%s/%s", path, ".nomedia"); + bool nm = exists(config.tmpBuf); return nm; } diff --git a/yoRadio/src/core/telnet.cpp b/yoRadio/src/core/telnet.cpp index 1d6a8ad..2d672f7 100644 --- a/yoRadio/src/core/telnet.cpp +++ b/yoRadio/src/core/telnet.cpp @@ -5,11 +5,12 @@ #include "player.h" #include "network.h" #include "telnet.h" +#include "esp_heap_caps.h" Telnet telnet; bool Telnet::_isIPSet(IPAddress ip) { - return ip.toString() == "0.0.0.0"; + return strcmp(config.ipToStr(ip), "0.0.0.0") == 0; } bool Telnet::begin(bool quiet) { @@ -21,12 +22,11 @@ bool Telnet::begin(bool quiet) { } if(!quiet) Serial.print("##[BOOT]#\ttelnet.begin\t"); if (WiFi.status() == WL_CONNECTED || _isIPSet(WiFi.softAPIP())) { - server.begin(); - server.setNoDelay(true); + toggle(); if(!quiet){ Serial.println("done"); Serial.println("##[BOOT]#"); - BOOTLOG("Ready! Go to http:/%s/ to configure", WiFi.localIP().toString().c_str()); + BOOTLOG("Ready! Go to http:/%s/ to configure", config.ipToStr(WiFi.localIP())); BOOTLOG("------------------------------------------------"); Serial.println("##[BOOT]#"); } @@ -36,10 +36,19 @@ bool Telnet::begin(bool quiet) { } } +void Telnet::start() { + server.begin(); + server.setNoDelay(true); +} + void Telnet::stop() { server.stop(); } +void Telnet::toggle() { + if(config.store.telnet) { start(); }else{ stop(); } +} + void Telnet::emptyClientStream(WiFiClient client) { client.flush(); delay(50); @@ -72,40 +81,41 @@ void Telnet::loop() { return; } uint8_t i; - if (WiFi.status() == WL_CONNECTED) { - if (server.hasClient()) { - for (i = 0; i < MAX_TLN_CLIENTS; i++) { - if (!clients[i] || !clients[i].connected()) { - if (clients[i]) { - clients[i].stop(); + if(config.store.telnet) + if (WiFi.status() == WL_CONNECTED) { + if (server.hasClient()) { + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (!clients[i] || !clients[i].connected()) { + if (clients[i]) { + clients[i].stop(); + } + clients[i] = server.available(); + if (!clients[i]) Serial.println("available broken"); + on_connect(config.ipToStr(clients[i].remoteIP()), i); + clients[i].setNoDelay(true); + emptyClientStream(clients[i]); + break; } - clients[i] = server.available(); - if (!clients[i]) Serial.println("available broken"); - on_connect(clients[i].remoteIP().toString().c_str(), i); - clients[i].setNoDelay(true); - emptyClientStream(clients[i]); - break; + } + if (i >= MAX_TLN_CLIENTS) { + server.available().stop(); } } - if (i >= MAX_TLN_CLIENTS) { - server.available().stop(); + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (clients[i] && clients[i].connected() && clients[i].available()) { + String inputstr = clients[i].readStringUntil('\n'); + inputstr.trim(); + on_input(inputstr.c_str(), i); + } } - } - for (i = 0; i < MAX_TLN_CLIENTS; i++) { - if (clients[i] && clients[i].connected() && clients[i].available()) { - String inputstr = clients[i].readStringUntil('\n'); - inputstr.trim(); - on_input(inputstr.c_str(), i); + } else { + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (clients[i]) { + clients[i].stop(); + } } + delay(1000); } - } else { - for (i = 0; i < MAX_TLN_CLIENTS; i++) { - if (clients[i]) { - clients[i].stop(); - } - } - delay(1000); - } handleSerial(); } @@ -125,35 +135,33 @@ void Telnet::print(uint8_t id, const char *buf) { } void Telnet::printf(const char *format, ...) { - char buf[MAX_PRINTF_LEN]; va_list args; va_start (args, format ); - vsnprintf(buf, MAX_PRINTF_LEN, format, args); + vsnprintf(cmBuf, sizeof(cmBuf), format, args); va_end (args); for (int id = 0; id < MAX_TLN_CLIENTS; id++) { if (clients[id] && clients[id].connected()) { - clients[id].print(buf); + clients[id].print(cmBuf); } } - if (strcmp(buf, "> ") == 0) return; + if (strcmp(cmBuf, "> ") == 0) return; //if(strstr(buf,"\n> ")==NULL) Serial.print(buf); - char *nl = strstr(buf, "\n> "); - if (nl != NULL) { buf[nl-buf+1] = '\0'; } - Serial.print(buf); + char *nl = strstr(cmBuf, "\n> "); + if (nl != NULL) { cmBuf[nl-cmBuf+1] = '\0'; } + Serial.print(cmBuf); } void Telnet::printf(uint8_t id, const char *format, ...) { - char buf[MAX_PRINTF_LEN]; va_list argptr; va_start(argptr, format); - vsnprintf(buf, MAX_PRINTF_LEN, format, argptr); + vsnprintf(cmBuf, sizeof(cmBuf), format, argptr); va_end(argptr); if(id>MAX_TLN_CLIENTS){ - Serial.print(buf); + Serial.print(cmBuf); return; } if (clients[id] && clients[id].connected()) { - clients[id].print(buf); + clients[id].print(cmBuf); } } @@ -164,9 +172,8 @@ void Telnet::on_connect(const char* str, uint8_t clientId) { void Telnet::info() { telnet.printf("##CLI.INFO#\n"); - char timeStringBuff[50]; - strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &network.timeinfo); - telnet.printf("##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset + strftime(config.tmpBuf, sizeof(config.tmpBuf), "%Y-%m-%dT%H:%M:%S+03:00", &network.timeinfo); + telnet.printf("##SYS.DATE#: %s\n", config.tmpBuf); //TODO timezone offset telnet.printf("##CLI.NAMESET#: %d %s\n", config.lastStation(), config.station.name); if (player.status() == PLAYING) { telnet.printf("##CLI.META#: %s\n", config.station.title); @@ -180,6 +187,16 @@ void Telnet::info() { telnet.printf("> "); } +void Telnet::printHeapFragmentationInfo(uint8_t id){ + size_t freeHeap = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + size_t largestBlock = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT); + float fragmentation = 100.0 * (1.0 - ((float)largestBlock / (float)freeHeap)); + printf(id, "\n*************************************\n"); + printf(id, "* Free heap: %u bytes\n", freeHeap); + printf(id, "* Largest free block: %u bytes\n", largestBlock); + printf(id, "* Fragmentation: %.2f%%\n", fragmentation); + printf(id, "*************************************\n\n"); +} void Telnet::on_input(const char* str, uint8_t clientId) { if (strlen(str) == 0) return; if(network.status == CONNECTED){ @@ -253,12 +270,11 @@ void Telnet::on_input(const char* str, uint8_t clientId) { if (!file || file.isDirectory()) { return; } - char sName[BUFLEN], sUrl[BUFLEN]; int sOvol; uint8_t c = 1; while (file.available()) { - if (config.parseCSV(file.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { - printf(clientId, "#CLI.LISTNUM#: %*d: %s, %s\n", 3, c, sName, sUrl); + if (config.parseCSV(file.readStringUntil('\n').c_str(), config.tmpBuf, config.tmpBuf2, sOvol)) { + printf(clientId, "#CLI.LISTNUM#: %*d: %s, %s\n", 3, c, config.tmpBuf, config.tmpBuf2); c++; } } @@ -268,12 +284,11 @@ void Telnet::on_input(const char* str, uint8_t clientId) { } if (strcmp(str, "cli.info") == 0 || strcmp(str, "info") == 0) { printf(clientId, "##CLI.INFO#\n"); - char timeStringBuff[50]; - strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S", &network.timeinfo); + strftime(config.tmpBuf, sizeof(config.tmpBuf), "%Y-%m-%dT%H:%M:%S", &network.timeinfo); if (config.store.tzHour < 0) { - printf(clientId, "##SYS.DATE#: %s%03d:%02d\n", timeStringBuff, config.store.tzHour, config.store.tzMin); + printf(clientId, "##SYS.DATE#: %s%03d:%02d\n", config.tmpBuf, config.store.tzHour, config.store.tzMin); } else { - printf(clientId, "##SYS.DATE#: %s+%02d:%02d\n", timeStringBuff, config.store.tzHour, config.store.tzMin); + printf(clientId, "##SYS.DATE#: %s+%02d:%02d\n", config.tmpBuf, config.store.tzHour, config.store.tzMin); } printf(clientId, "##CLI.NAMESET#: %d %s\n", config.lastStation(), config.station.name); if (player.status() == PLAYING) { @@ -406,11 +421,10 @@ void Telnet::on_input(const char* str, uint8_t clientId) { printf(clientId, "#WIFI.CON#\n"); File file = SPIFFS.open(SSIDS_PATH, "r"); if (file && !file.isDirectory()) { - char sSid[BUFLEN], sPas[BUFLEN]; uint8_t c = 1; while (file.available()) { - if (config.parseSsid(file.readStringUntil('\n').c_str(), sSid, sPas)) { - printf(clientId, "%d: %s, %s\n", c, sSid, sPas); + if (config.parseSsid(file.readStringUntil('\n').c_str(), config.tmpBuf, config.tmpBuf2)) { + printf(clientId, "%d: %s, %s\n", c, config.tmpBuf, config.tmpBuf2); c++; } } @@ -422,11 +436,10 @@ void Telnet::on_input(const char* str, uint8_t clientId) { printf(clientId, "#WIFI.STATION#\n"); File file = SPIFFS.open(SSIDS_PATH, "r"); if (file && !file.isDirectory()) { - char sSid[BUFLEN], sPas[BUFLEN]; uint8_t c = 1; while (file.available()) { - if (config.parseSsid(file.readStringUntil('\n').c_str(), sSid, sPas)) { - if(c==config.store.lastSSID) printf(clientId, "%d: %s, %s\n", c, sSid, sPas); + if (config.parseSsid(file.readStringUntil('\n').c_str(), config.tmpBuf, config.tmpBuf2)) { + if(c==config.store.lastSSID) printf(clientId, "%d: %s, %s\n", c, config.tmpBuf, config.tmpBuf2); c++; } } @@ -434,23 +447,21 @@ void Telnet::on_input(const char* str, uint8_t clientId) { printf(clientId, "##WIFI.STATION#\n> "); return; } - char newssid[30], newpass[40]; - if (sscanf(str, "wifi.con(\"%[^\"]\",\"%[^\"]\")", newssid, newpass) == 2 || sscanf(str, "wifi.con(%[^,],%[^)])", newssid, newpass) == 2 || sscanf(str, "wifi.con(%[^ ] %[^)])", newssid, newpass) == 2 || sscanf(str, "wifi %[^ ] %s", newssid, newpass) == 2) { - char buf[BUFLEN]; - snprintf(buf, BUFLEN, "New SSID: \"%s\" with PASS: \"%s\" for next boot\n> ", newssid, newpass); - printf(clientId, buf); + if (sscanf(str, "wifi.con(\"%[^\"]\",\"%[^\"]\")", config.tmpBuf, config.tmpBuf2) == 2 || sscanf(str, "wifi.con(%[^,],%[^)])", config.tmpBuf, config.tmpBuf2) == 2 || sscanf(str, "wifi.con(%[^ ] %[^)])", config.tmpBuf, config.tmpBuf2) == 2 || sscanf(str, "wifi %[^ ] %s", config.tmpBuf, config.tmpBuf2) == 2) { + snprintf(cmBuf, sizeof(cmBuf), "New SSID: \"%s\" with PASS: \"%s\" for next boot\n> ", config.tmpBuf, config.tmpBuf2); + printf(clientId, cmBuf); printf(clientId, "...REBOOTING...\n> "); - memset(buf, 0, BUFLEN); - snprintf(buf, BUFLEN, "%s\t%s", newssid, newpass); - config.saveWifiFromNextion(buf); + memset(cmBuf, 0, sizeof(cmBuf)); + snprintf(cmBuf, sizeof(cmBuf), "%s\t%s", config.tmpBuf, config.tmpBuf2); + config.saveWifiFromNextion(cmBuf); return; } if (strcmp(str, "wifi.status") == 0 || strcmp(str, "status") == 0) { printf(clientId, "#WIFI.STATUS#\nStatus:\t\t%d\nMode:\t\t%s\nIP:\t\t%s\nMask:\t\t%s\nGateway:\t%s\nRSSI:\t\t%d dBm\n##WIFI.STATUS#\n> ", WiFi.status(), WiFi.getMode()==WIFI_STA?"WIFI_STA":"WIFI_AP", - WiFi.getMode()==WIFI_STA?WiFi.localIP().toString():WiFi.softAPIP().toString(), - WiFi.getMode()==WIFI_STA?WiFi.subnetMask().toString():"255.255.255.0", - WiFi.getMode()==WIFI_STA?WiFi.gatewayIP().toString():WiFi.softAPIP().toString(), + WiFi.getMode()==WIFI_STA?config.ipToStr(WiFi.localIP()):config.ipToStr(WiFi.softAPIP()), + WiFi.getMode()==WIFI_STA?config.ipToStr(WiFi.subnetMask()):"255.255.255.0", + WiFi.getMode()==WIFI_STA?config.ipToStr(WiFi.gatewayIP()):config.ipToStr(WiFi.softAPIP()), WiFi.RSSI() ); return; @@ -460,12 +471,14 @@ void Telnet::on_input(const char* str, uint8_t clientId) { return; } if (strcmp(str, "sys.heap") == 0 || strcmp(str, "heap") == 0) { - printf(clientId, "Free heap:\t%d bytes\n> ", xPortGetFreeHeapSize()); + //printf(clientId, "Free heap:\t%d bytes\n> ", xPortGetFreeHeapSize()); + printHeapFragmentationInfo(clientId); return; } if (strcmp(str, "sys.config") == 0 || strcmp(str, "config") == 0) { config.bootInfo(); - printf(clientId, "Free heap:\t%d bytes\n> ", xPortGetFreeHeapSize()); + //printf(clientId, "Free heap:\t%d bytes\n> ", xPortGetFreeHeapSize()); + printHeapFragmentationInfo(clientId); return; } if (strcmp(str, "wifi.discon") == 0 || strcmp(str, "discon") == 0 || strcmp(str, "disconnect") == 0) { diff --git a/yoRadio/src/core/telnet.h b/yoRadio/src/core/telnet.h index 03ab99d..6e45ebd 100644 --- a/yoRadio/src/core/telnet.h +++ b/yoRadio/src/core/telnet.h @@ -4,14 +4,15 @@ #include #define MAX_TLN_CLIENTS 5 -#define MAX_PRINTF_LEN BUFLEN+50 class Telnet { public: Telnet() {}; bool begin(bool quiet=false); void loop(); + void start(); void stop(); + void toggle(); void print(uint8_t id, const char *buf); void print(const char *buf); void printf(uint8_t id, const char *format, ...); @@ -25,8 +26,10 @@ class Telnet { void on_connect(const char* str, uint8_t clientId); void on_input(const char* str, uint8_t clientId); private: + char cmBuf[220]; bool _isIPSet(IPAddress ip); void handleSerial(); + void printHeapFragmentationInfo(uint8_t id); }; extern Telnet telnet; diff --git a/yoRadio/src/core/timekeeper.cpp b/yoRadio/src/core/timekeeper.cpp new file mode 100644 index 0000000..a2b60b6 --- /dev/null +++ b/yoRadio/src/core/timekeeper.cpp @@ -0,0 +1,386 @@ +#include "timekeeper.h" +#include "options.h" +#include "config.h" +#include "network.h" +#include "display.h" +#include "player.h" +#include "netserver.h" +#include "rtcsupport.h" + +#if RTCSUPPORTED + //#define TIME_SYNC_INTERVAL 24*60*60*1000 + #define TIME_SYNC_INTERVAL config.store.timeSyncIntervalRTC*60*60*1000 +#else + #define TIME_SYNC_INTERVAL config.store.timeSyncInterval*60*1000 +#endif +#define WEATHER_SYNC_INTERVAL config.store.weatherSyncInterval*60*1000 + +#define SYNC_STACK_SIZE 1024 * 4 +#define SYNC_TASK_CORE 0 +#define SYNC_TASK_PRIORITY 1 + +TimeKeeper timekeeper; + +void _syncTask(void *pvParameters) { + if (timekeeper.forceWeather && timekeeper.forceTimeSync) { + timekeeper.weatherTask(); + timekeeper.timeTask(); + } + else if (timekeeper.forceWeather) { + timekeeper.weatherTask(); + } + else if (timekeeper.forceTimeSync) { + timekeeper.timeTask(); + } + timekeeper.busy = false; + vTaskDelete(NULL); +} + +bool TimeKeeper::loop0(){ // core0 (display) + uint32_t currentTime = millis(); + static uint32_t _last1s = 0; + static uint32_t _last2s = 0; + static uint32_t _last5s = 0; + if (currentTime - _last1s >= 1000) { // 1sec + _last1s = currentTime; +#ifndef DUMMYDISPLAY + #ifndef UPCLOCK_CORE1 + _upClock(); + #endif +#endif + } + if (currentTime - _last2s >= 2000) { // 2sec + _last2s = currentTime; + _upRSSI(); + } + if (currentTime - _last5s >= 5000) { // 2sec + _last5s = currentTime; + //HEAP_INFO(); + } + #ifdef DUMMYDISPLAY + return true; + #endif + static uint32_t lastWeatherTime = 0; + if (currentTime - lastWeatherTime >= WEATHER_SYNC_INTERVAL) { + lastWeatherTime = currentTime; + forceWeather = true; + } + static uint32_t lastTimeTime = 0; + if (currentTime - lastTimeTime >= TIME_SYNC_INTERVAL) { + lastTimeTime = currentTime; + forceTimeSync = true; + } + if (!busy && (forceWeather || forceTimeSync) && network.status == CONNECTED) { + busy = true; + //config.setTimeConf(); + xTaskCreatePinnedToCore( + _syncTask, + "syncTask", + SYNC_STACK_SIZE, + NULL, // Params + SYNC_TASK_PRIORITY, + NULL, // Descriptor + SYNC_TASK_CORE + ); + } + return true; // just in case +} + +bool TimeKeeper::loop1(){ // core1 (player) + uint32_t currentTime = millis(); + static uint32_t _last1s = 0; + static uint32_t _last2s = 0; + if (currentTime - _last1s >= 1000) { // 1sec + pm.on_ticker(); + _last1s = currentTime; +#ifndef DUMMYDISPLAY + #ifdef UPCLOCK_CORE1 + _upClock(); + #endif +#endif + _upScreensaver(); + _upSDPos(); + _returnPlayer(); + _doAfterWait(); + } + if (currentTime - _last2s >= 2000) { // 2sec + _last2s = currentTime; + } + return true; // just in case +} + +void TimeKeeper::waitAndReturnPlayer(uint8_t time_s){ + _returnPlayerTime = millis()+time_s*1000; +} +void TimeKeeper::_returnPlayer(){ + if(_returnPlayerTime>0 && millis()>=_returnPlayerTime){ + _returnPlayerTime = 0; + display.putRequest(NEWMODE, PLAYER); + } +} + +void TimeKeeper::waitAndDo(uint8_t time_s, void (*callback)()){ + _doAfterTime = millis()+time_s*1000; + _aftercallback = callback; +} +void TimeKeeper::_doAfterWait(){ + if(_doAfterTime>0 && millis()>=_doAfterTime){ + _doAfterTime = 0; + _aftercallback(); + } +} + +void TimeKeeper::_upClock(){ +#if RTCSUPPORTED + if(config.isRTCFound()){ + rtc.getTime(&network.timeinfo); + mktime(&network.timeinfo); + if(display.ready()) display.putRequest(CLOCK); + } +#else + if(network.timeinfo.tm_year>100 || network.status == SDREADY) { + network.timeinfo.tm_sec++; + mktime(&network.timeinfo); + if(display.ready()) display.putRequest(CLOCK); + } +#endif +} + +void TimeKeeper::_upScreensaver(){ +#ifndef DSP_LCD + if(!display.ready()) return; + if(config.store.screensaverEnabled && display.mode()==PLAYER && !player.isRunning()){ + config.screensaverTicks++; + if(config.screensaverTicks > config.store.screensaverTimeout+SCREENSAVERSTARTUPDELAY){ + if(config.store.screensaverBlank){ + display.putRequest(NEWMODE, SCREENBLANK); + }else{ + display.putRequest(NEWMODE, SCREENSAVER); + } + } + } + if(config.store.screensaverPlayingEnabled && display.mode()==PLAYER && player.isRunning()){ + config.screensaverPlayingTicks++; + if(config.screensaverPlayingTicks > config.store.screensaverPlayingTimeout*60+SCREENSAVERSTARTUPDELAY){ + if(config.store.screensaverPlayingBlank){ + display.putRequest(NEWMODE, SCREENBLANK); + }else{ + display.putRequest(NEWMODE, SCREENSAVER); + } + } + } +#endif +} + +void TimeKeeper::_upRSSI(){ + if(network.status == CONNECTED){ + netserver.setRSSI(WiFi.RSSI()); + netserver.requestOnChange(NRSSI, 0); + if(display.ready()) display.putRequest(DSPRSSI, netserver.getRSSI()); + } +#ifdef USE_SD + if(display.mode()!=SDCHANGE) player.sendCommand({PR_CHECKSD, 0}); +#endif + player.sendCommand({PR_VUTONUS, 0}); +} + +void TimeKeeper::_upSDPos(){ + if(player.isRunning() && config.getMode()==PM_SDCARD) netserver.requestOnChange(SDPOS, 0); +} + +void TimeKeeper::timeTask(){ + static uint8_t tsFailCnt = 0; + if(getLocalTime(&network.timeinfo)){ + tsFailCnt = 0; + forceTimeSync = false; + mktime(&network.timeinfo); + display.putRequest(CLOCK); + network.requestTimeSync(true); + #if RTCSUPPORTED + if (config.isRTCFound()) rtc.setTime(&network.timeinfo); + #endif + }else{ + if(tsFailCnt<4){ + forceTimeSync = true; + tsFailCnt++; + }else{ + forceTimeSync = false; + tsFailCnt=0; + } + } +} +void TimeKeeper::weatherTask(){ + if(!network.weatherBuf || strlen(config.store.weatherkey)==0 || !config.store.showweather) return; + forceWeather = false; + _getWeather(network.weatherBuf); +} + +bool _getWeather(char *wstr) { +#if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER) + + WiFiClient client; + const char* host = "api.openweathermap.org"; + if (!client.connect(host, 80)) { + Serial.println("##WEATHER###: connection failed"); + return false; + } + char httpget[250] = {0}; + sprintf(httpget, "GET /data/2.5/weather?lat=%s&lon=%s&units=%s&lang=%s&appid=%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", config.store.weatherlat, config.store.weatherlon, weatherUnits, weatherLang, config.store.weatherkey, host); + client.print(httpget); + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 2000UL) { + Serial.println("##WEATHER###: client available timeout !"); + client.stop(); + return false; + } + } + timeout = millis(); + String line = ""; + if (client.connected()) { + while (client.available()) + { + line = client.readStringUntil('\n'); + if (strstr(line.c_str(), "\"temp\"") != NULL) { + client.stop(); + break; + } + if ((millis() - timeout) > 500) + { + client.stop(); + Serial.println("##WEATHER###: client read timeout !"); + return false; + } + } + } + if (strstr(line.c_str(), "\"temp\"") == NULL) { + Serial.println("##WEATHER###: weather not found !"); + return false; + } + char *tmpe; + char *tmps; + char *tmpc; + const char* cursor = line.c_str(); + char desc[120], temp[20], hum[20], press[20], icon[5]; + + tmps = strstr(cursor, "\"description\":\""); + if (tmps == NULL) { Serial.println("##WEATHER###: description not found !"); return false;} + tmps += 15; + tmpe = strstr(tmps, "\",\""); + if (tmpe == NULL) { Serial.println("##WEATHER###: description not found !"); return false;} + strlcpy(desc, tmps, tmpe - tmps + 1); + cursor = tmpe + 2; + + // "ясно","icon":"01d"}], + tmps = strstr(cursor, "\"icon\":\""); + if (tmps == NULL) { Serial.println("##WEATHER###: icon not found !"); return false;} + tmps += 8; + tmpe = strstr(tmps, "\"}"); + if (tmpe == NULL) { Serial.println("##WEATHER###: icon not found !"); return false;} + strlcpy(icon, tmps, tmpe - tmps + 1); + cursor = tmpe + 2; + + tmps = strstr(cursor, "\"temp\":"); + if (tmps == NULL) { Serial.println("##WEATHER###: temp not found !"); return false;} + tmps += 7; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("##WEATHER###: temp not found !"); return false;} + strlcpy(temp, tmps, tmpe - tmps + 1); + cursor = tmpe + 1; + float tempf = atof(temp); + + tmps = strstr(cursor, "\"feels_like\":"); + if (tmps == NULL) { Serial.println("##WEATHER###: feels_like not found !"); return false;} + tmps += 13; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("##WEATHER###: feels_like not found !"); return false;} + strlcpy(temp, tmps, tmpe - tmps + 1); + cursor = tmpe + 2; + float tempfl = atof(temp); (void)tempfl; + + tmps = strstr(cursor, "\"pressure\":"); + if (tmps == NULL) { Serial.println("##WEATHER###: pressure not found !"); return false;} + tmps += 11; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("##WEATHER###: pressure not found !"); return false;} + strlcpy(press, tmps, tmpe - tmps + 1); + cursor = tmpe + 2; + int pressi = (float)atoi(press) / 1.333; + + tmps = strstr(cursor, "humidity\":"); + if (tmps == NULL) { Serial.println("##WEATHER###: humidity not found !"); return false;} + tmps += 10; + tmpe = strstr(tmps, ",\""); + tmpc = strstr(tmps, "}"); + if (tmpe == NULL) { Serial.println("##WEATHER###: humidity not found !"); return false;} + strlcpy(hum, tmps, tmpe - tmps + (tmpc>tmpe?1:0)); + + tmps = strstr(cursor, "\"grnd_level\":"); + bool grnd_level_pr = (tmps != NULL); + if(grnd_level_pr){ + tmps += 13; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("##WEATHER###: grnd_level not found !"); return false;} + strlcpy(press, tmps, tmpe - tmps + 1); + cursor = tmpe + 2; + pressi = (float)atoi(press) / 1.333; + } + + tmps = strstr(cursor, "\"speed\":"); + if (tmps == NULL) { Serial.println("##WEATHER###: wind speed not found !"); return false;} + tmps += 8; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("##WEATHER###: wind speed not found !"); return false;} + strlcpy(temp, tmps, tmpe - tmps + 1); + cursor = tmpe + 1; + float wind_speed = atof(temp); (void)wind_speed; + + tmps = strstr(cursor, "\"deg\":"); + if (tmps == NULL) { Serial.println("##WEATHER###: wind deg not found !"); return false;} + tmps += 6; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("##WEATHER###: wind deg not found !"); return false;} + strlcpy(temp, tmps, tmpe - tmps + 1); + cursor = tmpe + 1; + int wind_deg = atof(temp)/22.5; + if(wind_deg<0) wind_deg = 16+wind_deg; + + + #ifdef USE_NEXTION + nextion.putcmdf("press_txt.txt=\"%dmm\"", pressi); + nextion.putcmdf("hum_txt.txt=\"%d%%\"", atoi(hum)); + char cmd[30]; + snprintf(cmd, sizeof(cmd)-1,"temp_txt.txt=\"%.1f\"", tempf); + nextion.putcmd(cmd); + int iconofset; + if(strstr(icon,"01")!=NULL) iconofset = 0; + else if(strstr(icon,"02")!=NULL) iconofset = 1; + else if(strstr(icon,"03")!=NULL) iconofset = 2; + else if(strstr(icon,"04")!=NULL) iconofset = 3; + else if(strstr(icon,"09")!=NULL) iconofset = 4; + else if(strstr(icon,"10")!=NULL) iconofset = 5; + else if(strstr(icon,"11")!=NULL) iconofset = 6; + else if(strstr(icon,"13")!=NULL) iconofset = 7; + else if(strstr(icon,"50")!=NULL) iconofset = 8; + else iconofset = 9; + nextion.putcmd("cond_img.pic", 50+iconofset); + nextion.weatherVisible(1); + #endif + + Serial.printf("##WEATHER###: description: %s, temp:%.1f C, pressure:%dmmHg, humidity:%s%%\n", desc, tempf, pressi, hum); + #ifdef WEATHER_FMT_SHORT + sprintf(wstr, weatherFmt, tempf, pressi, hum); + #else + #if EXT_WEATHER + sprintf(wstr, weatherFmt, desc, tempf, tempfl, pressi, hum, wind_speed, wind[wind_deg]); + #else + sprintf(wstr, weatherFmt, desc, tempf, pressi, hum); + #endif + #endif + display.putRequest(NEWWEATHER); + return true; +#endif // if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER) + return false; +} + +//****************** diff --git a/yoRadio/src/core/timekeeper.h b/yoRadio/src/core/timekeeper.h new file mode 100644 index 0000000..6f5aefc --- /dev/null +++ b/yoRadio/src/core/timekeeper.h @@ -0,0 +1,42 @@ +#ifndef timekeeper_h +#define timekeeper_h +#include "Arduino.h" + +void _syncTask(void * pvParameters); +bool _getWeather(char *wstr); + +class TimeKeeper { + public: + volatile bool forceWeather; + volatile bool forceTimeSync; + volatile bool busy; + public: + TimeKeeper() { + busy = false; + forceWeather = true; + forceTimeSync = true; + _returnPlayerTime = _doAfterTime = 0; + } + bool loop0(); + bool loop1(); + void timeTask(); + void weatherTask(); + void waitAndReturnPlayer(uint8_t time_s); + void waitAndDo(uint8_t time_s, void (*callback)()); + private: + uint32_t _returnPlayerTime, _doAfterTime; + void (*_aftercallback)(); + void (*_watchdogcallback)(); + void _upRSSI(); + void _upSDPos(); + void _upClock(); + void _upScreensaver(); + void _returnPlayer(); + void _doAfterWait(); + void _doWatchDog(); + +}; + +extern TimeKeeper timekeeper; + +#endif diff --git a/yoRadio/src/displays/widgets/pages.cpp b/yoRadio/src/displays/widgets/pages.cpp index 61f0bc8..d056cb7 100644 --- a/yoRadio/src/displays/widgets/pages.cpp +++ b/yoRadio/src/displays/widgets/pages.cpp @@ -13,7 +13,7 @@ void Pager::loop(){ } Page& Pager::addPage(Page* page, bool setNow){ - _pages.add(page); + _pages.push_back(page); if(setNow) setPage(page); return *page; } @@ -21,7 +21,15 @@ Page& Pager::addPage(Page* page, bool setNow){ bool Pager::removePage(Page* page){ page->setActive(false); dsp.clearDsp(); - return _pages.remove(page); + auto i = std::find_if(_pages.begin(), _pages.end(), [&page](const Page* pn){ return page == pn; }); + if (i != _pages.end()){ + delete (*i); + (*i) = nullptr; + _pages.erase(i); + return true; + } + return false; + //return _pages.remove(page); } void Pager::setPage(Page* page, bool black){ @@ -33,12 +41,13 @@ void Pager::setPage(Page* page, bool black){ /*******************************************************/ -Page::Page() : _widgets(LinkedList([](Widget * wd) { delete wd;})), _pages(LinkedList([](Page* pg){ delete pg; })) { - _active = false; -} +//Page::Page() : _widgets(LinkedList([](Widget * wd) { delete wd;})), _pages(LinkedList([](Page* pg){ delete pg; })) { +// _active = false; +//} Page::~Page() { for (const auto& w : _widgets) removeWidget(w); + // what about deleting _pages ??? } void Page::loop() { @@ -46,23 +55,40 @@ void Page::loop() { } Widget& Page::addWidget(Widget* widget) { - _widgets.add(widget); + _widgets.push_back(widget); widget->setActive(_active, _active); return *widget; } bool Page::removeWidget(Widget* widget){ widget->setActive(false, _active); - return _widgets.remove(widget); + auto i = std::find_if(_widgets.begin(), _widgets.end(), [&widget](const Widget* wn){ return widget == wn; }); + if (i != _widgets.end()){ + delete (*i); + (*i) = nullptr; + _widgets.erase(i); + return true; + } + return false; + + //return _widgets.remove(widget); } Page& Page::addPage(Page* page){ - _pages.add(page); + _pages.push_back(page); return *page; } bool Page::removePage(Page* page){ - return _pages.remove(page); + auto i = std::find_if(_pages.begin(), _pages.end(), [&page](const Page* pn){ return page == pn; }); + if (i != _pages.end()){ + delete (*i); + (*i) = nullptr; + _pages.erase(i); + return true; + } + return false; +// return _pages.remove(page); } void Page::setActive(bool act) { diff --git a/yoRadio/src/displays/widgets/pages.h b/yoRadio/src/displays/widgets/pages.h index d79ee85..f675462 100644 --- a/yoRadio/src/displays/widgets/pages.h +++ b/yoRadio/src/displays/widgets/pages.h @@ -1,16 +1,16 @@ #ifndef pages_h #define pages_h -#include "Arduino.h" -#include "../../AsyncWebServer/StringArray.h" +#include + class Page { protected: - LinkedList _widgets; - LinkedList _pages; + std::list _widgets; + std::list _pages; bool _active; public: - Page(); + //Page(); ~Page(); void loop(); Widget& addWidget(Widget* widget); @@ -23,14 +23,14 @@ class Page { class Pager{ public: - Pager() : _pages(LinkedList([](Page* pg){ delete pg; })) {} + //Pager() : _pages(std::list([](Page* pg){ delete pg; })) {} void begin(); void loop(); Page& addPage(Page* page, bool setNow = false); bool removePage(Page* page); void setPage(Page* page, bool black=false); private: - LinkedList _pages; + std::list _pages; }; diff --git a/yoRadio/src/displays/widgets/widgets.h b/yoRadio/src/displays/widgets/widgets.h index 897e4c7..9d05e5e 100644 --- a/yoRadio/src/displays/widgets/widgets.h +++ b/yoRadio/src/displays/widgets/widgets.h @@ -119,6 +119,7 @@ class TextWidget: public Widget { TextWidget() {} TextWidget(WidgetConfig wconf, uint16_t buffsize, bool uppercase, uint16_t fgcolor, uint16_t bgcolor) { init(wconf, buffsize, uppercase, fgcolor, bgcolor); } ~TextWidget(); + using Widget::init; void init(WidgetConfig wconf, uint16_t buffsize, bool uppercase, uint16_t fgcolor, uint16_t bgcolor); void setText(const char* txt); void setText(int val, const char *format); @@ -139,6 +140,7 @@ class FillWidget: public Widget { public: FillWidget() {} FillWidget(FillConfig conf, uint16_t bgcolor) { init(conf, bgcolor); } + using Widget::init; void init(FillConfig conf, uint16_t bgcolor); void setHeight(uint16_t newHeight); protected: @@ -151,6 +153,7 @@ class ScrollWidget: public TextWidget { ScrollWidget(){} ScrollWidget(const char* separator, ScrollConfig conf, uint16_t fgcolor, uint16_t bgcolor); ~ScrollWidget(); + using Widget::init; void init(const char* separator, ScrollConfig conf, uint16_t fgcolor, uint16_t bgcolor); void loop(); void setText(const char* txt); @@ -182,6 +185,7 @@ class SliderWidget: public Widget { SliderWidget(FillConfig conf, uint16_t fgcolor, uint16_t bgcolor, uint32_t maxval, uint16_t oucolor=0){ init(conf, fgcolor, bgcolor, maxval, oucolor); } + using Widget::init; void init(FillConfig conf, uint16_t fgcolor, uint16_t bgcolor, uint32_t maxval, uint16_t oucolor=0); void setValue(uint32_t val); protected: @@ -199,6 +203,7 @@ class VuWidget: public Widget { VuWidget() {} VuWidget(WidgetConfig wconf, VUBandsConfig bands, uint16_t vumaxcolor, uint16_t vumincolor, uint16_t bgcolor) { init(wconf, bands, vumaxcolor, vumincolor, bgcolor); } ~VuWidget(); + using Widget::init; void init(WidgetConfig wconf, VUBandsConfig bands, uint16_t vumaxcolor, uint16_t vumincolor, uint16_t bgcolor); void loop(); protected: @@ -213,6 +218,7 @@ class VuWidget: public Widget { class NumWidget: public TextWidget { public: + using Widget::init; void init(WidgetConfig wconf, uint16_t buffsize, bool uppercase, uint16_t fgcolor, uint16_t bgcolor); void setText(const char* txt); void setText(int val, const char *format); @@ -227,6 +233,7 @@ class ProgressWidget: public TextWidget { ProgressWidget(WidgetConfig conf, ProgressConfig pconf, uint16_t fgcolor, uint16_t bgcolor) { init(conf, pconf, fgcolor, bgcolor); } + using Widget::init; void init(WidgetConfig conf, ProgressConfig pconf, uint16_t fgcolor, uint16_t bgcolor){ TextWidget::init(conf, pconf.width, false, fgcolor, bgcolor); _speed = pconf.speed; _width = pconf.width; _barwidth = pconf.barwidth; @@ -254,6 +261,7 @@ class BitrateWidget: public Widget { BitrateWidget() {} BitrateWidget(BitrateConfig bconf, uint16_t fgcolor, uint16_t bgcolor) { init(bconf, fgcolor, bgcolor); } ~BitrateWidget(){} + using Widget::init; void init(BitrateConfig bconf, uint16_t fgcolor, uint16_t bgcolor); void setBitrate(uint16_t bitrate); void setFormat(BitrateFormat format); diff --git a/yoRadio/src/main.cpp b/yoRadio/src/main.cpp index 70e5345..6a39482 100644 --- a/yoRadio/src/main.cpp +++ b/yoRadio/src/main.cpp @@ -9,6 +9,16 @@ #include "core/controls.h" #include "core/mqtt.h" #include "core/optionschecker.h" +#include "core/timekeeper.h" + +#if USE_OTA +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) +#include +#else +#include +#endif +#include +#endif #if DSP_HSPI || TS_HSPI || VS_HSPI SPIClass SPI2(HOOPSENb); @@ -16,6 +26,44 @@ SPIClass SPI2(HOOPSENb); extern __attribute__((weak)) void yoradio_on_setup(); +#if USE_OTA +void setupOTA(){ + if(strlen(config.store.mdnsname)>0) + ArduinoOTA.setHostname(config.store.mdnsname); +#ifdef OTA_PASS + ArduinoOTA.setPassword(OTA_PASS); +#endif + ArduinoOTA + .onStart([]() { + player.sendCommand({PR_STOP, 0}); + display.putRequest(NEWMODE, UPDATING); + telnet.printf("Start OTA updating %s\n", ArduinoOTA.getCommand() == U_FLASH?"firmware":"filesystem"); + }) + .onEnd([]() { + telnet.printf("\nEnd OTA update, Rebooting...\n"); + ESP.restart(); + }) + .onProgress([](unsigned int progress, unsigned int total) { + telnet.printf("Progress OTA: %u%%\r", (progress / (total / 100))); + }) + .onError([](ota_error_t error) { + telnet.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) { + telnet.printf("Auth Failed\n"); + } else if (error == OTA_BEGIN_ERROR) { + telnet.printf("Begin Failed\n"); + } else if (error == OTA_CONNECT_ERROR) { + telnet.printf("Connect Failed\n"); + } else if (error == OTA_RECEIVE_ERROR) { + telnet.printf("Receive Failed\n"); + } else if (error == OTA_END_ERROR) { + telnet.printf("End Failed\n"); + } + }); + ArduinoOTA.begin(); +} +#endif + void setup() { Serial.begin(115200); if(REAL_LEDBUILTIN!=255) pinMode(REAL_LEDBUILTIN, OUTPUT); @@ -45,23 +93,29 @@ void setup() { #ifdef MQTT_ROOT_TOPIC mqttInit(); #endif + #if USE_OTA + setupOTA(); + #endif if (config.getMode()==PM_SDCARD) player.initHeaders(config.station.url); player.lockOutput=false; if (config.store.smartstart == 1) { - delay(99); + delay(250); player.sendCommand({PR_PLAY, config.lastStation()}); } pm.on_end_setup(); } void loop() { + timekeeper.loop1(); telnet.loop(); if (network.status == CONNECTED || network.status==SDREADY) { player.loop(); - //loopControls(); +#if USE_OTA + ArduinoOTA.handle(); +#endif } loopControls(); - netserver.loop(); + //netserver.loop(); } #include "core/audiohandlers.h"