#include "netserver.h" #include #include "config.h" #include "player.h" #include "telnet.h" #include "display.h" #include "options.h" #include "network.h" #include "mqtt.h" #include "controls.h" #include #include #ifdef USE_SD #include "sdmanager.h" #endif #ifndef MIN_MALLOC #define MIN_MALLOC 24112 #endif #ifndef NSQ_SEND_DELAY #define NSQ_SEND_DELAY (TickType_t)100 //portMAX_DELAY? #endif //#define CORS_DEBUG //Enable CORS policy: 'Access-Control-Allow-Origin' (for testing) NetServer netserver; AsyncWebServer webserver(80); AsyncWebSocket websocket("/ws"); AsyncUDP udp; String processor(const String& var); void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void handleUploadWeb(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void handleUpdate(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void handleHTTPArgs(AsyncWebServerRequest * request); void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); bool shouldReboot = false; #ifdef MQTT_ROOT_TOPIC Ticker mqttplaylistticker; bool mqttplaylistblock = false; void mqttplaylistSend() { mqttplaylistblock = true; mqttplaylistticker.detach(); mqttPublishPlaylist(); mqttplaylistblock = false; } #endif char* updateError() { static char ret[140] = {0}; sprintf(ret, "Update failed with error (%d)
%s", (int)Update.getError(), Update.errorString()); return ret; } bool NetServer::begin(bool quiet) { if(network.status==SDREADY) return true; if(!quiet) Serial.print("##[BOOT]#\tnetserver.begin\t"); importRequest = IMDONE; irRecordEnable = false; nsQueue = xQueueCreate( 20, sizeof( nsRequestParams_t ) ); while(nsQueue==NULL){;} if(config.emptyFS){ webserver.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { request->send_P(200, "text/html", emptyfs_html); }); webserver.on("/webboard", HTTP_POST, [](AsyncWebServerRequest *request) { request->redirect("/"); ESP.restart(); }, handleUploadWeb); webserver.on("/", HTTP_POST, [](AsyncWebServerRequest *request) { 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()); request->redirect("/"); config.saveWifiFromNextion(buf); return; } request->redirect("/"); ESP.restart(); }, handleUploadWeb); }else{ webserver.on("/", HTTP_ANY, handleHTTPArgs); webserver.on("/settings.html", HTTP_GET, handleHTTPArgs); webserver.on("/update.html", HTTP_GET, handleHTTPArgs); webserver.on("/ir.html", HTTP_GET, handleHTTPArgs); webserver.on("/webboard", HTTP_GET, [](AsyncWebServerRequest * request) { request->send_P(200, "text/html", emptyfs_html); }); webserver.on("/webboard", HTTP_POST, [](AsyncWebServerRequest *request) { request->redirect("/"); }, handleUploadWeb); } webserver.on(PLAYLIST_PATH, HTTP_GET, handleHTTPArgs); webserver.on(INDEX_PATH, HTTP_GET, handleHTTPArgs); webserver.on(PLAYLIST_SD_PATH, HTTP_GET, handleHTTPArgs); webserver.on(INDEX_SD_PATH, HTTP_GET, handleHTTPArgs); webserver.on(SSIDS_PATH, HTTP_GET, handleHTTPArgs); webserver.on("/upload", HTTP_POST, beginUpload, handleUpload); webserver.on("/update", HTTP_GET, handleHTTPArgs); webserver.on("/update", HTTP_POST, beginUpdate, handleUpdate); webserver.on("/variables.js", HTTP_GET, [](AsyncWebServerRequest * request) { 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); }); webserver.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=31536000"); #ifdef CORS_DEBUG DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Origin"), F("*")); DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Headers"), F("content-type")); #endif webserver.begin(); if(strlen(config.store.mdnsname)>0) MDNS.begin(config.store.mdnsname); websocket.onEvent(onWsEvent); webserver.addHandler(&websocket); //echo -n "helle?" | socat - udp-datagram:255.255.255.255:44490,broadcast if (udp.listen(44490)) { udp.onPacket([](AsyncUDPPacket packet) { if (strcmp((char*)packet.data(), "helle?") == 0) packet.println(WiFi.localIP()); }); } if(!quiet) Serial.println("done"); return true; } void NetServer::beginUpdate(AsyncWebServerRequest *request) { shouldReboot = !Update.hasError(); AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot ? "OK" : updateError()); response->addHeader("Connection", "close"); request->send(response); } void handleUpdate(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { if (!index) { int target = (request->getParam("updatetarget", true)->value() == "spiffs") ? U_SPIFFS : U_FLASH; Serial.printf("Update Start: %s\n", filename.c_str()); player.sendCommand({PR_STOP, 0}); display.putRequest(NEWMODE, UPDATING); if (!Update.begin(UPDATE_SIZE_UNKNOWN, target)) { Update.printError(Serial); request->send(200, "text/html", updateError()); } } if (!Update.hasError()) { if (Update.write(data, len) != len) { Update.printError(Serial); request->send(200, "text/html", updateError()); } } if (final) { if (Update.end(true)) { Serial.printf("Update Success: %uB\n", index + len); } else { Update.printError(Serial); request->send(200, "text/html", updateError()); } } } void NetServer::beginUpload(AsyncWebServerRequest *request) { if (request->hasParam("plfile", true, true)) { netserver.importRequest = IMPL; request->send(200); } else if (request->hasParam("wifile", true, true)) { netserver.importRequest = IMWIFI; request->send(200); } else { request->send(404); } } size_t NetServer::chunkedHtmlPageCallback(uint8_t* buffer, size_t maxLen, size_t index){ File requiredfile; bool sdpl = strcmp(netserver.chunkedPathBuffer, PLAYLIST_SD_PATH) == 0; if(sdpl){ requiredfile = config.SDPLFS()->open(netserver.chunkedPathBuffer, "r"); }else{ requiredfile = SPIFFS.open(netserver.chunkedPathBuffer, "r"); } if (!requiredfile) return 0; size_t filesize = requiredfile.size(); size_t needread = filesize - index; if (!needread) { requiredfile.close(); return 0; } size_t canread = (needread > maxLen) ? maxLen : needread; DBGVB("[%s] seek to %d in %s and read %d bytes with maxLen=%d", __func__, index, netserver.chunkedPathBuffer, canread, maxLen); requiredfile.seek(index, SeekSet); //vTaskDelay(1); requiredfile.read(buffer, canread); index += canread; if (requiredfile) requiredfile.close(); return canread; } void NetServer::chunkedHtmlPage(const String& contentType, AsyncWebServerRequest *request, const char * path, bool doproc) { memset(chunkedPathBuffer, 0, sizeof(chunkedPathBuffer)); strlcpy(chunkedPathBuffer, path, sizeof(chunkedPathBuffer)-1); AsyncWebServerResponse *response; if(doproc) response = request->beginChunkedResponse(contentType, chunkedHtmlPageCallback, processor); else response = request->beginChunkedResponse(contentType, chunkedHtmlPageCallback); request->send(response); } #ifndef DSP_NOT_FLIPPED #define DSP_CAN_FLIPPED true #else #define DSP_CAN_FLIPPED false #endif #if !defined(HIDE_WEATHER) && (!defined(DUMMYDISPLAY) && !defined(USE_NEXTION)) #define SHOW_WEATHER true #else #define SHOW_WEATHER false #endif #ifndef NS_QUEUE_TICKS #define NS_QUEUE_TICKS 0 #endif const char *getFormat(BitrateFormat _format) { switch (_format) { case BF_MP3: return "MP3"; case BF_AAC: return "AAC"; case BF_FLAC: return "FLC"; case BF_OGG: return "OGG"; case BF_WAV: return "WAV"; default: return "bitrate"; } } 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; switch (request.type) { case PLAYLIST: getPlaylist(clientId); break; case PLAYLISTSAVED: { #ifdef USE_SD if(config.getMode()==PM_SDCARD) { // config.indexSDPlaylist(); config.initSDPlaylist(); } #endif if(config.getMode()==PM_WEB){ config.indexPlaylist(); config.initPlaylist(); } getPlaylist(clientId); break; } case GETACTIVE: { bool dbgact = false, nxtn=false; String act = F("\"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\","); #ifdef USE_NEXTION act += F("\"group_nextion\","); if (!SHOW_WEATHER || dbgact) act += F("\"group_weather\","); nxtn=true; #endif #if defined(LCD_I2C) || defined(DSP_OLED) act += F("\"group_oled\","); #endif #ifndef HIDE_VU act += F("\"group_vu\","); #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()); 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); requestOnChange(VOLUME, clientId); requestOnChange(EQUALIZER, clientId); requestOnChange(BALANCE, clientId); requestOnChange(BITRATE, clientId); requestOnChange(MODE, clientId); requestOnChange(SDINIT, clientId); requestOnChange(GETPLAYERMODE, clientId); if (config.getMode()==PM_SDCARD) { requestOnChange(SDPOS, clientId); requestOnChange(SDLEN, clientId); requestOnChange(SDSNUFFLE, clientId); } return; break; } case GETSYSTEM: sprintf (wsbuf, "{\"sst\":%d,\"aif\":%d,\"vu\":%d,\"softr\":%d,\"vut\":%d,\"mdns\":\"%s\"}", config.store.smartstart != 2, config.store.audioinfo, config.store.vumeter, config.store.softapdelay, config.vuThreshold, config.store.mdnsname); 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}", config.store.flipscreen, config.store.invertdisplay, config.store.numplaylist, config.store.fliptouch, config.store.dbgtouch, config.store.dspon, config.store.brightness, config.store.contrast, config.store.screensaverEnabled, config.store.screensaverTimeout, config.store.screensaverBlank, config.store.screensaverPlayingEnabled, config.store.screensaverPlayingTimeout, config.store.screensaverPlayingBlank); break; case GETTIMEZONE: sprintf (wsbuf, "{\"tzh\":%d,\"tzm\":%d,\"sntp1\":\"%s\",\"sntp2\":\"%s\"}", config.store.tzHour, config.store.tzMin, config.store.sntp1, config.store.sntp2); break; case GETWEATHER: sprintf (wsbuf, "{\"wen\":%d,\"wlat\":\"%s\",\"wlon\":\"%s\",\"wkey\":\"%s\"}", config.store.showweather, config.store.weatherlat, config.store.weatherlon, config.store.weatherkey); break; 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 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}", 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; #ifdef USE_SD case CHANGEMODE: config.changeMode(newConfigMode); return; break; #endif default: break; } 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(); #endif } } } void NetServer::loop() { if(network.status==SDREADY) return; if (shouldReboot) { Serial.println("Rebooting..."); delay(100); ESP.restart(); } websocket.cleanupClients(); switch (importRequest) { case IMPL: importPlaylist(); importRequest = IMDONE; break; case IMWIFI: config.saveWifi(); importRequest = IMDONE; break; default: break; } //if (rssi < 255) requestOnChange(NRSSI, 0); processQueue(); } #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); } 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); } #endif void NetServer::onWsMessage(void *arg, uint8_t *data, size_t len, uint8_t clientId) { AwsFrameInfo *info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { data[len] = 0; char cmd[65], val[65]; if (config.parseWsCommand((const char*)data, cmd, val, 65)) { //if (strcmp(cmd, "getmode") == 0 ) { requestOnChange(GETMODE, clientId); return; } if (strcmp(cmd, "getindex") == 0 ) { requestOnChange(GETINDEX, clientId); return; } if (strcmp(cmd, "getsystem") == 0 ) { requestOnChange(GETSYSTEM, clientId); return; } if (strcmp(cmd, "getscreen") == 0 ) { requestOnChange(GETSCREEN, clientId); return; } if (strcmp(cmd, "gettimezone") == 0 ) { requestOnChange(GETTIMEZONE, clientId); return; } if (strcmp(cmd, "getcontrols") == 0 ) { requestOnChange(GETCONTROLS, clientId); return; } if (strcmp(cmd, "getweather") == 0 ) { requestOnChange(GETWEATHER, clientId); return; } if (strcmp(cmd, "getactive") == 0 ) { requestOnChange(GETACTIVE, clientId); return; } if (strcmp(cmd, "newmode") == 0 ) { newConfigMode = atoi(val); requestOnChange(CHANGEMODE, 0); return; } if (strcmp(cmd, "smartstart") == 0) { uint8_t valb = atoi(val); uint8_t ss = valb == 1 ? 1 : 2; if (!player.isRunning() && ss == 1) ss = 0; config.setSmartStart(ss); return; } if (strcmp(cmd, "audioinfo") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.audioinfo, valb); display.putRequest(AUDIOINFO); return; } if (strcmp(cmd, "vumeter") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.vumeter, valb); display.putRequest(SHOWVUMETER); return; } if (strcmp(cmd, "prev") == 0) { player.prev(); return; } if (strcmp(cmd, "toggle") == 0) { player.toggle(); return; } if (strcmp(cmd, "next") == 0) { player.next(); return; } if (strcmp(cmd, "volm") == 0) { player.stepVol(false); return; } if (strcmp(cmd, "volp") == 0) { player.stepVol(true); return; } if (strcmp(cmd, "play") == 0) { uint16_t valb = atoi(val); player.sendCommand({PR_PLAY, valb}); return; } if (strcmp(cmd, "softap") == 0) { uint8_t valb = atoi(val); config.saveValue(&config.store.softapdelay, valb); return; } if (strcmp(cmd, "mdnsname") == 0) { config.saveValue(config.store.mdnsname, val, MDNS_LENGTH); return; } if (strcmp(cmd, "rebootmdns") == 0) { 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(clientId, buf); delay(500); ESP.restart(); return; } if (strcmp(cmd, "invertdisplay") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.invertdisplay, valb); display.invert(); return; } if (strcmp(cmd, "numplaylist") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.numplaylist, valb); display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); return; } if (strcmp(cmd, "fliptouch") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.fliptouch, valb); flipTS(); return; } if (strcmp(cmd, "dbgtouch") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.dbgtouch, valb); return; } if (strcmp(cmd, "flipscreen") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.flipscreen, valb); display.flip(); display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); return; } if (strcmp(cmd, "brightness") == 0) { uint8_t valb = atoi(val); if (!config.store.dspon) requestOnChange(DSPON, 0); config.store.brightness = valb; config.setBrightness(true); return; } if (strcmp(cmd, "screenon") == 0) { bool valb = static_cast(atoi(val)); config.setDspOn(valb); return; } if (strcmp(cmd, "contrast") == 0) { uint8_t valb = atoi(val); config.saveValue(&config.store.contrast, valb); display.setContrast(); return; } if (strcmp(cmd, "screensaverenabled") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.screensaverEnabled, valb); #ifndef DSP_LCD display.putRequest(NEWMODE, PLAYER); #endif return; } if (strcmp(cmd, "screensavertimeout") == 0) { uint16_t valb = atoi(val); valb = constrain(valb,5,65520); config.saveValue(&config.store.screensaverTimeout, valb); #ifndef DSP_LCD display.putRequest(NEWMODE, PLAYER); #endif return; } if (strcmp(cmd, "screensaverblank") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.screensaverBlank, valb); #ifndef DSP_LCD display.putRequest(NEWMODE, PLAYER); #endif return; } if (strcmp(cmd, "screensaverplayingenabled") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.screensaverPlayingEnabled, valb); #ifndef DSP_LCD display.putRequest(NEWMODE, PLAYER); #endif return; } if (strcmp(cmd, "screensaverplayingtimeout") == 0) { uint16_t valb = atoi(val); valb = constrain(valb,1,1080); config.saveValue(&config.store.screensaverPlayingTimeout, valb); #ifndef DSP_LCD display.putRequest(NEWMODE, PLAYER); #endif return; } if (strcmp(cmd, "screensaverplayingblank") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.screensaverPlayingBlank, valb); #ifndef DSP_LCD display.putRequest(NEWMODE, PLAYER); #endif return; } if (strcmp(cmd, "tzh") == 0) { int8_t vali = atoi(val); config.saveValue(&config.store.tzHour, vali); return; } if (strcmp(cmd, "tzm") == 0) { int8_t vali = atoi(val); config.saveValue(&config.store.tzMin, vali); return; } if (strcmp(cmd, "sntp2") == 0) { config.saveValue(config.store.sntp2, val, 35, false); return; } if (strcmp(cmd, "sntp1") == 0) { strlcpy(config.store.sntp1, val, 35); bool tzdone = false; 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); tzdone = true; } else if (strlen(config.store.sntp1) > 0) { configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1); tzdone = true; } if (tzdone) { network.forceTimeSync = true; config.saveValue(config.store.sntp1, val, 35); } return; } if (strcmp(cmd, "volsteps") == 0) { uint8_t valb = atoi(val); config.saveValue(&config.store.volsteps, valb); return; } if (strcmp(cmd, "encacceleration") == 0) { uint16_t valb = atoi(val); setEncAcceleration(valb); config.saveValue(&config.store.encacc, valb); return; } if (strcmp(cmd, "irtlp") == 0) { uint8_t valb = atoi(val); setIRTolerance(valb); return; } if (strcmp(cmd, "oneclickswitching") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.skipPlaylistUpDown, valb); return; } if (strcmp(cmd, "showweather") == 0) { bool valb = static_cast(atoi(val)); config.saveValue(&config.store.showweather, valb); network.trueWeather=false; network.forceWeather = true; display.putRequest(SHOWWEATHER); return; } if (strcmp(cmd, "lat") == 0) { config.saveValue(config.store.weatherlat, val, 10, false); return; } if (strcmp(cmd, "lon") == 0) { config.saveValue(config.store.weatherlon, val, 10, false); return; } if (strcmp(cmd, "key") == 0) { config.saveValue(config.store.weatherkey, val, WEATHERKEY_LENGTH); network.trueWeather=false; display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); return; } /* RESETS */ if (strcmp(cmd, "reset") == 0) { if (strcmp(val, "system") == 0) { config.saveValue(&config.store.smartstart, (uint8_t)2, false); config.saveValue(&config.store.audioinfo, false, false); config.saveValue(&config.store.vumeter, false, false); config.saveValue(&config.store.softapdelay, (uint8_t)0, false); snprintf(config.store.mdnsname, MDNS_LENGTH, "yoradio-%x", config.getChipId()); config.saveValue(config.store.mdnsname, config.store.mdnsname, MDNS_LENGTH, true, true); display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); requestOnChange(GETSYSTEM, clientId); return; } if (strcmp(val, "screen") == 0) { config.saveValue(&config.store.flipscreen, false, false); display.flip(); config.saveValue(&config.store.invertdisplay, false, false); display.invert(); config.saveValue(&config.store.dspon, true, false); config.store.brightness = 100; config.setBrightness(false); config.saveValue(&config.store.contrast, (uint8_t)55, false); display.setContrast(); config.saveValue(&config.store.numplaylist, false); config.saveValue(&config.store.screensaverEnabled, false); config.saveValue(&config.store.screensaverTimeout, (uint16_t)20); config.saveValue(&config.store.screensaverBlank, false); config.saveValue(&config.store.screensaverPlayingEnabled, false); config.saveValue(&config.store.screensaverPlayingTimeout, (uint16_t)5); config.saveValue(&config.store.screensaverPlayingBlank, false); display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); requestOnChange(GETSCREEN, clientId); return; } if (strcmp(val, "timezone") == 0) { config.saveValue(&config.store.tzHour, (int8_t)3, false); config.saveValue(&config.store.tzMin, (int8_t)0, false); config.saveValue(config.store.sntp1, "pool.ntp.org", 35, false); config.saveValue(config.store.sntp2, "0.ru.pool.ntp.org", 35); configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1, config.store.sntp2); network.forceTimeSync = true; requestOnChange(GETTIMEZONE, clientId); return; } if (strcmp(val, "weather") == 0) { config.saveValue(&config.store.showweather, false, false); config.saveValue(config.store.weatherlat, "55.7512", 10, false); config.saveValue(config.store.weatherlon, "37.6184", 10, false); config.saveValue(config.store.weatherkey, "", WEATHERKEY_LENGTH); network.trueWeather=false; display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER); requestOnChange(GETWEATHER, clientId); return; } if (strcmp(val, "controls") == 0) { config.saveValue(&config.store.volsteps, (uint8_t)1, false); config.saveValue(&config.store.fliptouch, false, false); config.saveValue(&config.store.dbgtouch, false, false); config.saveValue(&config.store.skipPlaylistUpDown, false); setEncAcceleration(200); setIRTolerance(40); requestOnChange(GETCONTROLS, clientId); return; } } /* EOF RESETS */ if (strcmp(cmd, "volume") == 0) { uint8_t v = atoi(val); player.setVol(v); } if (strcmp(cmd, "sdpos") == 0) { //return; if (config.getMode()==PM_SDCARD){ config.sdResumePos = 0; if(!player.isRunning()){ player.setResumeFilePos(atoi(val)-player.sd_min); player.sendCommand({PR_PLAY, config.store.lastSdStation}); }else{ player.setFilePos(atoi(val)-player.sd_min); } } return; } if (strcmp(cmd, "snuffle") == 0) { config.setSnuffle(strcmp(val, "true") == 0); return; } if (strcmp(cmd, "balance") == 0) { int8_t valb = atoi(val); player.setBalance(valb); config.setBalance(valb); netserver.requestOnChange(BALANCE, 0); return; } if (strcmp(cmd, "trebble") == 0) { int8_t valb = atoi(val); player.setTone(config.store.bass, config.store.middle, valb); config.setTone(config.store.bass, config.store.middle, valb); netserver.requestOnChange(EQUALIZER, 0); return; } if (strcmp(cmd, "middle") == 0) { int8_t valb = atoi(val); player.setTone(config.store.bass, valb, config.store.trebble); config.setTone(config.store.bass, valb, config.store.trebble); netserver.requestOnChange(EQUALIZER, 0); return; } if (strcmp(cmd, "bass") == 0) { int8_t valb = atoi(val); player.setTone(valb, config.store.middle, config.store.trebble); config.setTone(valb, config.store.middle, config.store.trebble); netserver.requestOnChange(EQUALIZER, 0); return; } if (strcmp(cmd, "reboot") == 0) { ESP.restart(); return; } if (strcmp(cmd, "format") == 0) { SPIFFS.format(); ESP.restart(); return; } if (strcmp(cmd, "reset") == 0) { config.reset(); return; } if (strcmp(cmd, "submitplaylist") == 0) { return; } if (strcmp(cmd, "submitplaylistdone") == 0) { #ifdef MQTT_ROOT_TOPIC //mqttPublishPlaylist(); mqttplaylistticker.attach(5, mqttplaylistSend); #endif if (player.isRunning()) { player.sendCommand({PR_PLAY, -config.lastStation()}); } return; } #if IR_PIN!=255 if (strcmp(cmd, "irbtn") == 0) { config.irindex = atoi(val); irRecordEnable = (config.irindex >= 0); config.irchck = 0; irValsToWs(); if (config.irindex < 0) config.saveIR(); } if (strcmp(cmd, "chkid") == 0) { config.irchck = atoi(val); } if (strcmp(cmd, "irclr") == 0) { uint8_t cl = atoi(val); config.ircodes.irVals[config.irindex][cl] = 0; } #endif } } } 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); } } int NetServer::_readPlaylistLine(File &file, char * line, size_t size){ int bytesRead = file.readBytesUntil('\n', line, size); if(bytesRead>0){ line[bytesRead] = 0; if(line[bytesRead-1]=='\r') line[bytesRead-1]=0; } return bytesRead; } bool NetServer::importPlaylist() { if(config.getMode()==PM_SDCARD) return false; File tempfile = SPIFFS.open(TMP_PATH, "r"); if (!tempfile) { return false; } char sName[BUFLEN], sUrl[BUFLEN], linePl[BUFLEN*3];; int sOvol; _readPlaylistLine(tempfile, linePl, sizeof(linePl)-1); if (config.parseCSV(linePl, sName, sUrl, sOvol)) { tempfile.close(); SPIFFS.rename(TMP_PATH, PLAYLIST_PATH); requestOnChange(PLAYLISTSAVED, 0); return true; } if (config.parseJSON(linePl, sName, sUrl, sOvol)) { File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w"); snprintf(linePl, sizeof(linePl)-1, "%s\t%s\t%d", sName, sUrl, 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); playlistfile.println(linePl); } } playlistfile.flush(); playlistfile.close(); tempfile.close(); SPIFFS.remove(TMP_PATH); requestOnChange(PLAYLISTSAVED, 0); return true; } tempfile.close(); SPIFFS.remove(TMP_PATH); return false; } void NetServer::requestOnChange(requestType_e request, uint8_t clientId) { if(nsQueue==NULL) return; nsRequestParams_t nsrequest; nsrequest.type = request; nsrequest.clientId = clientId; xQueueSend(nsQueue, &nsrequest, NSQ_SEND_DELAY); } void NetServer::resetQueue(){ if(nsQueue!=NULL) xQueueReset(nsQueue); } String processor(const String& var) { // %Templates% if (var == "ACTION") return (network.status == CONNECTED && !config.emptyFS)?"webboard":""; if (var == "UPLOADWIFI") return (network.status == CONNECTED)?" hidden":""; if (var == "VERSION") return YOVERSION; return String(); } int freeSpace; void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { if (!index) { if(filename!="tempwifi.csv"){ 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); if(SPIFFS.exists(INDEX_SD_PATH)) SPIFFS.remove(INDEX_SD_PATH); } freeSpace = (float)SPIFFS.totalBytes()/100*68-SPIFFS.usedBytes(); request->_tempFile = SPIFFS.open(TMP_PATH , "w"); } if (len) { if(freeSpace>index+len){ request->_tempFile.write(data, len); } } if (final) { request->_tempFile.close(); } } void handleUploadWeb(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { DBGVB("File: %s, size:%u bytes, index: %u, final: %s\n", filename.c_str(), len, index, final?"true":"false"); if (!index) { String spath = "/www/"; if(filename=="playlist.csv" || filename=="wifi.csv") spath = "/data/"; request->_tempFile = SPIFFS.open(spath + filename , "w"); } if (len) { request->_tempFile.write(data, len); } if (final) { request->_tempFile.close(); if(filename=="playlist.csv") config.indexPlaylist(); } } 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_DATA: netserver.onWsMessage(arg, data, len, client->id()); break; case WS_EVT_PONG: case WS_EVT_ERROR: break; } } void handleHTTPArgs(AsyncWebServerRequest * request) { if (request->method() == HTTP_GET) { DBGVB("[%s] client ip=%s request of %s", __func__, request->client()->remoteIP().toString().c_str(), 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 || strcmp(request->url().c_str(), TMP_PATH) == 0 || strcmp(request->url().c_str(), PLAYLIST_SD_PATH) == 0 || strcmp(request->url().c_str(), INDEX_SD_PATH) == 0) { #ifdef MQTT_ROOT_TOPIC if (strcmp(request->url().c_str(), PLAYLIST_PATH) == 0) while (mqttplaylistblock) vTaskDelay(5); #endif if(strcmp(request->url().c_str(), PLAYLIST_PATH) == 0 && config.getMode()==PM_SDCARD){ netserver.chunkedHtmlPage("application/octet-stream", request, PLAYLIST_SD_PATH, false); }else{ netserver.chunkedHtmlPage("application/octet-stream", request, request->url().c_str(), false); } return; } Serial.println(request->url()); 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"); } //netserver.chunkedHtmlPage(String(), request, network.status == CONNECTED ? "/www/index.html" : "/www/settings.html", false); 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); return; } if (network.status == CONNECTED) { bool commandFound=false; if (request->hasArg("start")) { player.sendCommand({PR_PLAY, config.lastStation()}); commandFound=true; } if (request->hasArg("stop")) { player.sendCommand({PR_STOP, 0}); commandFound=true; } if (request->hasArg("toggle")) { player.toggle(); commandFound=true; } if (request->hasArg("prev")) { player.prev(); commandFound=true; } if (request->hasArg("next")) { player.next(); commandFound=true; } if (request->hasArg("volm")) { player.stepVol(false); commandFound=true; } if (request->hasArg("volp")) { player.stepVol(true); commandFound=true; } #ifdef USE_SD if (request->hasArg("mode")) { AsyncWebParameter* p = request->getParam("mode"); int mm = atoi(p->value().c_str()); if(mm>2) mm=0; if(mm==2) config.changeMode(); else config.changeMode(mm); commandFound=true; } #endif if (request->hasArg("reset")) { request->redirect("/"); request->send(200); config.reset(); return; } if (request->hasArg("trebble") && request->hasArg("middle") && request->hasArg("bass")) { AsyncWebParameter* pt = request->getParam("trebble", request->method() == HTTP_POST); AsyncWebParameter* pm = request->getParam("middle", request->method() == HTTP_POST); AsyncWebParameter* pb = request->getParam("bass", request->method() == HTTP_POST); int t = atoi(pt->value().c_str()); int m = atoi(pm->value().c_str()); int b = atoi(pb->value().c_str()); player.setTone(b, m, t); config.setTone(b, m, t); netserver.requestOnChange(EQUALIZER, 0); commandFound=true; } if (request->hasArg("ballance")) { AsyncWebParameter* p = request->getParam("ballance", request->method() == HTTP_POST); int b = atoi(p->value().c_str()); player.setBalance(b); config.setBalance(b); netserver.requestOnChange(BALANCE, 0); commandFound=true; } if (request->hasArg("playstation") || request->hasArg("play")) { AsyncWebParameter* p = request->getParam(request->hasArg("playstation") ? "playstation" : "play", request->method() == HTTP_POST); int id = atoi(p->value().c_str()); if (id < 1) id = 1; uint16_t cs = config.playlistLength(); if (id > cs) id = cs; //config.sdResumePos = 0; player.sendCommand({PR_PLAY, id}); commandFound=true; DBGVB("[%s] play=%d", __func__, id); } if (request->hasArg("vol")) { AsyncWebParameter* p = request->getParam("vol", request->method() == HTTP_POST); int v = atoi(p->value().c_str()); if (v < 0) v = 0; if (v > 254) v = 254; config.store.volume = v; player.setVol(v); commandFound=true; DBGVB("[%s] vol=%d", __func__, v); } if (request->hasArg("dspon")) { AsyncWebParameter* p = request->getParam("dspon", request->method() == HTTP_POST); int d = atoi(p->value().c_str()); config.setDspOn(d!=0); commandFound=true; } if (request->hasArg("dim")) { AsyncWebParameter* p = request->getParam("dim", request->method() == HTTP_POST); int d = atoi(p->value().c_str()); if (d < 0) d = 0; if (d > 100) d = 100; config.store.brightness = (uint8_t)d; config.setBrightness(true); commandFound=true; } if (request->hasArg("sleep")) { AsyncWebParameter* sfor = request->getParam("sleep", request->method() == HTTP_POST); int sford = atoi(sfor->value().c_str()); int safterd = 0; if(request->hasArg("after")){ AsyncWebParameter* safter = request->getParam("after", request->method() == HTTP_POST); safterd = atoi(safter->value().c_str()); } if(sford > 0 && safterd >= 0){ request->send(200); config.sleepForAfter(sford, safterd); commandFound=true; } } if (request->hasArg("clearspiffs")) { if(config.spiffsCleanup()){ config.saveValue(&config.store.play_mode, static_cast(PM_WEB)); request->redirect("/"); ESP.restart(); }else{ request->send(200); } return; } if (request->params() > 0) { request->send(commandFound?200:404); return; } } else { if (request->params() > 0) { request->send(404); return; } } }