Files
yoradio/yoRadio/display.cpp
2022-08-23 15:50:56 +03:00

845 lines
21 KiB
C++

#include "options.h"
#include "WiFi.h"
#include "time.h"
#include "display.h"
#if VU_READY==1
#include "display_vu.h"
#endif
#include "player.h"
#include "netserver.h"
#include "network.h"
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});
}
#ifndef STARTTIME
#define STARTTIME 5000
#endif
#ifndef STARTTIME_PL
#define STARTTIME_PL 0
#endif
#ifndef META_SIZE
#define META_SIZE 2
#endif
#ifndef TITLE_SIZE1
#define TITLE_SIZE1 1
#endif
#ifndef TITLE_SIZE2
#define TITLE_SIZE2 1
#endif
#ifndef TITLE_TOP1
#define TITLE_TOP1 TFT_FRAMEWDT + META_SIZE * TFT_LINEHGHT
#endif
#ifndef TITLE_TOP2
#define TITLE_TOP2 TFT_FRAMEWDT + (META_SIZE+2) * TFT_LINEHGHT
#endif
#ifndef TITLE_FG1
#define TITLE_FG1 TFT_FG
#endif
#ifndef TITLE_FG2
#define TITLE_FG2 TFT_FG
#endif
#ifndef BOOTSTR_TOP1
#define BOOTSTR_TOP1 90
#endif
#ifndef BOOTSTR_TOP2
#define BOOTSTR_TOP2 110
#endif
#ifndef PLCURRENT_SIZE
#define PLCURRENT_SIZE 2
#endif
#ifndef CLOCK_SPACE
#define DO_SCROLL (tWidth > display.screenwidth - TFT_FRAMEWDT * 2)
#else
#define DO_SCROLL (tWidth > (display.screenwidth - (dsp.fillSpaces?((texttop==0)?CLOCK_SPACE:VOL_SPACE):0)))
#endif
#ifndef CORE_STACK_SIZE
#define CORE_STACK_SIZE 1024*3
#endif
byte currentScrollId = 0; /* one scroll on one time */
#if WEATHER_READY==1
bool weatherRequest = false;
TaskHandle_t weatherUpdateTaskHandle;
Ticker weatherTicker;
char weatherText[254] = { 0 };
#endif
void Scroll::init(byte ScrollId, const char *sep, byte tsize, byte top, uint16_t dlay, uint16_t fgcolor, uint16_t bgcolor) {
textsize = tsize;
id = ScrollId;
if (textsize == 0) return;
texttop = top;
fg = fgcolor;
bg = bgcolor;
delayStartScroll = dlay;
memset(separator, 0, 4);
strlcpy(separator, sep, 4);
locked = false;
}
void Scroll::setText(const char *txt) {
if (textsize == 0) return;
memset(text, 0, BUFLEN / 2);
strlcpy(text, txt, BUFLEN / 2 - 1);
getbounds(textwidth, textheight, sepwidth);
if (!locked) {
clearscrolls();
reset();
}
lockRequest = false;
}
void Scroll::lock() {
locked = true;
}
void Scroll::unlock() {
locked = false;
}
void Scroll::reset() {
if (textsize == 0) return;
locked = false;
clear();
setTextParams();
dsp.set_Cursor(TFT_FRAMEWDT, texttop);
dsp.printText(text);
drawFrame();
if (currentScrollId == id) currentScrollId = 0;
}
void Scroll::setTextParams() {
if (textsize == 0) return;
dsp.set_TextSize(textsize);
dsp.set_TextColor(fg, bg);
}
void Scroll::clearscrolls() {
if (textsize == 0) return;
x = TFT_FRAMEWDT;
scrolldelay = millis();
//clear();
}
void Scroll::loop() {
if (lockRequest) {
return;
}
if (textsize == 0) return;
if (currentScrollId != 0 && currentScrollId != id) {
return;
}
if (checkdelay(x == TFT_FRAMEWDT ? delayStartScroll : SCROLLTIME, scrolldelay)) {
scroll();
sticks();
}
}
boolean Scroll::checkdelay(int m, unsigned long &tstamp) {
if (millis() - tstamp > m) {
tstamp = millis();
return true;
} else {
return false;
}
}
void Scroll::drawFrame() {
if (textsize == 0) return;
dsp.drawScrollFrame(texttop, textheight, bg);
}
void Scroll::sticks() {
if (!doscroll || locked || textsize == 0) return;
setTextParams();
dsp.set_Cursor(x, texttop);
dsp.printText(text);
dsp.printText(separator);
dsp.printText(text);
if (x == TFT_FRAMEWDT) drawFrame();
}
void Scroll::scroll() {
if (!doscroll || textsize == 0) return;
//if (textwidth > display.screenwidth) {
x -= SCROLLDELTA;
if (-x > textwidth + sepwidth - TFT_FRAMEWDT) {
x = TFT_FRAMEWDT;
drawFrame();
currentScrollId = 0;
} else {
currentScrollId = id;
}
//}
}
void Scroll::clear() {
if (textsize == 0) return;
dsp.clearScroll(texttop, textheight, bg);
}
void Scroll::getbounds(uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth) {
if (textsize == 0) return;
if (strlen(text) == 0) {
dsp.getScrolBbounds("EMPTY", separator, textsize, tWidth, tHeight, sWidth);
} else {
dsp.getScrolBbounds(text, separator, textsize, tWidth, tHeight, sWidth);
}
doscroll = DO_SCROLL;
}
TaskHandle_t TaskCore0;
QueueHandle_t displayQueue;
void Display::createCore0Task(){
xTaskCreatePinnedToCore(
loopCore0, /* 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 loopCore0( void * pvParameters ){
delay(500);
while(!display.busy){
if(displayQueue==NULL) break;
display.loop();
vTaskDelay(10);
}
vTaskDelete( NULL );
TaskCore0=NULL;
}
void Display::init() {
#ifdef USE_NEXTION
nextion.begin();
#endif
dsp.initD(screenwidth, screenheight);
dsp.drawLogo();
meta.init(1, " * ", META_SIZE, TFT_FRAMEWDT, STARTTIME, config.theme.meta, config.theme.background);
title1.init(2, " * ", TITLE_SIZE1, TITLE_TOP1, STARTTIME, config.theme.title1, config.theme.background);
title2.init(3, " * ", TITLE_SIZE2, TITLE_TOP2, STARTTIME, config.theme.title2, config.theme.background);
int yStart = (screenheight / 2 - PLMITEMHEIGHT / 2) + 3;
#ifdef PL_TOP
yStart = PL_TOP;
#endif
plCurrent.init(4, " * ", PLCURRENT_SIZE, yStart, STARTTIME_PL, config.theme.background, config.theme.meta);
plCurrent.lock();
#if WEATHER_READY==1
if (DSP_MODEL == DSP_ST7735 || (DSP_MODEL == DSP_SSD1327)) {
weatherScroll.init(5, " * ", 1, TFT_LINEHGHT * 4 + 6, 0, config.theme.weather, config.theme.background);
}else if(DSP_MODEL == DSP_ILI9225){
weatherScroll.init(5, " * ", 1, TFT_LINEHGHT * 6 + 5, 0, config.theme.weather, config.theme.background);
} else {
weatherScroll.init(5, " * ", 2, TFT_LINEHGHT * 9 + 5, 0, config.theme.weather, config.theme.background);
}
#endif
if (dsp_on_init) dsp_on_init();
}
void Display::apScreen() {
#ifndef PL_TOP
meta.setText(dsp.utf8Rus("ёRADIO * ёRADIO * ёRADIO", false));
#endif
dsp.apScreen();
}
void Display::stop() {
busy = true;
swichMode(PLAYER);
timer.detach();
resetQueue();
vQueueDelete(displayQueue);
displayQueue=NULL;
delay(100);
bootLogo();
bootString("reloading", 1);
busy = false;
}
void Display::start(bool reboot) {
busy = false;
displayQueue = xQueueCreate( 5, sizeof( requestParams_t ) );
createCore0Task();
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();
}
ip();
volume();
station();
rssi();
time(reboot);
if(reboot) title();
timer.attach_ms(1000, ticks);
if (dsp_on_start) dsp_on_start(&dsp);
// Экстреминатус секвестирован /*дважды*/ /*трижды*/ /*четырежды*/ пятирежды
}
void Display::clear() {
dsp.clearDsp();
}
void Display::swichMode(displayMode_e newmode) {
#ifdef USE_NEXTION
nextion.swichMode(newmode);
#endif
if (newmode == VOL) {
volDelay = millis();
}
if (newmode == mode || network.status != CONNECTED) return;
clear();
mode = newmode;
if (newmode != STATIONS) {
ip();
volume();
}
if (newmode == PLAYER) {
currentScrollId = 0;
meta.reset();
title1.reset();
if (TITLE_SIZE2 != 0) title2.reset();
//player.loop();
plCurrent.lock();
time(true);
#ifdef CLOCK_SPACE // if set space for clock in 1602 displays
dsp.fillSpaces = true;
ip();
rssi();
volume();
#endif
dsp.loop(true);
} else {
if (newmode != NUMBERS) {
meta.lock();
}
title1.lock();
if (TITLE_SIZE2 != 0) title2.lock();
#ifdef CLOCK_SPACE
dsp.fillSpaces = false;
#endif
}
if (newmode == VOL) {
#ifdef IP_INST_VOL
dsp.frameTitle(WiFi.localIP().toString().c_str());
#else
dsp.frameTitle("VOLUME");
#endif
}
if (newmode == LOST) {
dsp.frameTitle("* LOST *");
}
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();
}
if (newmode == STATIONS) {
currentPlItem = config.store.lastStation;
plCurrent.reset();
currentScrollId = 0;
drawPlaylist();
}
if (dsp_on_newmode) dsp_on_newmode(newmode);
}
void Display::drawPlayer() {
if (clockRequest) {
//getLocalTime(&network.timeinfo);
//network.timeinfo.tm_sec ++;
//mktime(&network.timeinfo);
time();
clockRequest = false;
}
meta.loop();
title1.loop();
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();
swichMode(PLAYER);
}
}
void Display::resetQueue(){
xQueueReset(displayQueue);
}
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, 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){
if(displayQueue==NULL) return;
xQueueSend(displayQueue, &request, portMAX_DELAY);
}
void Display::loop() {
if(displayQueue==NULL) return;
#ifdef USE_NEXTION
nextion.loop();
#endif
requestParams_t request;
if(xQueueReceive(displayQueue, &request, 20)){
switch (request.type){
case NEWMODE: {
swichMode((displayMode_e)request.payload);
break;
}
case CLOCK: {
clockRequest = true;
break;
}
case NEWTITLE: {
title();
break;
}
case RETURNTITLE: {
returnTile();
break;
}
case NEWSTATION: {
station();
break;
}
case NEXTSTATION: {
drawNextStationNum((displayMode_e)request.payload);
break;
}
case DRAWPLAYLIST: {
int p = request.payload ? currentPlItem + 1 : currentPlItem - 1;
if (p < 1) p = config.store.countStation;
if (p > config.store.countStation) p = 1;
currentPlItem = p;
drawPlaylist();
break;
}
case DRAWVOL: {
volume();
break;
}
}
}
switch (mode) {
case PLAYER: {
drawPlayer();
#if WEATHER_READY==1
weatherScroll.loop();
#endif
break;
}
case INFO:
case TIMEZONE: {
sendInfo();
break;
}
case VOL: {
drawVolume();
break;
}
case NUMBERS: {
meta.loop();
break;
}
case STATIONS: {
plCurrent.loop();
break;
}
default:
break;
}
dsp.loop();
if (dsp_on_loop) dsp_on_loop(&dsp);
#if VU_READY==1
drawVU(&dsp);
#endif
#if WEATHER_READY==1
if (weatherRequest) {
weatherRequest = false;
weatherScroll.setText(dsp.utf8Rus(weatherText, true));
}
#endif
}
void Display::centerText(const char* text, byte y, uint16_t fg, uint16_t bg) {
dsp.centerText(text, y, fg, bg);
}
void Display::bootString(const char* text, byte y) {
dsp.set_TextSize(1);
dsp.centerText(text, y == 1 ? BOOTSTR_TOP1 : BOOTSTR_TOP2, DSP_OLED?1:0xE68B, 0x0000);
dsp.loop(true);
#ifdef USE_NEXTION
if(y==2) nextion.bootString(text);
#endif
}
void Display::bootLogo() {
clear();
dsp.drawLogo();
}
void Display::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) {
dsp.rightText(text, y, fg, 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
//dsp.loop(true);
//netserver.requestOnChange(STATION, 0);
}
void Display::returnTile() {
meta.setText(dsp.utf8Rus(config.station.name, true));
#ifdef USE_NEXTION
nextion.newNameset(config.station.name);
#endif
meta.reset();
//dsp.loop(true);
}
char *split(char *str, const char *delim) {
char *dmp = strstr(str, delim);
if (dmp == NULL) return NULL;
*dmp = '\0';
return dmp + strlen(delim);
}
void Display::title() {
DBGVB("call of %s(), config.station.title=%s", __func__, config.station.title);
if (strlen(config.station.title) > 0) {
char tmpbuf[strlen(config.station.title)+1];
strlcpy(tmpbuf, config.station.title, strlen(config.station.title)+1);
char *stitle = split(tmpbuf, " - ");
if(stitle && TITLE_SIZE2 != 0){
title1.setText(dsp.utf8Rus(tmpbuf, true));
title2.setText(dsp.utf8Rus(stitle, true));
}else{
title1.setText(dsp.utf8Rus(tmpbuf, true));
title2.setText(dsp.utf8Rus("", true));
}
#ifdef USE_NEXTION
nextion.newTitle(config.station.title);
#endif
if (player_on_track_change) player_on_track_change();
}
}
void Display::heap() {
if (config.store.audioinfo) dsp.displayHeapForDebug();
}
void Display::rssi() {
int rssi = WiFi.RSSI();
netserver.setRSSI(rssi);
if (dsp_before_rssi) if (!dsp_before_rssi(&dsp)) return;
char buf[20];
sprintf(buf, "%ddBm", rssi);
dsp.rssi(buf);
}
void Display::ip() {
if (dsp_before_ip) if (!dsp_before_ip(&dsp)) return;
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[40] = { 0 };
(void)timeStringBuff;
if (!dt) {
heap();
rssi();
}
#ifndef TFT_FULLTIME
if (!dt) {
strftime(timeStringBuff, sizeof(timeStringBuff), "%H:%M", &network.timeinfo);
} else {
strftime(timeStringBuff, sizeof(timeStringBuff), "%H %M", &network.timeinfo);
}
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);
}
void Display::volume() {
dsp.drawVolumeBar(mode == VOL);
#ifdef USE_NEXTION
nextion.setVol(config.store.volume, mode == VOL);
#endif
//netserver.requestOnChange(VOLUME, 0);
}
void Display::flip(){
dsp.flip();
}
void Display::invert(){
dsp.invert();
}
#if DSP_MODEL==DSP_NOKIA5110
void Display::setContrast(){
dsp.setContrast(config.store.contrast);
}
#endif // DSP_MODEL==DSP_NOKIA5110
bool Display::deepsleep(){
//#ifdef DSP_CAN_SLEEP
#if defined(LCD_I2C) || DSP_OLED || BRIGHTNESS_PIN!=255
dsp.sleep();
return true;
#endif
return false;
}
void Display::wakeup(){
//#ifdef DSP_CAN_SLEEP
#if defined(LCD_I2C) || DSP_OLED || BRIGHTNESS_PIN!=255
dsp.wake();
#endif
}
/******************************************************************************************************************/
#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
#ifndef DUMMYDISPLAY
#if WEATHER_READY==1
bool 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", config.store.weatherlat, config.store.weatherlon, config.store.weatherkey, 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);
Serial.printf("## OPENWEATHERMAP ###: description: %s, temp:%.1f C, pressure:%dmmHg, humidity:%s%%\n", desc, tempf, pressi, hum);
sprintf(weatherText, "%s, %.1f C * давление: %d мм * влажность: %s%%", desc, tempf, pressi, hum);
return true;
}
void Display::getWeather( void * pvParameters ) {
delay(200);
if (getForecast()) {
weatherRequest = true;
weatherTicker.detach();
weatherTicker.attach(WEATHER_REQUEST_INTERVAL, display.updateWeather);
} else {
weatherTicker.detach();
weatherTicker.attach(WEATHER_REQUEST_INTERVAL_FAULTY, display.updateWeather);
}
vTaskDelete( NULL );
}
#endif // WEATHER_READY==1
void Display::updateWeather(){
#if WEATHER_READY==1
if(!config.store.showweather || strlen(config.store.weatherkey)==0) return;
xTaskCreatePinnedToCore(
getWeather, /* Task function. */
"dspGetWeather1", /* name of task. */
1024 * 4, /* Stack size of task */
NULL, /* parameter of the task */
0, /* priority of the task */
&weatherUpdateTaskHandle, /* Task handle to keep track of created task */
0); /* pin task to core CORE_FOR_LOOP_CONTROLS */
#endif // WEATHER_READY==1
}
void Display::showWeather(){
#if WEATHER_READY==1
if(strlen(config.store.weatherkey)!=0 && config.store.showweather) display.updateWeather();
if(!config.store.showweather){
memset(weatherText, 0, sizeof(weatherText));
weatherScroll.setText(weatherText);
}
#endif // WEATHER_READY==1
}
/******************************************************************************************************************/
#endif // !DUMMYDISPLAY