diff --git a/README.md b/README.md index 62c9e29..77fb624 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,13 @@ Work is in progress... --- ## Version history +#### v0.7.000 +- added support for Nextion displays ([more info](nextion/README.md)) +- fixed work of VU Meter +- fixed time lag when adjusting the volume / selecting a station +- optimization of work with the DSP_DUMMY option +- some bug fixes + #### v0.6.530 - adding VU meter for displays ST7735 160x80, GC9106 160x80, ILI9225 220x176, ST7789 240x240 - TFT_22_ILI9225 library is integrated into the project diff --git a/exsamples/myoptions.h b/exsamples/myoptions.h index beeddd5..6c7b32c 100644 --- a/exsamples/myoptions.h +++ b/exsamples/myoptions.h @@ -22,6 +22,13 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti //#define TFT_DC 4 /* SPI DC/RS pin */ /******************************************/ +/* NEXTION */ +//#define NEXTION_RX 255 /* Nextion RX pin */ +//#define NEXTION_TX 255 /* Nextion TX pin */ +//#define NEXTION_WEATHER_LAT "55.7512" /* Nextion latitude for display Weather */ +//#define NEXTION_WEATHER_LON "37.6184" /* Nextion longitude for display Weather */ +//#define NEXTION_WEATHER_KEY "" /* Openweathermap API key https://openweathermap.org/appid */ + /* I2C PINS */ //#define I2C_SDA 21 /* I2C SDA pin. It is best to connect to pin 21. */ //#define I2C_SCL 22 /* I2C SCL pin. It is best to connect to pin 22. */ @@ -120,11 +127,11 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti * xTaskCreateUniversal(_async_service_task, "async_tcp", 8192 / 2, NULL, 3, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); */ /* VU settings. See the default settings for your display in file yoRadio/display_vu.h */ -/*****************************************************************************************************************************************************************************/ -/* vu left | vu top | band width | band height | band space | num of bands | max samples | horisontal | Max Bands Color | Min Bands Color */ -/*****************************************************************************************************************************************************************************/ -//#define VU_PARAMS { VU_X = 4, VU_Y = 60, VU_BW = 10, VU_BH = 34, VU_BS = 2, VU_NB = 8, VU_BMS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = SILVER } -/******************************************/ +/************************************************************************************************************************************************************************************/ +/* vu left | vu top | band width | band height | band space | num of bands | fade speed | horisontal | Max Bands Color | Min Bands Color */ +/************************************************************************************************************************************************************************************/ +//#define VU_PARAMS2 { VU_X = 4, VU_Y = 60, VU_BW = 10, VU_BH = 34, VU_BS = 2, VU_NB = 8, VU_FS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = SILVER } +/************************************************************************************************************************************************************************************/ /* IR control */ //#define IR_PIN 255 diff --git a/images/board.jpg b/images/board.jpg index d2b91fb..b61f2e0 100644 Binary files a/images/board.jpg and b/images/board.jpg differ diff --git a/images/board2.jpg b/images/board2.jpg index 1767176..4298c65 100644 Binary files a/images/board2.jpg and b/images/board2.jpg differ diff --git a/nextion/NX4024K032.HMI b/nextion/NX4024K032.HMI new file mode 100644 index 0000000..361003e Binary files /dev/null and b/nextion/NX4024K032.HMI differ diff --git a/nextion/README.md b/nextion/README.md new file mode 100644 index 0000000..a6d0ae9 --- /dev/null +++ b/nextion/README.md @@ -0,0 +1,17 @@ +
+ +#### Tested to work with display models: +- NX4024K032 400x240 3.2' https://aliexpress.com/item/4001118814367.html + +#### HOWTO +- Download & install Nextion Editor (ver 1.63.3 or higher) https://nextion.tech/nextion-editor/ +- Connect your display to USB port via UART adapter +- Open HMI project and Upload it + +#### Connection options for ESP32 +- NEXTION_RX - Nextion RX pin (any available GPIO beginning with 13) +- NEXTION_TX - Nextion TX pin (any available GPIO beginning with 13) +- NEXTION_WEATHER_LAT - Nextion latitude for display Weather (e.g "48.8542") +- NEXTION_WEATHER_LON - Nextion longitude for display Weather (e.g "2.3325") +- NEXTION_WEATHER_KEY - Openweathermap API key https://openweathermap.org/appid \ +See examples/myoptions.h for details diff --git a/nextion/images/nextion_preview.png b/nextion/images/nextion_preview.png new file mode 100644 index 0000000..1b9fbfd Binary files /dev/null and b/nextion/images/nextion_preview.png differ diff --git a/yoRadio/audiohandlers.ino b/yoRadio/audiohandlers.ino index a59c18d..8369efd 100644 --- a/yoRadio/audiohandlers.ino +++ b/yoRadio/audiohandlers.ino @@ -1,6 +1,12 @@ void audio_info(const char *info) { if(config.store.audioinfo) telnet.printf("##AUDIO.INFO#: %s\n", info); - if (strstr(info, "failed!") != NULL || strstr(info, " 404") != NULL) { +#ifdef USE_NEXTION + if (strstr(info, "format is aac") != NULL) nextion.bitratePic(ICON_AAC); + if (strstr(info, "format is flac") != NULL) nextion.bitratePic(ICON_FLAC); + if (strstr(info, "format is mp3") != NULL) nextion.bitratePic(ICON_MP3); + if (strstr(info, "format is wav") != NULL) nextion.bitratePic(ICON_WAV); +#endif + if (strstr(info, "failed!") != NULL || strstr(info, " 404") != NULL || strstr(info, " 403") != NULL || strstr(info, "address is empty") != NULL) { config.setTitle("[request failed]"); netserver.requestOnChange(TITLE, 0); player.setOutputPins(false); @@ -13,8 +19,11 @@ void audio_info(const char *info) { void audio_bitrate(const char *info) { - telnet.printf("%s %s\n", "##AUDIO.BITRATE#:", info); + if(config.store.audioinfo) telnet.printf("%s %s\n", "##AUDIO.BITRATE#:", info); config.station.bitrate = atoi(info) / 1000; +#ifdef USE_NEXTION + nextion.bitrate(config.station.bitrate); +#endif netserver.requestOnChange(BITRATE, 0); } diff --git a/yoRadio/config.cpp b/yoRadio/config.cpp index 8c9506f..4cdd2b7 100644 --- a/yoRadio/config.cpp +++ b/yoRadio/config.cpp @@ -220,7 +220,7 @@ void Config::loadStation(uint16_t ls) { playlist.close(); } -void Config::fillPlMenu(char plmenu[][40], int from, byte count) { +void Config::fillPlMenu(char plmenu[][40], int from, byte count, bool removeNum) { int ls = from; byte c = 0; bool finded = false; @@ -248,9 +248,13 @@ void Config::fillPlMenu(char plmenu[][40], int from, byte count) { while (playlist.available()) { if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { #ifdef PL_WITH_NUMBERS - char buf[BUFLEN+10]; - sprintf(buf, "%d %s", (int)(from+c), sName); - strlcpy(plmenu[c], buf, 39); + if(removeNum){ + strlcpy(plmenu[c], sName, 39); + }else{ + char buf[BUFLEN+10]; + sprintf(buf, "%d %s", (int)(from+c), sName); + strlcpy(plmenu[c], buf, 39); + } #else strlcpy(plmenu[c], sName, 39); #endif diff --git a/yoRadio/config.h b/yoRadio/config.h index a199b04..900281a 100644 --- a/yoRadio/config.h +++ b/yoRadio/config.h @@ -90,7 +90,7 @@ class Config { void setSmartStart(byte ss); void initPlaylist(); void indexPlaylist(); - void fillPlMenu(char plmenu[][40], int from, byte count); + void fillPlMenu(char plmenu[][40], int from, byte count, bool removeNum=false); void setTimezone(int8_t tzh, int8_t tzm); void setTimezoneOffset(uint16_t tzo); uint16_t getTimezoneOffset(); diff --git a/yoRadio/display.cpp b/yoRadio/display.cpp index affa523..0e067d2 100644 --- a/yoRadio/display.cpp +++ b/yoRadio/display.cpp @@ -12,9 +12,15 @@ DspCore dsp; Display display; +#ifdef USE_NEXTION +Nextion nextion; +#endif #ifndef DUMMYDISPLAY +/******************************************************************************************************************/ void ticks() { + network.timeinfo.tm_sec ++; + mktime(&network.timeinfo); display.putRequest({CLOCK,0}); } @@ -219,6 +225,9 @@ void loopCore0( void * pvParameters ){ } void Display::init() { +#ifdef USE_NEXTION + nextion.begin(); +#endif dsp.initD(screenwidth, screenheight); dsp.drawLogo(); meta.init(1, " * ", META_SIZE, TFT_FRAMEWDT, STARTTIME, TFT_LOGO, TFT_BG); @@ -261,9 +270,15 @@ void Display::start(bool reboot) { clear(); if (network.status != CONNECTED) { apScreen(); +#ifdef USE_NEXTION + nextion.apScreen(); +#endif return; } mode = PLAYER; +#ifdef USE_NEXTION + nextion.putcmd("page player"); +#endif if(!reboot){ config.setTitle("[READY]"); //loop(); @@ -284,10 +299,13 @@ void Display::clear() { } void Display::swichMode(displayMode_e newmode) { +#ifdef USE_NEXTION + nextion.swichMode(newmode); +#endif if (newmode == VOL) { volDelay = millis(); } - if (newmode == mode) return; + if (newmode == mode || network.status != CONNECTED) return; clear(); mode = newmode; if (newmode != STATIONS) { @@ -332,6 +350,9 @@ void Display::swichMode(displayMode_e newmode) { if (newmode == UPDATING) { dsp.frameTitle("* UPDATING *"); } + if (newmode == INFO || newmode == SETTINGS || newmode == TIMEZONE || newmode == WIFI) { + dsp.frameTitle("* NEXTION *"); + } if (newmode == NUMBERS) { //dsp.frameTitle("STATION"); meta.reset(); @@ -348,8 +369,8 @@ void Display::swichMode(displayMode_e newmode) { void Display::drawPlayer() { if (clockRequest) { //getLocalTime(&network.timeinfo); - network.timeinfo.tm_sec ++; - mktime(&network.timeinfo); + //network.timeinfo.tm_sec ++; + //mktime(&network.timeinfo); time(); clockRequest = false; } @@ -358,6 +379,16 @@ void Display::drawPlayer() { if (TITLE_SIZE2 != 0) title2.loop(); } +void Display::sendInfo(){ + if (clockRequest) { +#ifdef USE_NEXTION + if(mode==TIMEZONE) nextion.localTime(network.timeinfo); + if(mode==INFO) nextion.rssi(); +#endif + clockRequest = false; + } +} + void Display::drawVolume() { if (millis() - volDelay > 3000) { volDelay = millis(); @@ -373,15 +404,21 @@ void Display::drawPlaylist() { char buf[PLMITEMLENGHT]; dsp.drawPlaylist(currentPlItem, buf); plCurrent.setText(dsp.utf8Rus(buf, true)); +#ifdef USE_NEXTION + nextion.drawPlaylist(currentPlItem); +#endif } void Display::drawNextStationNum(uint16_t num) { char plMenu[1][40]; char currentItemText[40] = {0}; - config.fillPlMenu(plMenu, num, 1); + config.fillPlMenu(plMenu, num, 1, true); strlcpy(currentItemText, plMenu[0], 39); meta.setText(dsp.utf8Rus(currentItemText, true)); dsp.drawNextStationNum(num); +#ifdef USE_NEXTION + nextion.drawNextStationNum(num); +#endif } void Display::putRequest(requestParams_t request){ @@ -391,6 +428,9 @@ void Display::putRequest(requestParams_t request){ void Display::loop() { if(displayQueue==NULL) return; +#ifdef USE_NEXTION + nextion.loop(); +#endif requestParams_t request; if(xQueueReceive(displayQueue, &request, 20)){ switch (request.type){ @@ -438,6 +478,11 @@ void Display::loop() { drawPlayer(); break; } + case INFO: + case TIMEZONE: { + sendInfo(); + break; + } case VOL: { drawVolume(); break; @@ -468,6 +513,9 @@ void Display::bootString(const char* text, byte y) { dsp.set_TextSize(1); dsp.centerText(text, y == 1 ? BOOTSTR_TOP1 : BOOTSTR_TOP2, TFT_LOGO, TFT_BG); dsp.loop(true); +#ifdef USE_NEXTION + if(y==2) nextion.bootString(text); +#endif } void Display::bootLogo() { @@ -481,6 +529,11 @@ void Display::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) { void Display::station() { meta.setText(dsp.utf8Rus(config.station.name, true)); +#ifdef USE_NEXTION + nextion.newNameset(config.station.name); + nextion.bitrate(config.station.bitrate); + nextion.bitratePic(ICON_NA); +#endif #ifdef DEBUG_TITLES meta.setText(dsp.utf8Rus("Utenim adminim veniam FM", true)); #endif @@ -490,6 +543,9 @@ void Display::station() { void Display::returnTile() { meta.setText(dsp.utf8Rus(config.station.name, true)); +#ifdef USE_NEXTION + nextion.newNameset(config.station.name); +#endif #ifdef DEBUG_TITLES meta.setText(dsp.utf8Rus("Utenim adminim veniam FM", true)); #endif @@ -520,7 +576,9 @@ void Display::title() { #endif title1.setText(dsp.utf8Rus(ttl, true)); if (TITLE_SIZE2 != 0) title2.setText(dsp.utf8Rus(sng, true)); - +#ifdef USE_NEXTION + nextion.newTitle(config.station.title); +#endif //dsp.loop(true); if (player_on_track_change) player_on_track_change(); } @@ -542,12 +600,12 @@ void Display::rssi() { void Display::ip() { if (dsp_before_ip) if (!dsp_before_ip(&dsp)) return; - dsp.ip(WiFi.localIP().toString().c_str()); + dsp.ip(network.status == CONNECTED?WiFi.localIP().toString().c_str():WiFi.softAPIP().toString().c_str()); } void Display::time(bool redraw) { if (dsp_before_clock) if (!dsp_before_clock(&dsp, dt)) return; - char timeStringBuff[20] = { 0 }; + char timeStringBuff[40] = { 0 }; (void)timeStringBuff; if (!dt) { heap(); @@ -562,6 +620,9 @@ void Display::time(bool redraw) { dsp.printClock(timeStringBuff); #else dsp.printClock(network.timeinfo, dt, redraw); +#endif +#ifdef USE_NEXTION + nextion.printClock(network.timeinfo); #endif dt = !dt; if (dsp_after_clock) dsp_after_clock(&dsp, dt); @@ -569,7 +630,35 @@ void Display::time(bool redraw) { void Display::volume() { dsp.drawVolumeBar(mode == VOL); +#ifdef USE_NEXTION + nextion.setVol(config.store.volume, mode == VOL); +#endif //netserver.requestOnChange(VOLUME, 0); } +/******************************************************************************************************************/ +#endif // !DUMMYDISPLAY +#ifdef DUMMYDISPLAY +/******************************************************************************************************************/ +void Display::bootString(const char* text, byte y) { + #ifdef USE_NEXTION + if(y==2) nextion.bootString(text); + #endif +} +void Display::init(){ + #ifdef USE_NEXTION + nextion.begin(true); + #endif +} +void Display::start(bool reboot){ + #ifdef USE_NEXTION + nextion.start(); + #endif +} +void Display::putRequest(requestParams_t request){ + #ifdef USE_NEXTION + nextion.putRequest(request); + #endif +} +/******************************************************************************************************************/ #endif // DUMMYDISPLAY diff --git a/yoRadio/display.h b/yoRadio/display.h index b0563b4..c484607 100644 --- a/yoRadio/display.h +++ b/yoRadio/display.h @@ -7,6 +7,7 @@ #include "config.h" #if DSP_MODEL==DSP_DUMMY +#define DUMMYDISPLAY #include "src/displays/displayDummy.h" #elif DSP_MODEL==DSP_ST7735 #include "src/displays/displayST7735.h" @@ -38,7 +39,7 @@ #include "src/displays/displayILI9225.h" #endif -enum displayMode_e { PLAYER, VOL, STATIONS, NUMBERS, LOST, UPDATING }; +enum displayMode_e { PLAYER, VOL, STATIONS, NUMBERS, LOST, UPDATING, INFO, SETTINGS, TIMEZONE, WIFI }; enum displayRequestType_e { NEWMODE, CLOCK, NEWTITLE, RETURNTITLE, NEWSTATION, NEXTSTATION, DRAWPLAYLIST, DRAWVOL }; struct requestParams_t @@ -47,6 +48,11 @@ struct requestParams_t int payload; }; +#if NEXTION_RX!=255 && NEXTION_TX!=255 +#define USE_NEXTION +#include "src/displays/nextion.h" +#endif + #ifndef DUMMYDISPLAY void loopCore0( void * pvParameters ); @@ -91,6 +97,7 @@ class Display { Scroll plCurrent; #endif bool busy; + bool dt; // dots public: Display() {}; #ifndef DUMMYDISPLAY @@ -105,23 +112,22 @@ class Display { void bootLogo(); void putRequest(requestParams_t request); #else - void init(){}; + void init(); void loop(){}; - void start(bool reboot=false){}; + void start(bool reboot=false); void stop(){}; void resetQueue(){}; void centerText(const char* text, byte y, uint16_t fg, uint16_t bg){}; void rightText(const char* text, byte y, uint16_t fg, uint16_t bg){}; - void bootString(const char* text, byte y){}; + void bootString(const char* text, byte y); void bootLogo(){}; - void putRequest(requestParams_t request){}; + void putRequest(requestParams_t request); #endif #ifndef DUMMYDISPLAY private: Ticker timer; Scroll meta, title1, title2; bool clockRequest; - bool dt; // dots unsigned long volDelay; void clear(); void heap(); @@ -130,6 +136,7 @@ class Display { void time(bool redraw = false); void apScreen(); void drawPlayer(); + void sendInfo(); void drawVolume(); void swichMode(displayMode_e newmode); void drawPlaylist(); diff --git a/yoRadio/display_vu.h b/yoRadio/display_vu.h index fd14e60..db34015 100644 --- a/yoRadio/display_vu.h +++ b/yoRadio/display_vu.h @@ -3,8 +3,8 @@ #define display_vu_h #include "player.h" -#ifdef VU_PARAMS -enum : uint16_t VU_PARAMS; +#ifdef VU_PARAMS2 +enum : uint16_t VU_PARAMS2; #else /* * vu left - left position @@ -13,29 +13,29 @@ enum : uint16_t VU_PARAMS; * band height - height of band * band space - space between bands * num of bands - num of bands - * max samples - for i2s dac: count of measurements before fixing the value + * fade speed - fade speed * horisontal - bands orientation * Max/Min Bands Color - color of bands */ /**********************************************************************************************************************************************************************************/ -/* vu left | vu top | band width | band height | band space | num of bands | max samples | horisontal | Max Bands Color | Min Bands Color */ +/* vu left | vu top | band width | band height | band space | num of bands | fade speed | horisontal | Max Bands Color | Min Bands Color */ /**********************************************************************************************************************************************************************************/ #if DSP_MODEL==DSP_ST7735 && DTYPE==INITR_BLACKTAB /* ST7735 160x128 */ -enum : uint16_t { VU_X = 4, VU_Y = 50, VU_BW = 10, VU_BH = 44, VU_BS = 2, VU_NB = 8, VU_BMS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = SILVER }; +enum : uint16_t { VU_X = 4, VU_Y = 50, VU_BW = 10, VU_BH = 44, VU_BS = 2, VU_NB = 8, VU_FS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; #elif DSP_MODEL==DSP_ST7735 && DTYPE==INITR_144GREENTAB /* ST7735 128x128 */ -enum : uint16_t { VU_X = 4, VU_Y = 45, VU_BW = 60, VU_BH = 8, VU_BS = 0, VU_NB = 10, VU_BMS = 3, VU_HOR = 1, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = SILVER }; +enum : uint16_t { VU_X = 4, VU_Y = 45, VU_BW = 60, VU_BH = 8, VU_BS = 0, VU_NB = 10, VU_FS = 2, VU_HOR = 1, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = DARK_GRAY }; #define GREENTAB128 #elif DSP_MODEL==DSP_ILI9341 /* ILI9341 320x240 */ -enum : uint16_t { VU_X = 4, VU_Y = 100, VU_BW = 20, VU_BH = 86, VU_BS = 4, VU_NB = 10, VU_BMS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; +enum : uint16_t { VU_X = 4, VU_Y = 100, VU_BW = 20, VU_BH = 86, VU_BS = 4, VU_NB = 10, VU_FS = 5, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; #elif DSP_MODEL==DSP_ST7789 /* ST7789 320x240 */ -enum : uint16_t { VU_X = 4, VU_Y = 100, VU_BW = 20, VU_BH = 86, VU_BS = 4, VU_NB = 10, VU_BMS = 3, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; +enum : uint16_t { VU_X = 4, VU_Y = 100, VU_BW = 20, VU_BH = 86, VU_BS = 4, VU_NB = 10, VU_FS = 3, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; #elif DSP_MODEL==DSP_ST7789_240 /* ST7789 240x240 */ -enum : uint16_t { VU_X = 4, VU_Y = 90, VU_BW = 120, VU_BH = 20, VU_BS = 0, VU_NB = 12, VU_BMS = 3, VU_HOR = 1, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; +enum : uint16_t { VU_X = 4, VU_Y = 90, VU_BW = 120, VU_BH = 20, VU_BS = 0, VU_NB = 12, VU_FS = 3, VU_HOR = 1, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; #elif DSP_MODEL==DSP_ILI9225 /* ILI9225 220x176 */ -enum : uint16_t { VU_X = 4, VU_Y = 74, VU_BW = 13, VU_BH = 60, VU_BS = 2, VU_NB = 10, VU_BMS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; +enum : uint16_t { VU_X = 4, VU_Y = 74, VU_BW = 13, VU_BH = 60, VU_BS = 2, VU_NB = 10, VU_FS = 3, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; #elif (DSP_MODEL==DSP_ST7735 && DTYPE==INITR_MINI160x80) || (DSP_MODEL==DSP_GC9106) /* ST7735 160x80, GC9106 160x80 */ -enum : uint16_t { VU_X = 1, VU_Y = 30, VU_BW = 12, VU_BH = 36, VU_BS = 4, VU_NB = 8, VU_BMS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; +enum : uint16_t { VU_X = 1, VU_Y = 30, VU_BW = 12, VU_BH = 36, VU_BS = 4, VU_NB = 8, VU_FS = 2, VU_HOR = 0, VU_COLOR_MAX = TFT_LOGO, VU_COLOR_MIN = GRAY }; #else #error YOUR DISPLAY DOES NOT SUPPORT ENABLE_VU_METER FEATURE YET #endif @@ -51,31 +51,37 @@ void drawVU(DspCore *dsp){ #ifdef GREENTAB128 if(display.mode==VOL) return; #endif +#if !defined(USE_NEXTION) && I2S_DOUT==255 player.getVUlevel(); - static uint16_t samples_cnt, measL, measR; - uint16_t bandColor; - samples_cnt++; - uint16_t dimension = VU_HOR?VU_BW:VU_BH; - uint8_t L = map((VS1053_CS!=255)?player.vuLeft:log(player.vuLeft)*38+45, 255, 0, 0, dimension); - uint8_t R = map((VS1053_CS!=255)?player.vuRight:log(player.vuRight)*38+45, 255, 0, 0, dimension); - if(player.isRunning()){ - if(L>measL) measL=L; - if(R>measR) measR=R; - }else{ - if(measL=measL)?measL+VU_FS:L; + measR=(R>=measR)?measR+VU_FS:R; + }else{ + if(measLdimension) measL=dimension; + if(measR>dimension) measR=dimension; uint8_t h=(dimension/VU_NB)-2; for(int i=0; iVU_BW-(VU_BW/VU_NB)*4)?VU_COLOR_MAX:VU_COLOR_MIN; gfxc.fillRect(i, 0, h, VU_BH, bandColor); gfxc.fillRect(i+VU_BW+VU_BS, 0, h, VU_BH, bandColor); + #else + bandColor = (i>(VU_BW/VU_NB))?VU_COLOR_MIN:VU_COLOR_MAX; + gfxc.fillRect(i, 0, h, VU_BH, bandColor); + bandColor = (i>VU_BW-(VU_BW/VU_NB)*3)?VU_COLOR_MAX:VU_COLOR_MIN; + gfxc.fillRect(i+VU_BW+VU_BS, 0, h, VU_BH, bandColor); + #endif }else{ bandColor = (i<(VU_BH/VU_NB)*3)?VU_COLOR_MAX:VU_COLOR_MIN; gfxc.fillRect(0, i, VU_BW, h, bandColor); @@ -84,19 +90,20 @@ void drawVU(DspCore *dsp){ } } if(VU_HOR){ + #ifndef BOOMBOX_STYLE gfxc.fillRect(VU_BW-measL, 0, measL, VU_BW, TFT_BG); gfxc.fillRect(VU_BW*2+VU_BS-measR, 0, measR, VU_BW, TFT_BG); dsp->drawRGBBitmap(VU_X, (display.mode==VOL && DSP_MODEL==DSP_ST7789_240)?VU_Y-40:VU_Y, gfxc.getBuffer(), VU_BW*2+VU_BS, VU_BH); + #else + gfxc.fillRect(0, 0, VU_BW-(VU_BW-measL), VU_BW, TFT_BG); + gfxc.fillRect(VU_BW*2+VU_BS-measR, 0, measR, VU_BW, TFT_BG); + dsp->drawRGBBitmap(VU_X, (display.mode==VOL && DSP_MODEL==DSP_ST7789_240)?VU_Y-40:VU_Y, gfxc.getBuffer(), VU_BW*2+VU_BS, VU_BH); + #endif }else{ gfxc.fillRect(0, 0, VU_BW, measL, TFT_BG); gfxc.fillRect(VU_BW+VU_BS, 0, VU_BW, measR, TFT_BG); dsp->drawRGBBitmap(VU_X, VU_Y, gfxc.getBuffer(), VU_BW*2+VU_BS, VU_BH); } - if(player.isRunning()){ - measL=0; - measR=0; - } } - #endif #endif diff --git a/yoRadio/netserver.cpp b/yoRadio/netserver.cpp index 14b7de0..df9785a 100644 --- a/yoRadio/netserver.cpp +++ b/yoRadio/netserver.cpp @@ -380,7 +380,7 @@ 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: - Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); + if(config.store.audioinfo) Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); netserver.requestOnChange(STATION, client->id()); netserver.requestOnChange(TITLE, client->id()); @@ -393,7 +393,7 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp break; case WS_EVT_DISCONNECT: - Serial.printf("WebSocket client #%u disconnected\n", client->id()); + if(config.store.audioinfo) Serial.printf("WebSocket client #%u disconnected\n", client->id()); break; case WS_EVT_DATA: netserver.onWsMessage(arg, data, len); diff --git a/yoRadio/network.cpp b/yoRadio/network.cpp index f57b834..bc76cb9 100644 --- a/yoRadio/network.cpp +++ b/yoRadio/network.cpp @@ -66,6 +66,9 @@ void Network::begin() { //getLocalTime(&timeinfo); stimer.once_ms(200,getFirstTime); ntimer.attach_ms(TSYNC_DELAY, syncTime); +#ifdef USE_NEXTION + nextion.startWeather(); +#endif if (network_on_connect) network_on_connect(); } diff --git a/yoRadio/options.h b/yoRadio/options.h index 59f12c4..8dc37b0 100644 --- a/yoRadio/options.h +++ b/yoRadio/options.h @@ -1,7 +1,7 @@ #ifndef options_h #define options_h -#define VERSION "0.6.530" +#define VERSION "0.7.000" /******************************************************* DO NOT EDIT THIS FILE. @@ -57,6 +57,23 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #define TFT_DC 4 #endif +/* NEXTION */ +#ifndef NEXTION_RX + #define NEXTION_RX 255 +#endif +#ifndef NEXTION_TX + #define NEXTION_TX 255 +#endif +#ifndef NEXTION_WEATHER_LAT + #define NEXTION_WEATHER_LAT "55.7512" +#endif +#ifndef NEXTION_WEATHER_LON + #define NEXTION_WEATHER_LON "37.6184" +#endif +#ifndef NEXTION_WEATHER_KEY + #define NEXTION_WEATHER_KEY "" +#endif + /* OLED I2C DISPLAY */ #ifndef I2C_SDA #define I2C_SDA 13 diff --git a/yoRadio/player.cpp b/yoRadio/player.cpp index dee38fd..c6f4013 100644 --- a/yoRadio/player.cpp +++ b/yoRadio/player.cpp @@ -164,7 +164,7 @@ void Player::stepVol(bool up) { } byte Player::volToI2S(byte volume) { - int vol = map(volume, 0, 254 - config.station.ovol * 2 , 0, 254); + int vol = map(volume, 0, 254 - config.station.ovol * 3 , 0, 254); if (vol > 254) vol = 254; if (vol < 0) vol = 0; return vol; diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index 54c31f5..e6ecb0a 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -2157,6 +2157,11 @@ bool Audio::pauseResume() { bool Audio::playChunk() { // If we've got data, try and pump it out.. int16_t sample[2]; + /* VU Meter ************************************************************************************************************/ + /* По мотивам https://github.com/schreibfaul1/ESP32-audioI2S/pull/170/commits/6cce84217e5bc8f2f8925936affc84576932a29b */ + uint8_t maxl = 0, maxr = 0; + uint8_t minl = 0xFF, minr = 0xFF; + /************************************************************************************************************ VU Meter */ if(getBitsPerSample() == 8) { if(getChannels() == 1) { while(m_validSamples) { @@ -2164,11 +2169,19 @@ bool Audio::playChunk() { uint8_t y = (m_outBuff[m_curSample] & 0xFF00) >> 8; sample[LEFTCHANNEL] = x; sample[RIGHTCHANNEL] = x; + if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL]; + if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL]; while(1) { if(playSample(sample)) break; } // Can't send? sample[LEFTCHANNEL] = y; sample[RIGHTCHANNEL] = y; + if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL]; + if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL]; while(1) { if(playSample(sample)) break; } // Can't send? @@ -2189,7 +2202,10 @@ bool Audio::playChunk() { sample[LEFTCHANNEL] = xy; sample[RIGHTCHANNEL] = xy; } - + if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL]; + if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL]; while(1) { if(playSample(sample)) break; } // Can't send? @@ -2197,6 +2213,8 @@ bool Audio::playChunk() { m_curSample++; } } + vuLeft = maxl - minl; + vuRight = maxr - minr; m_curSample = 0; return true; } @@ -2205,6 +2223,10 @@ bool Audio::playChunk() { while(m_validSamples) { sample[LEFTCHANNEL] = m_outBuff[m_curSample]; sample[RIGHTCHANNEL] = m_outBuff[m_curSample]; + if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL]; + if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL]; if(!playSample(sample)) { return false; } // Can't send @@ -2223,6 +2245,10 @@ bool Audio::playChunk() { sample[LEFTCHANNEL] = xy; sample[RIGHTCHANNEL] = xy; } + if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL]; + if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL]; + if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL]; if(!playSample(sample)) { return false; } // Can't send @@ -2230,6 +2256,8 @@ bool Audio::playChunk() { m_curSample++; } } + vuLeft = maxl - minl; + vuRight = maxr - minr; m_curSample = 0; return true; } @@ -4269,8 +4297,6 @@ int32_t Audio::Gain(int16_t s[2]) { step = step * m_balance * 16; r = (uint8_t)(step); } - vuLeft = s[LEFTCHANNEL] >> 7; - vuRight = s[RIGHTCHANNEL] >> 7; v[LEFTCHANNEL] = (s[LEFTCHANNEL] * (m_vol - l)) >> 8; v[RIGHTCHANNEL]= (s[RIGHTCHANNEL] * (m_vol - r)) >> 8; diff --git a/yoRadio/src/audioVS1053/audioVS1053Ex.cpp b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp index 6c5725e..57e60d3 100644 --- a/yoRadio/src/audioVS1053/audioVS1053Ex.cpp +++ b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp @@ -467,6 +467,7 @@ void Audio::stopSong() //--------------------------------------------------------------------------------------------------------------------- void Audio::softReset() { + if(VS1053_RST>0) return; // Hard resrt present write_register(SCI_MODE, _BV (SM_SDINEW) | _BV(SM_RESET)); delay(10); await_data_request(); @@ -1568,7 +1569,7 @@ void Audio::setDefaults(){ * \n The VU meter takes about 0.2MHz of processing power with 48 kHz samplerate. */ void Audio::setVUmeter() { - if(!ENABLE_VU_METER) return; +// if(!ENABLE_VU_METER) return; uint16_t MP3Status = read_register(SCI_STATUS); write_register(SCI_STATUS, MP3Status | _BV(9)); } @@ -1586,12 +1587,14 @@ void Audio::setVUmeter() { * \warning This feature is only available with patches that support VU meter. */ void Audio::getVUlevel() { - if(!ENABLE_VU_METER) return; +// if(!ENABLE_VU_METER) return; int16_t reg = read_register(SCI_AICTRL3); - uint8_t rl = map((uint8_t)reg, 81, 92, 0, 255); - uint8_t rr = map((uint8_t)(reg >> 8), 81, 92, 0, 255); - if(rl>30 || !isRunning()) vuLeft = rl; - if(rr>30 || !isRunning()) vuRight = rr; + uint8_t rl = map((uint8_t)reg, 85, 92, 0, 255); + uint8_t rr = map((uint8_t)(reg >> 8), 85, 92, 0, 255); + //if(rl>30 || !isRunning()) vuLeft = rl; + //if(rr>30 || !isRunning()) vuRight = rr; + vuLeft = rl; + vuRight = rr; } //--------------------------------------------------------------------------------------------------------------------- bool Audio::connecttohost(String host){ diff --git a/yoRadio/src/displays/nextion.cpp b/yoRadio/src/displays/nextion.cpp new file mode 100644 index 0000000..eb3d80b --- /dev/null +++ b/yoRadio/src/displays/nextion.cpp @@ -0,0 +1,778 @@ +#include "../../options.h" + +#if NEXTION_RX!=255 && NEXTION_TX!=255 +#include "nextion.h" +#include "../../config.h" + +#include "../../player.h" +#include "../../controls.h" +#include "../../netserver.h" +#include "../../network.h" + +#ifndef CORE_STACK_SIZE +#define CORE_STACK_SIZE 1024*3 +#endif + +HardwareSerial hSerial(1); // use UART1 +Ticker weatherticker; +//char weather[254] = { 0 }; +//bool weatherRequest = false; + + +const char *ndow[7] = {"воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"}; +const char *nmnths[12] = {"января","февраля","марта","апреля","мая","июня","июля","августа","сентября","октября","ноября","декабря"}; + +#ifdef DUMMYDISPLAY +void ticks() { + network.timeinfo.tm_sec ++; + mktime(&network.timeinfo); + nextion.putRequest({CLOCK,0}); + if(nextion.mode==TIMEZONE) nextion.localTime(network.timeinfo); + if(nextion.mode==INFO) nextion.rssi(); + if(nextion.dt){ + int rssi = WiFi.RSSI(); + netserver.setRSSI(rssi); + } + nextion.dt=!nextion.dt; +} +#endif + +Nextion::Nextion() { + +} + +void nextionCore0( void * pvParameters ){ + delay(500); + while(true){ +// if(displayQueue==NULL) break; + nextion.loop(); + vTaskDelay(5); + } + vTaskDelete( NULL ); +} + +void Nextion::createCore0Task(){ + xTaskCreatePinnedToCore( + nextionCore0, /* Task function. */ + "TaskCore0", /* name of task. */ + CORE_STACK_SIZE, /* Stack size of task */ + NULL, /* parameter of the task */ + 4, /* no one flies higher than the Toruk */ + &_TaskCore0, /* Task handle to keep track of created task */ + !xPortGetCoreID()); /* pin task to core 0 */ +} + +void Nextion::begin(bool dummy) { + _dummyDisplay=dummy; + hSerial.begin(NEXTION_BAUD, SERIAL_8N1, NEXTION_RX, NEXTION_TX); + if (!hSerial) { + Serial.println("Invalid HardwareSerial pin configuration, check config"); + while (1) { + delay (1000); + } + } + rx_pos = 0; + _volInside=false; + snprintf(_espcoreversion, sizeof(_espcoreversion) - 1, "%d.%d.%d", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH); + putcmd("rest"); + delay(200); + putcmd("bkcmd=0"); +// putcmd("page boot"); + if(dummy) { + _displayQueue = xQueueCreate( 5, sizeof( requestParams_t ) ); + createCore0Task(); + } +} + +void Nextion::start(){ + if (network.status != CONNECTED) { + apScreen(); + return; + } +#ifdef DUMMYDISPLAY + if(_dummyDisplay) _timer.attach_ms(1000, ticks); + display.mode = PLAYER; + config.setTitle("[READY]"); +#endif + mode = PLAYER; + putcmd("page player"); + delay(100); +#ifdef DUMMYDISPLAY + newNameset(config.station.name); + newTitle(config.station.title); +#endif + setVol(config.store.volume, mode == VOL); +} + +void Nextion::apScreen() { + putcmd("apscreenlock=1"); + putcmd("page settings_wifi"); + //char cmd[20]; + /*for(int i=0;i<5;i++){ + snprintf(cmd, sizeof(cmd)-1, "vis b%d,%d", i, 0); + putcmd(cmd); + }*/ + //putcmd("vis btnBack,0"); + +} + +void Nextion::putRequest(requestParams_t request){ + if(_displayQueue==NULL) return; + xQueueSend(_displayQueue, &request, portMAX_DELAY); +} + +void Nextion::processQueue(){ + if(_displayQueue==NULL) return; + requestParams_t request; + if(xQueueReceive(_displayQueue, &request, 20)){ + switch (request.type){ + case NEWMODE: { + swichMode((displayMode_e)request.payload); + break; + } + case CLOCK: { + printClock(network.timeinfo); + break; + } + case NEWTITLE: { + newTitle(config.station.title); + break; + } + case RETURNTITLE: { + //returnTile(); + break; + } + case NEWSTATION: { + newNameset(config.station.name); + bitrate(config.station.bitrate); + bitratePic(ICON_NA); + break; + } + case NEXTSTATION: { + drawNextStationNum((displayMode_e)request.payload); + break; + } + case DRAWPLAYLIST: { + int p = request.payload ? display.currentPlItem + 1 : display.currentPlItem - 1; + if (p < 1) p = config.store.countStation; + if (p > config.store.countStation) p = 1; + display.currentPlItem = p; + drawPlaylist(display.currentPlItem); + break; + } + case DRAWVOL: { + if(!_volInside){ + setVol(config.store.volume, mode == VOL); + } + _volInside=false; + break; + } + } + } + switch (mode) { + case PLAYER: { + //drawPlayer(); + break; + } + case INFO: + case TIMEZONE: { + //sendInfo(); + break; + } + case VOL: { + if (millis() - _volDelay > 3000) { + _volDelay = millis(); + swichMode(PLAYER); + } + break; + } + case NUMBERS: { + //meta.loop(); + break; + } + case STATIONS: { + //plCurrent.loop(); + break; + } + default: + break; + } +} + +void Nextion::loop() { + processQueue(); + drawVU(); + char RxTemp; + char scanBuf[50]; + int scanDigit; (void)scanDigit; + static String wifisettings; + if (hSerial.available() > 4) { + RxTemp = hSerial.read(); + if (RxTemp != '^') { + return; + }else{ + rx_pos = 0; + rxbuf[rx_pos] = '\0'; + } + while (hSerial.available()) { + RxTemp = hSerial.read(); + if (RxTemp == '^') { + rx_pos = 0; + rxbuf[rx_pos] = '\0'; + continue; + } + if (RxTemp != '$') { + rxbuf[rx_pos] = RxTemp; + rx_pos++; + } else { + rxbuf[rx_pos] = '\0'; + rx_pos = 0; + if (sscanf(rxbuf, "page=%s", scanBuf) == 1){ + if(strcmp(scanBuf, "player") == 0) display.putRequest({NEWMODE, PLAYER}); + if(strcmp(scanBuf, "playlist") == 0) display.putRequest({NEWMODE, STATIONS}); + if(strcmp(scanBuf, "info") == 0) { + putcmd("yoversion.txt", VERSION); + putcmd("espcore.txt", _espcoreversion); + putcmd("ipaddr.txt", WiFi.localIP().toString().c_str()); + putcmd("ssid.txt", WiFi.SSID().c_str()); + display.putRequest({NEWMODE, INFO}); + } + if(strcmp(scanBuf, "eq") == 0) { + putcmd("t4.txt", config.store.balance, true); + putcmd("h0.val", config.store.balance+16); + putcmd("t5.txt", config.store.trebble, true); + putcmd("h1.val", config.store.trebble+16); + putcmd("t6.txt", config.store.middle, true); + putcmd("h2.val", config.store.middle+16); + putcmd("t7.txt", config.store.bass, true); + putcmd("h3.val", config.store.bass+16); + display.putRequest({NEWMODE, SETTINGS}); + } + if(strcmp(scanBuf, "wifi") == 0) { + if(mode != WIFI){ + char cell[10]; + wifisettings=""; + for(int i=0;i0) measL-=5; + if(measR>0) measR-=5; + } + if(measL>100) measL=0; + if(measR>100) measR=0; + fillVU(measL, measR); +} + +void Nextion::putcmd(const char* cmd) { + snprintf(txbuf, sizeof(txbuf) - 1, "%s\xFF\xFF\xFF", cmd); + hSerial.print(txbuf); +} + +void Nextion::putcmd(const char* cmd, const char* val, uint16_t dl) { + snprintf(txbuf, sizeof(txbuf) - 1, "%s=\"%s\"\xFF\xFF\xFF", cmd, val); + hSerial.print(txbuf); + if(dl>0) delay(dl); +} + +void Nextion::putcmd(const char* cmd, int val, bool toString, uint16_t dl) { + if(toString){ + snprintf(txbuf, sizeof(txbuf) - 1, "%s=\"%d\"\xFF\xFF\xFF", cmd, val); + }else{ + snprintf(txbuf, sizeof(txbuf) - 1, "%s=%d\xFF\xFF\xFF", cmd, val); + } + hSerial.print(txbuf); + if(dl>0) delay(dl); +} + +void Nextion::putcmdf(const char* fmt, int val, uint16_t dl) { + snprintf(txbuf, sizeof(txbuf) - 1, fmt, val); + hSerial.print(txbuf); + hSerial.print("\xFF\xFF\xFF"); + if(dl>0) delay(dl); +} + +void Nextion::bitrate(int bpm){ + if(bpm>0){ + putcmd("player.bitrate.txt", bpm, true); + }else{ + putcmd("player.bitrate.txt=\"und\""); + } +} + +void Nextion::rssi(){ + putcmdf("rssi.txt=\"%d dBm\"", WiFi.RSSI()); +} + +void Nextion::weatherVisible(uint8_t vis){ + putcmd("weatherVisible", vis, false, 20); + putcmdf("vis press_img,%d", vis, 20); + putcmdf("vis press_txt,%d", vis, 20); + putcmdf("vis hum_img,%d", vis, 20); + putcmdf("vis hum_txt,%d", vis, 20); + putcmdf("vis temp_img,%d", vis, 20); + putcmdf("vis temp_txt,%d", vis, 20); + putcmdf("vis cond_img,%d", vis, 20); +} + +void Nextion::bitratePic(uint8_t pic){ + putcmd("player.bitrate.pic", pic); +} + +void Nextion::bootString(const char* bs) { + char buf[50] = { 0 }; + strlcpy(buf, bs, 50); + putcmd("boot.bootstring.txt", utf8Rus(buf, false)); +} + +void Nextion::newNameset(const char* meta){ + char newnameset[59] = { 0 }; + strlcpy(newnameset, meta, 59); + putcmd("player.meta.txt", utf8Rus(newnameset, true)); +} + +void Nextion::setVol(uint8_t vol, bool dialog){ + if(dialog){ + putcmd("dialog.text.txt", vol, true); + }/*else{ + putcmd("player.volText.txt", vol, true); + putcmd("player.volumeSlider.val", vol); + }*/ + putcmd("player.volText.txt", vol, true); + putcmd("player.volumeSlider.val", vol); +} + +void Nextion::fillVU(uint8_t LC, uint8_t RC){ + putcmd("player.vul.val", LC); + putcmd("player.vur.val", RC); +} + +void Nextion::newTitle(const char* title){ + char ttl[50] = { 0 }; + char sng[50] = { 0 }; + if (strlen(title) > 0) { + char* ici; + if ((ici = strstr(title, " - ")) != NULL) { + strlcpy(sng, ici + 3, 50); + strlcpy(ttl, title, strlen(title) - strlen(ici) + 1); + } else { + strlcpy(ttl, title, 50); + sng[0] = '\0'; + } + putcmd("player.title1.txt", utf8Rus(ttl, true)); + putcmd("player.title2.txt", utf8Rus(sng, true)); + } +} + +void Nextion::printClock(struct tm timeinfo){ + char timeStringBuff[70] = { 0 }; + strftime(timeStringBuff, sizeof(timeStringBuff), "player.clock.txt=\"%H:%M\"", &timeinfo); + putcmd(timeStringBuff); + putcmdf("player.secText.txt=\"%02d\"", timeinfo.tm_sec); + snprintf(timeStringBuff, sizeof(timeStringBuff), "player.dateText.txt=\"%s, %d %s %d\"", ndow[timeinfo.tm_wday], timeinfo.tm_mday, nmnths[timeinfo.tm_mon], timeinfo.tm_year+1900); + putcmd(utf8Rus(timeStringBuff, false)); +} + +void Nextion::localTime(struct tm timeinfo){ + char timeStringBuff[40] = { 0 }; + strftime(timeStringBuff, sizeof(timeStringBuff), "localTime.txt=\"%H:%M:%S\"", &timeinfo); + putcmd(timeStringBuff); +} + +void Nextion::drawPlaylist(uint16_t currentPlItem){ + char plMenu[7][40]; + for (byte i = 0; i < 7; i++) { + plMenu[i][0] = '\0'; + } + config.fillPlMenu(plMenu, currentPlItem - 3, 7); + char cmd[60]={0}; + for (byte i = 0; i < 7; i++) { + snprintf(cmd, sizeof(cmd) - 1, "t%d.txt=\"%s\"", i, nextion.utf8Rus(plMenu[i], true)); + putcmd(cmd); + } +} + +void Nextion::drawNextStationNum(uint16_t num) {//dialog + char plMenu[1][40]; + char currentItemText[40] = {0}; + config.fillPlMenu(plMenu, num, 1, true); + strlcpy(currentItemText, plMenu[0], 39); + //meta.setText(dsp.utf8Rus(currentItemText, true)); + putcmd("dialog.title.txt", utf8Rus(currentItemText, true)); + putcmd("dialog.text.txt", num, true); + //dsp.drawNextStationNum(num); +} + +void Nextion::swichMode(displayMode_e newmode){ + if (newmode == VOL) { + _volDelay = millis(); + } + if (newmode == mode) return; + mode = newmode; +#ifdef DUMMYDISPLAY + display.mode = newmode; +#endif +/* if (newmode != STATIONS) { + ip(); + volume(); + }*/ + if (newmode == PLAYER) { + putcmd("page player"); + putcmd("dialog.title.txt", ""); + putcmd("dialog.text.txt", ""); + } + if (newmode == VOL) { + putcmd("dialog.title.txt", "VOLUME"); + putcmd("page dialog"); + putcmd("icon.pic", 65); + } + if (newmode == LOST) { + putcmd("page lost"); + } + if (newmode == UPDATING) { + putcmd("page updating"); + } + if (newmode == NUMBERS) { + putcmd("page dialog"); + putcmd("icon.pic", 63); + } + if (newmode == STATIONS) { + putcmd("page playlist"); +#ifdef DUMMYDISPLAY + display.currentPlItem = config.store.lastStation; +#endif + drawPlaylist(config.store.lastStation); + } +} + +bool Nextion::getForecast(){ + WiFiClient client; + const char* host = "api.openweathermap.org"; + if (!client.connect(host, 80)) { + Serial.println("## OPENWEATHERMAP ###: connection failed"); + return false; + } + char httpget[250] = {0}; + sprintf(httpget, "GET /data/2.5/weather?lat=%s&lon=%s&units=metric&lang=ru&appid=%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", NEXTION_WEATHER_LAT, NEXTION_WEATHER_LON, NEXTION_WEATHER_KEY, host); + client.print(httpget); + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 2000UL) { + Serial.println("## OPENWEATHERMAP ###: 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("## OPENWEATHERMAP ###: client read timeout !"); + return false; + } + } + } + if (strstr(line.c_str(), "\"temp\"") == NULL) { + Serial.println("## OPENWEATHERMAP ###: weather not found !"); + return false; + } + char *tmpe; + char *tmps; + 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("## OPENWEATHERMAP ###: description not found !"); return false;} + tmps += 15; + tmpe = strstr(tmps, "\",\""); + if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: 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("## OPENWEATHERMAP ###: icon not found !"); return false;} + tmps += 8; + tmpe = strstr(tmps, "\"}"); + if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: icon not found !"); return false;} + strlcpy(icon, tmps, tmpe - tmps + 1); + cursor = tmpe + 2; + + tmps = strstr(cursor, "\"temp\":"); + if (tmps == NULL) { Serial.println("## OPENWEATHERMAP ###: temp not found !"); return false;} + tmps += 7; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: temp not found !"); return false;} + strlcpy(temp, tmps, tmpe - tmps + 1); + cursor = tmpe + 2; + float tempf = atof(temp); + tmps = strstr(cursor, "\"pressure\":"); + if (tmps == NULL) { Serial.println("## OPENWEATHERMAP ###: pressure not found !"); return false;} + tmps += 11; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: 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("## OPENWEATHERMAP ###: humidity not found !"); return false;} + tmps += 10; + tmpe = strstr(tmps, ",\""); + if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: humidity not found !"); return false;} + strlcpy(hum, tmps, tmpe - tmps + 1); + + if(config.store.audioinfo) Serial.printf("## OPENWEATHERMAP ###: description: %s, temp:%.1f C, pressure:%dmmHg, humidity:%s%%\n", desc, tempf, pressi, hum); + + putcmdf("press_txt.txt=\"%dmm\"", pressi); + putcmdf("hum_txt.txt=\"%d%%\"", atoi(hum)); + char cmd[30]; + snprintf(cmd, sizeof(cmd)-1,"temp_txt.txt=\"%.1f\"", tempf); + 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; + } + putcmd("cond_img.pic", 50+iconofset); + weatherVisible(1); + return true; +} + +void Nextion::getWeather(void * pvParameters){ + delay(200); + if (nextion.getForecast()) { +// nextion.weatherRequest = true; + weatherticker.detach(); + weatherticker.attach(WEATHER_REQUEST_INTERVAL, nextion.updateWeather); + } else { + weatherticker.detach(); + weatherticker.attach(WEATHER_REQUEST_INTERVAL_FAULTY, nextion.updateWeather); + } + vTaskDelete( NULL ); +} + +void Nextion::updateWeather() { + xTaskCreatePinnedToCore( + nextion.getWeather, /* Task function. */ + "nextiongetWeather", /* name of task. */ + 1024 * 4, /* Stack size of task */ + NULL, /* parameter of the task */ + 0, /* priority of the task */ + &nextion.weatherUpdateTaskHandle, /* Task handle to keep track of created task */ + 0); /* pin task to core CORE_FOR_LOOP_CONTROLS */ +} + +void Nextion::startWeather(){ + if(strlen(NEXTION_WEATHER_KEY)==0) { + Serial.println("## OPENWEATHERMAP ###: ERROR: NEXTION_WEATHER_KEY not configured"); + return; + } + updateWeather(); /* pin task to core CORE_FOR_LOOP_CONTROLS */ +} + +/* + По мотивам https://forum.amperka.ru/threads/%D0%94%D0%B8%D1%81%D0%BF%D0%BB%D0%B5%D0%B9-nextion-%D0%B0%D0%B7%D1%8B-arduino-esp8266.9204/page-18#post-173442 +*/ +char* Nextion::utf8Rus(char* str, bool uppercase) { + int index = 0; + static char out[BUFLEN]; + bool E = false; + memset(out, 0, sizeof(out)); + if (uppercase) { + bool next = false; + for (char *iter = str; *iter != '\0'; ++iter) + { + if (E) { + E = false; + continue; + } + byte rus = (byte) * iter; + if (rus == 208 && (byte) * (iter + 1) == 129) { + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (rus == 209 && (byte) * (iter + 1) == 145) { + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (next) { + if (rus >= 128 && rus <= 143) *iter = (char)(rus + 32); + if (rus >= 176 && rus <= 191) *iter = (char)(rus - 32); + next = false; + } + if (rus == 208) next = true; + if (rus == 209) { + *iter = (char)208; + next = true; + } + *iter = toupper(*iter); + } + } + uint32_t codepoint = 0; + while (str[index]) + { + uint8_t ch = (uint8_t) (str[index]); + if (ch <= 0x7f) + codepoint = ch; + else if (ch <= 0xbf) + codepoint = (codepoint << 6) | (ch & 0x3f); + else if (ch <= 0xdf) + codepoint = ch & 0x1f; + else if (ch <= 0xef) + codepoint = ch & 0x0f; + else + codepoint = ch & 0x07; + ++index; + if (((str[index] & 0xc0) != 0x80) && (codepoint <= 0x10ffff)) + { + if (codepoint <= 255) + { + out[strlen(out)]=(uint8_t)codepoint; + } + else + { + if(codepoint > 0x400){ + out[strlen(out)]=(uint8_t)(codepoint - 0x360); + } + } + } + } + out[strlen(out)+1]=0; + return out; +} + +#endif //NEXTION_RX!=255 && NEXTION_TX!=255 diff --git a/yoRadio/src/displays/nextion.h b/yoRadio/src/displays/nextion.h new file mode 100644 index 0000000..844722b --- /dev/null +++ b/yoRadio/src/displays/nextion.h @@ -0,0 +1,78 @@ +#ifndef NEXTION_H +#define NEXTION_H + +//#include +#include +#include "../../display.h" + +#define TXBUFLEN 255 +#define RXBUFLEN 50 +//#define NEXTION_BAUD 38400 +//#define NEXTION_BAUD 74880 +#define NEXTION_BAUD 115200 + +#define ICON_NA 44 +#define ICON_AAC ICON_NA+1 +#define ICON_FLAC ICON_NA+2 +#define ICON_MP3 ICON_NA+3 +#define ICON_WAV ICON_NA+4 + +#define WEATHER_REQUEST_INTERVAL 1800 //30min +#define WEATHER_REQUEST_INTERVAL_FAULTY 30 + +class Nextion { + private: + char txbuf[TXBUFLEN]; + char rxbuf[RXBUFLEN]; + uint16_t rx_pos; + int _tchY; + char _espcoreversion[16]; + TaskHandle_t _TaskCore0=NULL; + QueueHandle_t _displayQueue=NULL; + bool _dummyDisplay; + bool _volInside; + Ticker _timer; + unsigned long _volDelay; + void createCore0Task(); + void processQueue(); + void drawVU(); + public: + displayMode_e mode; + bool dt; + TaskHandle_t weatherUpdateTaskHandle; +// bool weatherRequest; + public: + Nextion(); + void begin(bool dummy=false); + void start(); + void apScreen(); + void loop(); + void putcmd(const char* cmd); + void putcmd(const char* cmd, const char* val, uint16_t dl=0); + void putcmd(const char* cmd, int val, bool toString=false, uint16_t dl=0); + void putcmdf(const char* fmt, int val, uint16_t dl=0); + void bootString(const char* bs); + void newTitle(const char* title); + void newNameset(const char* meta); + void setVol(uint8_t vol, bool dialog); + void fillVU(uint8_t LC, uint8_t RC); + char* utf8Rus(char* str, bool uppercase); + void printClock(struct tm timeinfo); + void bitrate(int bpm); + void bitratePic(uint8_t pic); + void rssi(); + void weatherVisible(uint8_t vis); + void localTime(struct tm timeinfo); + void drawPlaylist(uint16_t currentPlItem); + void swichMode(displayMode_e newmode); + void drawNextStationNum(uint16_t num); + void putRequest(requestParams_t request); + void startWeather(); + bool getForecast(); + static void updateWeather(); + static void getWeather(void * pvParameters); +}; + +extern Nextion nextion; + +#endif diff --git a/yoRadio/telnet.cpp b/yoRadio/telnet.cpp index c410d8e..99533da 100644 --- a/yoRadio/telnet.cpp +++ b/yoRadio/telnet.cpp @@ -214,7 +214,7 @@ void Telnet::on_input(const char* str, byte clientId) { int sstart; if (sscanf(str, "smartstart(%d)", &sstart) == 1 || sscanf(str, "cli.smartstart(\"%d\")", &sstart) == 1 || sscanf(str, "smartstart %d", &sstart) == 1) { config.store.smartstart = (byte)sstart; - printf(clientId, "new smartstart value is: %d\n> ", config.store.audioinfo); + printf(clientId, "new smartstart value is: %d\n> ", config.store.smartstart); config.save(); return; }