#include "../core/options.h" #if NEXTION_RX!=255 && NEXTION_TX!=255 #include "nextion.h" #include "../core/config.h" #include "../core/player.h" #include "../core/controls.h" #include "../core/netserver.h" #include "../core/network.h" #ifndef CORE_STACK_SIZE #define CORE_STACK_SIZE 1024*3 #endif HardwareSerial hSerial(1); // use UART1 Nextion::Nextion() { } void nextionCore0( void * pvParameters ){ delay(500); while(true){ nextion.loop(); vTaskDelay(5); } vTaskDelete( NULL ); } void Nextion::begin(bool dummy) { _dummyDisplay=dummy; mode=LOST; 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(""); putcmd("rest"); delay(300); putcmd(""); putcmd("bkcmd=0"); // putcmd("page boot"); _displayQueue = xQueueCreate( 10, sizeof( requestParams_t ) ); if(dummy) { xTaskCreatePinnedToCore(nextionCore0, "TaskCore0", CORE_STACK_SIZE, NULL, 4, &_TaskCore0, !xPortGetCoreID()); } } void Nextion::start(){ Serial.print("##[BOOT]#\tNextion.start\t"); delay(100); if (network.status != CONNECTED) { apScreen(); return; } #ifdef DUMMYDISPLAY display.mode(PLAYER); config.setTitle(const_PlReady); #endif putRequest({NEWMODE, PLAYER}); putRequest({NEWSTATION, 0}); putRequest({NEWTITLE, 0}); putRequest({DRAWVOL, 0}); Serial.println("done"); } void Nextion::apScreen() { putcmd("apscreenlock=1"); putcmd("page settings_wifi"); } void Nextion::putRequest(requestParams_t request){ if(_displayQueue==NULL) return; xQueueSend(_displayQueue, &request, portMAX_DELAY); } #ifndef NEXTION_QUEUE_TICKS #define NEXTION_QUEUE_TICKS 8 #endif void Nextion::processQueue(){ if(_displayQueue==NULL) return; requestParams_t request; if(xQueueReceive(_displayQueue, &request, NEXTION_QUEUE_TICKS)){ switch (request.type){ case NEWMODE: swichMode((displayMode_e)request.payload); break; case CLOCK: printClock(network.timeinfo); break; case DSPRSSI: rssi(); break; case NEWTITLE: newTitle(config.station.title); break; case BOOTSTRING: { char buf[50]; snprintf(buf, 50, bootstrFmt, config.ssids[request.payload].ssid); bootString(buf); break; } case NEWSTATION: { newNameset(config.station.name); bitrate(config.station.bitrate); bitratePic(ICON_NA); break; } case SHOWWEATHER: weatherVisible(strlen(config.store.weatherkey)>0 && config.store.showweather); break; case NEXTSTATION: drawNextStationNum(request.payload); break; case DRAWPLAYLIST: drawPlaylist(request.payload); break; case DRAWVOL: { if(!_volInside){ setVol(config.store.volume, mode == VOL); } _volInside=false; break; } default: break; } } #ifdef DUMMYDISPLAY if(mode==VOL || mode==STATIONS || mode==NUMBERS ){ if (millis() - _volDelay > (mode==VOL?3000:30000)) { _volDelay = millis(); swichMode(PLAYER); } } #endif } 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", YOVERSION); 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;i config.store.countStation) p = 1; display.currentPlItem = p; display.putRequest(DRAWPLAYLIST, p); } if(strcmp(scanBuf, "go") == 0) { display.putRequest(NEWMODE, PLAYER); player.sendCommand({PR_PLAY, display.currentPlItem}); } if(strcmp(scanBuf, "toggle") == 0) { player.toggle(); } } if (sscanf(rxbuf, "vol=%d", &scanDigit) == 1){ _volInside = true; player.sendCommand({PR_VOL, scanDigit}); } if (sscanf(rxbuf, "balance=%d", &scanDigit) == 1){ config.setBalance((int8_t)scanDigit); player.setBalance(config.store.balance); netserver.requestOnChange(BALANCE, 0); } if (sscanf(rxbuf, "treble=%d", &scanDigit) == 1){ player.setTone(config.store.bass, config.store.middle, scanDigit); config.setTone(config.store.bass, config.store.middle, scanDigit); netserver.requestOnChange(EQUALIZER, 0); } if (sscanf(rxbuf, "middle=%d", &scanDigit) == 1){ player.setTone(config.store.bass, scanDigit, config.store.trebble); config.setTone(config.store.bass, scanDigit, config.store.trebble); netserver.requestOnChange(EQUALIZER, 0); } if (sscanf(rxbuf, "bass=%d", &scanDigit) == 1){ player.setTone(scanDigit, config.store.middle, config.store.trebble); config.setTone(scanDigit, config.store.middle, config.store.trebble); netserver.requestOnChange(EQUALIZER, 0); } if (sscanf(rxbuf, "tzhour=%d", &scanDigit) == 1){ config.setTimezone((int8_t)scanDigit, config.store.tzMin); 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); } network.forceTimeSync = true; } if (sscanf(rxbuf, "tzmin=%d", &scanDigit) == 1){ config.setTimezone(config.store.tzHour, (int8_t)scanDigit); 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); } network.forceTimeSync = true; } if (sscanf(rxbuf, "audioinfo=%d", &scanDigit) == 1){ config.saveValue(&config.store.audioinfo, static_cast(scanDigit)); } if (sscanf(rxbuf, "smartstart=%d", &scanDigit) == 1){ config.saveValue(&config.store.smartstart, static_cast(scanDigit==0?2:1)); } if (sscanf(rxbuf, "addssid=%s", scanBuf) == 1){ wifisettings+=(String(scanBuf)+"\t"); } if (sscanf(rxbuf, "addpass=%s", scanBuf) == 1){ wifisettings+=(String(scanBuf)+"\n"); } if (sscanf(rxbuf, "wifidone=%d", &scanDigit) == 1){ config.saveWifiFromNextion(wifisettings.c_str()); } } } } } void Nextion::drawVU(){ //if(mode!=PLAYER) return; if(mode!=PLAYER && mode!=VOL) return; static uint8_t measL, measR; //player.getVUlevel(); uint16_t vulevel = player.get_VUlevel((uint16_t)100); uint8_t L = (vulevel >> 8) & 0xFF; uint8_t R = vulevel & 0xFF; //uint8_t L = map(player.vuLeft, 0, 255, 0, 100); //uint8_t R = map(player.vuRight, 0, 255, 0, 100); if(player.isRunning()){ measL=(L<=measL)?measL-5:L; measR=(R<=measR)?measR-5:R; }else{ if(measL>0) 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=\" \""); } } 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::audioinfo(const char* info){ if (strstr(info, "format is aac") != NULL) bitratePic(ICON_AAC); if (strstr(info, "format is flac") != NULL) bitratePic(ICON_FLAC); if (strstr(info, "format is mp3") != NULL) bitratePic(ICON_MP3); if (strstr(info, "format is wav") != NULL) bitratePic(ICON_WAV); } 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); } 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[100] = { 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\"", dowf[timeinfo.tm_wday], timeinfo.tm_mday, mnths[timeinfo.tm_mon], timeinfo.tm_year+1900); putcmd(utf8Rus(timeStringBuff, false)); if(mode==TIMEZONE) localTime(network.timeinfo); if(mode==INFO) rssi(); } void Nextion::localTime(struct tm timeinfo){ char timeStringBuff[40] = { 0 }; strftime(timeStringBuff, sizeof(timeStringBuff), "localTime.txt=\"%H:%M:%S\"", &timeinfo); putcmd(timeStringBuff); } void Nextion::printPLitem(uint8_t pos, const char* item){ char cmd[60]={0}; snprintf(cmd, sizeof(cmd) - 1, "t%d.txt=\"%s\"", pos, nextion.utf8Rus((char*)item, true)); putcmd(cmd); } void Nextion::drawPlaylist(uint16_t currentPlItem){ mode=STATIONS; uint8_t lastPos = config.fillPlMenu(currentPlItem - 3, 7, true); if(lastPos<7){ for(int i=0;i<7-lastPos;i++){ nextion.printPLitem(lastPos+i, ""); } } _volDelay = millis(); } void Nextion::drawNextStationNum(uint16_t num) {//dialog putcmd("dialog.title.txt", utf8Rus(config.stationByNum(num), true)); putcmd("dialog.text.txt", num, true); _volDelay = millis(); } 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 == 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.lastStation(); #endif drawPlaylist(config.lastStation()); } } void Nextion::sleep(void) { putcmd("sleep=1"); } void Nextion::wake(void) { putcmd("sleep=0"); } /* По мотивам 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; } uint8_t rus = (uint8_t) * iter; if (rus == 208 && (uint8_t) * (iter + 1) == 129) { *iter = (char)209; *(iter + 1) = (char)145; E = true; continue; } if (rus == 209 && (uint8_t) * (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