This commit is contained in:
e2002
2025-07-27 18:00:01 +03:00
parent bddc5bdf17
commit 507414da7e
35 changed files with 1257 additions and 819 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -13,4 +13,5 @@
--playlist-hover: #323232;
--section-gradient: #111111;
--section-border: #555555;
--heapbar-color: #3ea220;
}

View File

@@ -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;

View File

@@ -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<ConnectParams*>(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);

View File

@@ -32,10 +32,6 @@
#include <FFat.h>
#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<strlen(str); i++){
if(str[i] < 32) continue; // ignore control sign
hash += (str[i] - 31) * i * 32;
if(str[i] < 32) continue; // ignore control sign
hash += (str[i] - 31) * i * 32;
}
return hash;
}
}
private:
const char *codecname[9] = {"unknown", "WAV", "MP3", "AAC", "M4A", "FLAC", "OGG", "OGG FLAC", "OPUS"};
@@ -475,6 +472,14 @@ private:
std::vector<char*> m_playlistURL; // m3u8 streamURLs buffer
std::vector<uint32_t> 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;

View File

@@ -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<ConnectParams*>(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,7 +1843,23 @@ 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;

View File

@@ -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<char*> m_playlistURL; // m3u8 streamURLs buffer
std::vector<uint32_t> 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 */

View File

@@ -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);
}
}
}

View File

@@ -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<bool>(atoi(value))); return true; }
if (strEquals(command, "screensaverplayingtimeout")){ config.setScreensaverPlayingTimeout(static_cast<uint16_t>(atoi(value))); return true; }
if (strEquals(command, "screensaverplayingblank")) { config.setScreensaverPlayingBlank(static_cast<bool>(atoi(value))); return true; }
if (strEquals(command, "abuff")){ config.saveValue(&config.store.abuff, static_cast<uint16_t>(atoi(value))); return true; }
if (strEquals(command, "telnet")){ config.saveValue(&config.store.telnet, static_cast<bool>(atoi(value))); telnet.toggle(); return true; }
if (strEquals(command, "watchdog")){ config.saveValue(&config.store.watchdog, static_cast<bool>(atoi(value))); return true; }
if (strEquals(command, "tzh")) { config.saveValue(&config.store.tzHour, static_cast<int8_t>(atoi(value))); return true; }
if (strEquals(command, "tzm")) { config.saveValue(&config.store.tzMin, static_cast<int8_t>(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<int8_t>(atoi(value))); return true; }
if (strEquals(command, "tzm")) { config.saveValue(&config.store.tzMin, static_cast<int8_t>(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<uint16_t>(atoi(value))); return true; }
if (strEquals(command, "timeintrtc")) { config.saveValue(&config.store.timeSyncIntervalRTC, static_cast<uint16_t>(atoi(value))); return true; }
if (strEquals(command, "volsteps")) { config.saveValue(&config.store.volsteps, static_cast<uint8_t>(atoi(value))); return true; }
if (strEquals(command, "encacc")) { setEncAcceleration(static_cast<uint16_t>(atoi(value))); return true; }
@@ -79,14 +85,16 @@ 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<uint16_t>(atoi(value))); return true; }
if (strEquals(command, "volume")) { player.setVol(static_cast<uint8_t>(atoi(value))); return true; }
if (strEquals(command, "sdpos")) { config.setSDpos(static_cast<uint32_t>(atoi(value))); return true; }
if (strEquals(command, "snuffle")) { config.setSnuffle(strcmp(value, "true") == 0); return true; }
if (strEquals(command, "balance")) { config.setBalance(static_cast<uint8_t>(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; }
@@ -101,10 +109,9 @@ bool CommandHandler::exec(const char *command, const char *value, uint8_t cid) {
if (strEquals(command, "softap")) { config.saveValue(&config.store.softapdelay, static_cast<uint8_t>(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;
}

View File

@@ -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();
}

View File

@@ -1,7 +1,6 @@
#ifndef config_h
#define config_h
#include "Arduino.h"
#include <Ticker.h>
#include <SPI.h>
#include <SPIFFS.h>
#include <EEPROM.h>
@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -3,20 +3,18 @@
#include "options.h"
#include "Arduino.h"
#include <Ticker.h>
#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

View File

@@ -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

View File

@@ -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

View File

@@ -1,7 +1,6 @@
#include "netserver.h"
#include <SPIFFS.h>
#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 <Update.h>
#include <ESPmDNS.h>
#if USE_OTA
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
#include <NetworkUdp.h>
#else
#include <WiFiUdp.h>
#endif
#include <ArduinoOTA.h>
#endif
//#include <Ticker.h>
#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)<br /> %s", (int)Update.getError(), Update.errorString());
return ret;
sprintf(netserver.nsBuf, "Update failed with error (%d)<br /> %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){

View File

@@ -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"(
<div id="content" class="hidden progmem">
</div><!--content-->
<div id="progress"><span id="loader"></span></div>
<div id="heap"></div>
</body>
</html>
)";
@@ -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);

View File

@@ -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;
}

View File

@@ -1,6 +1,5 @@
#ifndef network_h
#define network_h
#include <Ticker.h>
#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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -4,14 +4,15 @@
#include <WiFi.h>
#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;

View File

@@ -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;
}
//******************

View File

@@ -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

View File

@@ -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 * >([](Widget * wd) { delete wd;})), _pages(LinkedList<Page*>([](Page* pg){ delete pg; })) {
_active = false;
}
//Page::Page() : _widgets(LinkedList<Widget * >([](Widget * wd) { delete wd;})), _pages(LinkedList<Page*>([](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) {

View File

@@ -1,16 +1,16 @@
#ifndef pages_h
#define pages_h
#include "Arduino.h"
#include "../../AsyncWebServer/StringArray.h"
#include <list>
class Page {
protected:
LinkedList<Widget*> _widgets;
LinkedList<Page*> _pages;
std::list<Widget*> _widgets;
std::list<Page*> _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*>([](Page* pg){ delete pg; })) {}
//Pager() : _pages(std::list<Page*>([](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<Page*> _pages;
std::list<Page*> _pages;
};

View File

@@ -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);

View File

@@ -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 <NetworkUdp.h>
#else
#include <WiFiUdp.h>
#endif
#include <ArduinoOTA.h>
#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"