/
singlwolf
/
Radiola-2S3
Обзор
Документация
Войти
/
singlwolf
/
Radiola-2S3
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/web_server.cpp
1 616 строк
39 KB
SinglWolf
v1.0.6
15 апр 2026, 05:04
15 апр 2026, 05:04
55b96c5
Код
Авторство
О чём код?
#include "web_server.h" #include <fs_system.h> #include "FFat.h" #include "SD_MMC.h" #include "eeStorage.h" #include "hardware.h" #include "services.h" #include "audioVS.h" #include "net_work.h" #include "ESP32FtpServer.h" #include "AsyncJson.h" #include <ArduinoJson.h> #include <Update.h> #include "url_parser.h" #include "lv_i18n.h" #include "screens.h" #include "prio_.h" #define txtPrintInfo(fmt, ...) \ do \ { \ printf(LOG_COLOR_I); \ printf(fmt, ##__VA_ARGS__); \ printf(LOG_RESET_COLOR "\n"); \ } while (0) // #define txtPrintWarn(fmt, ...) \ do \ { \ printf(LOG_COLOR_W); \ printf(fmt, ##__VA_ARGS__); \ printf(LOG_RESET_COLOR "\n"); \ } while (0) // #define txtPrintError(fmt, ...) \ do \ { \ printf(LOG_COLOR_E); \ printf(fmt, ##__VA_ARGS__); \ printf(LOG_RESET_COLOR "\n"); \ } while (0) webServer web; AsyncWebServer webserver(80); FtpServer ftpSrv; AsyncWebSocket websocket("/ws"); // AsyncUDP udp; void afterUpload(AsyncWebServerRequest *request); void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); void handleUpdate(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); void handleDataGET(AsyncWebServerRequest *request); void handleGetListIcon(AsyncWebServerRequest *request); void handleDataGETJSon(AsyncWebServerRequest *request); void handleConfigsGET(AsyncWebServerRequest *request); void handleConfigsPOST(AsyncWebServerRequest *request, JsonVariant &json); void handleGetPlay(AsyncWebServerRequest *request); void handleRenameFile(AsyncWebServerRequest *request); void handleDeleteFile(AsyncWebServerRequest *request); void handleDownloadFile(AsyncWebServerRequest *request); void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total); static size_t chunkedHtmlPageCallback(uint8_t *buffer, size_t maxLen, size_t index); char *updateError() { static char ret[140] = {0}; sprintf(ret, _("Обновление завершилось с ошибкой (%d)<br /> %s"), (int)Update.getError(), Update.errorString()); return ret; } // void handleIndex(AsyncWebServerRequest *request) { txtPrintInfo("request->url(): %s\nrequest->method(): %s\n", request->url().c_str(), (const char *)request->methodToString()); // List all parameters (Compatibility) auto args = request->args(); for (size_t i = 0; i < args; i++) { Serial.printf("ARG[%s]: %s\n", request->argName(i).c_str(), request->arg(i).c_str()); } } // bool webServer::begin() { requestQueue = xQueueCreate(20, sizeof(ParamsRequest_s)); while (requestQueue == nullptr) { delay(1); } ir_training = false; webserver.serveStatic("/", *FileFS, "/www/").setCacheControl("no-cache").setDefaultFile("index.html").setFilter(ON_STA_FILTER); webserver.serveStatic("/", *FileFS, "/www/").setCacheControl("no-cache").setDefaultFile("wifi.html").setFilter(ON_AP_FILTER); webserver.serveStatic("/icons", *FileFS, "/icons/"); webserver.on("/", HTTP_ANY, handleIndex); webserver.onRequestBody(onBody); webserver.on("/play", HTTP_GET, handleGetPlay); webserver.on("/list", HTTP_GET, handleGetListIcon); webserver.on("/configs", HTTP_GET, handleConfigsGET); webserver.on("/delete", HTTP_GET, handleDeleteFile); webserver.on("/download", HTTP_GET, handleDownloadFile); webserver.on("/json*", HTTP_GET, handleDataGETJSon); webserver.on("/update", HTTP_POST, beginUpdate, handleUpdate); webserver.on("/upload", HTTP_POST, afterUpload, onUpload); webserver.on("/rename", HTTP_POST, handleRenameFile); auto *handler = new AsyncCallbackJsonWebHandler( "/configs", // URL endpoint handleConfigsPOST // Имя функции-обработчика ); webserver.addHandler(handler); 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()); }); // } DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Origin"), F("*")); // CORS DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Headers"), F("content-type")); // CORS webserver.begin(); return true; } void webServer::loop() { if (needReboot) { ftpSrv.end(); websocket.closeAll(); delay(100); webserver.end(); delay(100); if (network.state == WIFI_STA) { WiFi.disconnect(true); } else { WiFi.mode(WIFI_MODE_NULL); delay(100); ESP.restart(); } while (true) { delay(100); } } // loop Queue // websocket.cleanupClients(); ParamsRequest_s request; if (xQueueReceive(requestQueue, &request, 0)) { JsonDocument doc; JsonObject root = doc.to<JsonObject>(); String buf = ""; switch (request.number) { case TEMPERATURE: { root["wscurtemp"] = dev.Temperature; } break; case PRESSURE: { root["wscurpres"] = dev.Pressure; } break; case HUMIDITY: { root["wscurhymi"] = dev.Humidity; } break; case PLAYLIST: { } break; case PLAYLISTSAVED: { player.stop(); delay(100); dev.Stop = true; delay(100); eeprom.InitPlaylist(); dev.Stop = false; delay(100); player.setStation(); serv.evtSetWait(WAIT_COMM); } break; case CONFIGSAVED: { delay(100); dev.Stop = true; delay(100); eeprom.InitConfig(); eeprom.SaveConfig(); dev.Stop = false; delay(100); } break; case KBDSAVED: { eeprom.InitKeyboard(); } break; case IRCODESAVED: { eeprom.InitIrcodes(); } break; case ICY: { JsonObject wsicy = root["wsicy"].to<JsonObject>(); if (player.play_type == player.LOCAL) wsicy["wsID"] = 0; else wsicy["wsID"] = eeprom.GetID(); wsicy["name"] = player.title[ICY_NAME]; wsicy["descr"] = player.title[ICY_DESCR]; wsicy["genre"] = player.title[ICY_GENGE]; wsicy["format"] = player.title[ICY_FORMAT]; wsicy["wsuri"] = player.title[ICY_URI]; wsicy["bitr"] = player.title[ICY_BITRATE]; if (request.id) { root["meta"] = player.title[ICY_META]; } } break; case TITLE: { switch (request.mode_num) { case ICY_NAME: { cli_printf("#ICY-NAME#: %s", player.title[ICY_NAME].c_str()); } break; case ICY_URI: { cli_printf("#ICY-URI#: %s", player.title[ICY_URI].c_str()); } break; case ICY_META: { root["meta"] = player.title[ICY_META]; cli_printf("#ICY-META#: %s", player.title[ICY_META].c_str()); serv.evtSetMeta(); } break; case ICY_DESCR: { cli_printf("#ICY-DESCR#: %s", player.title[ICY_DESCR].c_str()); } break; case ICY_GENGE: { cli_printf("#ICY-GENGE#: %s", player.title[ICY_GENGE].c_str()); } break; case ICY_BITRATE: { cli_printf("#ICY-BITRATE#: %s", player.title[ICY_BITRATE].c_str()); } break; case ICY_FORMAT: { cli_printf("#ICY-FORMAT#: %s", player.title[ICY_FORMAT].c_str()); requestOnChange(ICY); } break; default: break; } } break; case VOLUME: { root["wsvol"] = eeprom.GetVolume(); } break; case TRANSLATE: { root["lang"] = Config->lang; } break; case NRSSI: { root["wsrssi"] = rssi; rssi = -1; } break; case MODE: { root["mode"] = player.play_mode; } break; case EQUALIZER: { // int8_t bass, middle, trebble; // eeprom.GetTone(bass, middle, trebble); // root["bass"] = bass; // root["middle"] = middle; // root["trebble"] = trebble; } break; case BALANCE: { root["balance"] = eeprom.GetBalanse(); } break; case TDA: { // JsonObject tda = root["tda"].to<JsonObject>(); const char *key = nullptr; const char *label = nullptr; int8_t val = 0; auto mode = (tda_mode_e)request.mode_num; switch (mode) { case VOL: key = "vol"; val = (int8_t)eeprom.tda->get_volume(&label); break; case BAS: key = "bas"; val = eeprom.tda->get_bass(&label); break; case TRE: key = "tre"; val = eeprom.tda->get_treble(&label); break; case INP: key = "input"; val = (int8_t)eeprom.tda->get_input(); break; case MUT: key = "mute"; val = (int8_t)eeprom.tda->get_mute(); break; case REA: key = "rear"; val = (int8_t)eeprom.tda->get_mute(); break; case LOU: key = "loud"; val = (int8_t)eeprom.tda->get_loud(eeprom.tda->get_input()); break; case SLA: key = "sla"; val = (int8_t)eeprom.tda->get_sla(eeprom.tda->get_input()); break; case ALF: key = "lf"; val = (int8_t)eeprom.tda->get_attlf(&label); break; case ARF: key = "rf"; val = (int8_t)eeprom.tda->get_attrf(&label); break; case ALR: key = "lr"; val = (int8_t)eeprom.tda->get_attlr(&label); break; case ARR: key = "rr"; val = (int8_t)eeprom.tda->get_attrr(&label); break; default: return; } root["tda"][key]["val"] = val; if (label) root["tda"][key]["label"] = label; break; } } if (root.size()) { serializeJson(root, buf); if (request.id == 0) { websocket.textAll(buf); } else { websocket.text(request.id, buf); } } } } void afterUpload(AsyncWebServerRequest *request) { txtPrintInfo("After Uploading"); if (request->hasParam("list", true, true)) { web.requestOnChange(PLAYLISTSAVED); } if (request->hasParam("config", true, true)) { web.requestOnChange(CONFIGSAVED); } if (request->hasParam("ircodes", true, true)) { web.requestOnChange(IRCODESAVED); } if (request->hasParam("keyboard", true, true)) { web.requestOnChange(KBDSAVED); } auto params = request->params(); for (size_t i = 0; i < params; i++) { const AsyncWebParameter *p = request->getParam(i); if (p->isFile()) { txtPrintInfo("FILE[%s]: %s, size: %u", p->name().c_str(), p->value().c_str(), p->size()); } else if (p->isPost()) { txtPrintInfo("POST[%s]: %s", p->name().c_str(), p->value().c_str()); } else { txtPrintInfo("GET[%s]: %s", p->name().c_str(), p->value().c_str()); } } request->send(200); } // void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { // txtPrintWarn("File: %s, size:%u bytes, index: %u, final: %s\n", filename.c_str(), len, index, final ? "true" : "false"); if (!index) { if (request->args()) { if (request->argName(0) == "icons") { filename = "/icons/" + filename; TAKE_MUTEX(); if (!FileFS->exists(filename)) request->_tempFile = FileFS->open(filename.c_str(), "w"); GIVE_MUTEX(); } } if (filename.startsWith("list") && filename.endsWith(".json")) { filename = "/configs/" + filename; TAKE_MUTEX(); request->_tempFile = FileFS->open(filename.c_str(), "w"); GIVE_MUTEX(); } else if (filename == "keyboard.json") { TAKE_MUTEX(); request->_tempFile = FileFS->open(PATH_KBD_BTN, "w"); GIVE_MUTEX(); } else if (filename == "ircodes.json") { TAKE_MUTEX(); request->_tempFile = FileFS->open(PATH_IRCODES, "w"); GIVE_MUTEX(); } else if (filename == "config.json") { TAKE_MUTEX(); request->_tempFile = FileFS->open(PATH_CONFIG, "w"); GIVE_MUTEX(); } } if (len) { TAKE_MUTEX(); if (!Config->sd) { if (FFat.freeBytes() > index + len) { request->_tempFile.write(data, len); } } else { if ((SD_MMC.totalBytes() - SD_MMC.usedBytes()) > index + len) { request->_tempFile.write(data, len); } } GIVE_MUTEX(); } if (final) { TAKE_MUTEX(); request->_tempFile.close(); GIVE_MUTEX(); } } // void handleUpdate(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) { if (!index) { int target = (request->getParam("updtarget", true)->value() == "fs") ? U_SPIFFS : U_FLASH; txtPrintInfo("%s: %s", _("Запуск обновления"), filename.c_str()); 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)) { cli_printf("#ICY-META#: %s - %u %s", _("Успешное обновление"), index + len, _("байт")); delay(3000); } else { Update.printError(Serial); request->send(200, "text/html", updateError()); } } } // void webServer::beginUpdate(AsyncWebServerRequest *request) { web.needReboot = !Update.hasError(); AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", web.needReboot ? "OK" : updateError()); response->addHeader("Connection", "close"); request->send(response); } // void webServer::irToWs(uint32_t irvalue) { char buf[30] = {0}; sprintf(buf, R"({"ircode": "%07X"})", irvalue); websocket.textAll(buf); } // void webServer::onWsMessage(void *arg, uint8_t *data, size_t len, uint32_t id) { auto *info = (AwsFrameInfo *)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { data[len] = 0; txtPrintWarn("Данные веб-клиента: %s\n", (const char *)data); JsonDocument doc; auto error = deserializeJson(doc, (const char *)data); if (error) { txtPrintError("deserializeJson() failed with code %s", error.c_str()); return; } if (!doc["autoplay"].isNull()) { if (doc["autoplay"] != eeprom.GetAutoPlay()) { eeprom.SetAutoPlay(doc["autoplay"]); } } else if (!doc["eq"].isNull()) { JsonObject eq = doc["eq"]; if (!eq["mode"].isNull()) { if (eq["mode"] != eeprom.GetEqMode()) { uint8_t mode = eq["mode"]; if (mode != eeprom.GetEqMode()) { eeprom.SetEqMode(mode); player.setEqMode(mode); } } } else if (!eq["band"].isNull()) { for (size_t i = 0; i < sizeof(player.eq.band); i++) { if (player.eq.band[i] != eq["band"][i]) { player.eq.band[i] = eq["band"][i]; player.ToneSet(i, player.eq.band[i]); break; } } } else if (!eq["set"].isNull()) { bool save = false; for (size_t i = 0; i < sizeof(player.eq.set); i++) { if (player.eq.set[i] != eq["set"][i]) { save = true; player.eq.set[i] = eq["set"][i]; } } if (save) { eeprom.SetEqData(player.eq.band, player.eq.set, (int8_t)player.eq.mode); player.setAllBands(); } } } else if (!doc["show_rows"].isNull()) { if (doc["show_rows"] != eeprom.GetShowRows()) { eeprom.SetShowRows(doc["show_rows"]); } } else if (!doc["license"].isNull()) { if (doc["license"] != Config->license) { Config->license = doc["license"]; eeprom.SaveConfig(); } } else if (!doc["reboot"].isNull()) { delay(1000); web.needReboot = true; } else if (!doc["stop"].isNull()) { player.stop(); } else if (!doc["ir_train"].isNull()) { web.ir_training = doc["ir_train"]; } else if (!doc["volume"].isNull()) { uint8_t vol = doc["volume"]; player.setVol(vol, false); } else if (!doc["ir_set"].isNull()) { if (eeprom.getIRSet() != doc["ir_set"]) { eeprom.setIRSet(doc["ir_set"]); } } else if (!doc["beep"]["on"].isNull()) { if (Config->beep.on != (bool)doc["beep"]["on"]) { Config->beep.on = doc["beep"]["on"]; } } else if (!doc["beep"]["ir"].isNull()) { if (Config->beep.ir != doc["beep"]["ir"]) { Config->beep.ir = doc["beep"]["ir"]; } } else if (!doc["beep"]["kbd"].isNull()) { if (Config->beep.kbd != doc["beep"]["kbd"]) { Config->beep.kbd = doc["beep"]["kbd"]; } } else if (!doc["beep"]["enc"].isNull()) { if (Config->beep.enc != doc["beep"]["enc"]) { Config->beep.enc = doc["beep"]["enc"]; } } // else if (!doc["sensor"]["time"].isNull()) { if (Config->sensor.time != doc["sensor"]["time"]) { Config->sensor.time = doc["sensor"]["time"]; serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } else if (!doc["sensor"]["temp"].isNull()) { if (Config->sensor.temp != doc["sensor"]["temp"]) { Config->sensor.temp = doc["sensor"]["temp"]; serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } else if (!doc["sensor"]["cels"].isNull()) { if (Config->sensor.cels != doc["sensor"]["cels"]) { Config->sensor.cels = doc["sensor"]["cels"]; dev.forseSensor(); delay(5); serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } else if (!doc["sensor"]["degree"].isNull()) { if (Config->sensor.degree != doc["sensor"]["degree"]) { Config->sensor.offset = doc["sensor"]["degree"]; serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } else if (!doc["sensor"]["offset"].isNull()) { if (Config->sensor.offset != doc["sensor"]["offset"]) { Config->sensor.offset = doc["sensor"]["offset"]; dev.forseSensor(); delay(5); serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } else if (!doc["sensor"]["press"].isNull()) { if (Config->sensor.press != doc["sensor"]["press"]) { Config->sensor.press = doc["sensor"]["press"]; serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } else if (!doc["sensor"]["torr"].isNull()) { if (Config->sensor.torr != doc["sensor"]["torr"]) { Config->sensor.torr = doc["sensor"]["torr"]; dev.forseSensor(); delay(5); serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } else if (!doc["sensor"]["wet"].isNull()) { if (Config->sensor.wet != doc["sensor"]["wet"]) { Config->sensor.wet = doc["sensor"]["wet"]; serv.evtSetSensor(false); delay(5); serv.startShowSensor(); } } // else if (!doc["lcd"]["rotat"].isNull()) { if (Config->lcd.rotat != doc["lcd"]["rotat"]) { Config->lcd.rotat = doc["lcd"]["rotat"]; serv.evtRotat(); } } else if (!doc["lcd"]["dark"].isNull()) { if (dark_theme != doc["lcd"]["dark"]) { dark_theme = doc["lcd"]["dark"]; serv.evtSetTheme(); } } // else if (!doc["led"]["on"].isNull()) { if (Config->led.on != doc["led"]["on"]) { Config->led.on = doc["led"]["on"]; } } else if (!doc["led"]["ldr"].isNull()) { if (Config->led.ldr != doc["led"]["ldr"]) { Config->led.ldr = doc["led"]["ldr"]; dev.InitLDR(); } } else if (!doc["led"]["mode"].isNull()) { if (Config->led.mode != doc["led"]["mode"]) { Config->led.mode = doc["led"]["mode"]; } } else if (!doc["led"]["hand"].isNull()) { if (Config->led.hand != doc["led"]["hand"]) { Config->led.hand = doc["led"]["hand"]; } } else if (!doc["led"]["day"].isNull()) { if (Config->led.day != doc["led"]["day"]) { Config->led.day = doc["led"]["day"]; } } else if (!doc["led"]["night"].isNull()) { if (Config->led.night != doc["led"]["night"]) { Config->led.night = doc["led"]["night"]; } } else if (!doc["led"]["begin"].isNull()) { if (Config->led.begin != doc["led"]["begin"]) { Config->led.begin = doc["led"]["begin"]; } } else if (!doc["led"]["end"].isNull()) { if (Config->led.end != doc["led"]["end"]) { Config->led.end = doc["led"]["end"]; } } else if (!doc["color"]["clock"].isNull()) { serv.evtSetSensor(false); delay(5); serv.evtSetColor(objects.clock, serv.colorHTML(doc["color"]["clock"][dark_theme])); } else if (!doc["color"]["temp"].isNull()) { serv.evtSetSensor(false); delay(5); serv.evtSetColor(objects.temperature_label, serv.colorHTML(doc["color"]["temp"][dark_theme])); serv.evtSetColor(objects.minus_label, serv.colorHTML(doc["color"]["temp"][dark_theme])); serv.evtSetColor(objects.degre_label, serv.colorHTML(doc["color"]["temp"][dark_theme])); serv.startShowSensor(); } else if (!doc["color"]["press"].isNull()) { serv.evtSetSensor(false); delay(5); serv.evtSetColor(objects.pressure_label, serv.colorHTML(doc["color"]["press"][dark_theme])); serv.startShowSensor(); } else if (!doc["color"]["wet"].isNull()) { serv.evtSetSensor(false); delay(5); serv.evtSetColor(objects.wet_label, serv.colorHTML(doc["color"]["wet"][dark_theme])); serv.startShowSensor(); } else if (!doc["tda"].isNull()) { JsonObject tda = doc["tda"].to<JsonObject>(); if (!tda["ini"].isNull()) { JsonObject ini = tda["ini"].to<JsonObject>(); ini["mute"] = eeprom.tda->get_mute(); ini["rear"] = eeprom.tda->get_rear(); ini["input"] = eeprom.tda->get_input(); for (int i = 0; i < 3; i++) { ini["sla"][i] = eeprom.tda->get_sla(i); ini["loud"][i] = eeprom.tda->get_loud(i); } String out; serializeJson(doc, out); websocket.text(id, out); return; } bool save = tda["save"]; tda_mode_e mode = NON; if (!tda["vol"].isNull()) { if (save) { eeprom.tda->set_volume(tda["vol"]); } mode = VOL; } else if (!tda["bas"].isNull()) { if (save) { eeprom.tda->set_bass(tda["bas"]); } mode = BAS; } else if (!tda["tre"].isNull()) { if (save) { eeprom.tda->set_treble(tda["tre"]); } mode = TRE; } else if (!tda["lf"].isNull()) { if (save) { eeprom.tda->set_attlf(tda["lf"]); } mode = ALF; } else if (!tda["rf"].isNull()) { if (save) { eeprom.tda->set_attrf(tda["rf"]); } mode = ARF; } else if (!tda["lr"].isNull()) { if (save) { eeprom.tda->set_attlr(tda["lr"]); } mode = ALR; } else if (!tda["rr"].isNull()) { if (save) { eeprom.tda->set_attrr(tda["rr"]); } mode = ARR; } else if (!tda["input"].isNull()) { if (save) { eeprom.tda->set_input(tda["input"]); } mode = INP; } else if (!tda["mute"].isNull()) { if (save) { eeprom.tda->set_mute(tda["mute"]); } mode = MUT; } else if (!tda["rear"].isNull()) { if (save) { eeprom.tda->set_rear(tda["rear"]); } mode = REA; } else if (!tda["loud"].isNull()) { if (save) { eeprom.tda->set_loud(tda["loud"]); } mode = LOU; } else if (!tda["sla"].isNull()) { if (save) { eeprom.tda->set_sla(tda["sla"]); } mode = SLA; } web.requestOnChange(TDA, mode, id); if (save) { if (!eeprom.tda->isChanged()) network.save_tda_conf(); } } } } void webServer::setRSSI(int8_t val) { rssi = val; requestOnChange(NRSSI); } // void webServer::requestOnChange(requestNum_e requestNum) { requestOnChange(requestNum, 255, 0); } // void webServer::requestOnChange(requestNum_e requestNum, uint32_t id) { requestOnChange(requestNum, 255, id); } // void webServer::requestOnChange(requestNum_e requestNum, uint8_t num, uint32_t id) { if (requestQueue == nullptr) return; ParamsRequest_s netRequest; netRequest.number = requestNum; netRequest.mode_num = num; netRequest.id = id; xQueueSend(requestQueue, &netRequest, 0); } // void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: txtPrintInfo("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); web.requestOnChange(MODE, client->id()); web.requestOnChange(ICY, client->id()); // web.requestOnChange(TITLE, client->id()); // web.requestOnChange(VOLUME, client->id()); // web.requestOnChange(EQUALIZER, client->id()); // web.requestOnChange(BALANCE, client->id()); // web.requestOnChange(BITRATE, client->id()); // web.playlistrequest = client->id(); break; case WS_EVT_DISCONNECT: txtPrintWarn("WebSocket client #%u disconnected\n", client->id()); break; case WS_EVT_DATA: web.onWsMessage(arg, data, len, client->id()); break; case WS_EVT_PONG: case WS_EVT_ERROR: case WS_EVT_PING: break; } } /////////////////////////////////////////////////////////////////////////////////////////////////////// void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { auto mcb = heap_caps_get_free_size(MALLOC_CAP_8BIT); auto mci = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); (void)mci; txtPrintInfo("[%s]:\nMALLOC_CAP_INTERNAL=%d, MALLOC_CAP_8BIT=%d", __func__, mci, mcb); txtPrintInfo("request->url(): %s\nrequest->method(): %s\n", request->url().c_str(), (const char *)request->methodToString()); auto args = request->args(); for (size_t i = 0; i < args; i++) { Serial.printf("ARG[%s]: %s\n", request->argName(i).c_str(), request->arg(i).c_str()); } data[len] = 0; txtPrintWarn("[DATA]\t%s\r\nsize: %d\r\n", (const char *)data, len); return; bool answer = false; bool reboot = false; JsonDocument doc; JsonObject root = doc.to<JsonObject>(); auto error = deserializeJson(doc, (const char *)data); if (error) { txtPrintError("deserializeJson() failed with code %s", error.c_str()); request->send(400); return; } txtPrintWarn("%s\n", request->url().c_str()); if (!answer) { root.clear(); root[""] = ""; } String output; serializeJson(root, output); AsyncWebServerResponse *response = request->beginResponse(200, "application/json", output); request->send(response); if (reboot) { web.needReboot = reboot; } } /////////////////////////////////////////////////////////////////////////////////////////////////////// void handleDataGETJSon(AsyncWebServerRequest *request) { JsonDocument doc; JsonObject root = doc.to<JsonObject>(); txtPrintWarn("handleDataGETJSon:\nrequest->url(): %s\nrequest->method(): %s\n", request->url().c_str(), (const char *)request->methodToString()); auto args = request->args(); for (size_t i = 0; i < args; i++) { Serial.printf("ARG[%s]: %s\n", request->argName(i).c_str(), request->arg(i).c_str()); } String output; serializeJson(root, output); AsyncWebServerResponse *response = request->beginResponse(200, "application/json", output); request->send(response); delay(5); } /////////////////////////////////////////////////////////////////////////////////////////////////////// void handleDataGET(AsyncWebServerRequest *request) { // txtPrintWarn("handleDataGET:\nrequest->url(): %s\nrequest->method(): %s\n", request->url().c_str(), (const char *)request->methodToString()); if (!FileFS->exists(request->url())) { request->send(404); return; } File file = FileFS->open(request->url(), "r"); AsyncWebServerResponse *response = request->beginResponse( "application/octet-stream", file.size(), [file](uint8_t *buffer, size_t maxLen, size_t total) mutable -> size_t { auto bytes = file.read(buffer, maxLen); // close file at the end if (bytes + total == file.size()) file.close(); return max(0, (int)bytes); // return 0 even when no bytes were loaded }); request->send(response); } void handleGetListIcon(AsyncWebServerRequest *request) { bool first_files = true; String str_data = ""; File root = FileFS->open("/icons"); File file = root.openNextFile(); while (file) { if (first_files) { first_files = false; } else { str_data += ":"; } if (!file.isDirectory()) { String name = file.name(); // delay(6); name.toLowerCase(); // delay(6); if (name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".gif")) { str_data += file.name(); } } file = root.openNextFile(); } AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", str_data); request->send(response); } /////////////////////////////////////////////////////////////////////////////// size_t chunkedHtmlPageCallback(uint8_t *buffer, size_t maxLen, size_t index) { String lang = "ru"; if (Config->lang == 1) { lang = "en"; } String lic = "/www/license_"; lic = lic + lang + ".html.gz"; File requiredfile = FileFS->open(lic, "r"); if (!requiredfile) return 0; size_t filesize = requiredfile.size(); size_t needread = filesize - index; if (!needread) { return 0; } size_t canread = (needread > maxLen) ? maxLen : needread; requiredfile.seek(index, SeekSet); // vTaskDelay(1); requiredfile.read(buffer, canread); index += canread; if (requiredfile) requiredfile.close(); return canread; } /////////////////////////////////////////////////////////////////////////////// void handleConfigsGET(AsyncWebServerRequest *request) { txtPrintWarn("handleConfigsGET:\nrequest->url(): %s\nrequest->argName(0): %s\nrequest->method(): %s\n", request->url().c_str(), request->argName(0).c_str(), (const char *)request->methodToString()); auto args = request->args(); std::string ssid = ""; std::string password = ""; std::string tz = ""; for (size_t i = 0; i < args; i++) { // Serial.printf("%d : ARG[%s]: %s\n", i, request->argName(i).c_str(), request->arg(i).c_str()); if (request->argName(i) == "ssid") { ssid = request->arg(i).c_str(); } if (request->argName(i) == "password") { password = request->arg(i).c_str(); } if (request->argName(i) == "tz") { tz = request->arg(i).c_str(); } } if ((ssid != "") && (password != "") && (tz != "")) { network.state = WIFI_AP; WiFi.begin(ssid.c_str(), password.c_str()); uint8_t errcnt = 0; while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); errcnt++; if (errcnt > 16) { errcnt = 0; break; }; } Serial.println(); if (WiFi.status() == WL_CONNECTED) { txtPrintInfo("wifi ok!"); ee->ssid = ssid; ee->pass = password; if (ee->tz != tz) { ee->tz = tz; if (ee->rts.on) { ee->rts.last = 0LL; } } eeprom.SaveEEdata(); delay(100); request->send(200); delay(500); web.needReboot = true; return; } request->send(500); return; } StringPS output = {}; if (request->hasArg("config")) { output = eeprom.SaveConfig(false); } if (request->hasArg("wifi")) { output = eeprom.SaveEEdata(false); } if (request->hasArg("current")) { output = eeprom.SaveCurrConfig(); } if (request->hasArg("ircodes")) { serializeJson(IR_KEY, output); } if (request->hasArg("wifiscan")) { JsonDocument doc; JsonObject root = doc.to<JsonObject>(); root["lang"] = Config->lang; root["tz"] = ee->tz; int n = WiFi.scanNetworks(); if (n == 0) { root["net"] = 0; } else { for (int i = 0; i < n; ++i) { txtPrintWarn("%d", i + 1); txtPrintWarn(": "); txtPrintWarn("%s", WiFi.SSID(i).c_str()); root["net"]["ssid"][i] = WiFi.SSID(i); txtPrintWarn(" ("); txtPrintWarn("%i", WiFi.RSSI(i)); root["net"]["power"][i] = WiFi.RSSI(i); txtPrintWarn(")"); txtPrintWarn((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*"); root["net"]["passw"][i] = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "NO" : "YES"; txtPrintWarn("\n"); delay(10); } } serializeJson(root, output); } if (request->hasArg("keyboard")) { serializeJson(KBD_BTN, output); } if (request->hasArg("list")) { if (eeprom.GetTotalSta() > 0) { output = eeprom.GetList(); } else { output = "[]"; } } if (request->hasArg("license")) { AsyncWebServerResponse *response = request->beginChunkedResponse("application/octet-stream", chunkedHtmlPageCallback); response->addHeader(F("Content-Encoding"), F("gzip")); request->send(response); return; } // Serial.println(output); AsyncWebServerResponse *response = request->beginResponse(200, "application/json", output.c_str()); request->send(response); } void handleConfigsPOST(AsyncWebServerRequest *request, JsonVariant &json) { if (!json.is<JsonObject>()) { request->send(400, "text/plain", "Error: Expected a JSON object."); return; } StringPS output = {}; if (request->hasArg("config")) { serializeJson(json.as<JsonObject>(), output); eeprom.InitConfig(false, &output); } else if (request->hasArg("wifi")) { if (ee->rts.on) { if (json["tz"] != ee->tz) { ee->rts.last = 0LL; eeprom.SaveEEdata(); } } serializeJson(json.as<JsonObject>(), output); eeprom.initEEdata(false, &output); } request->send(200); } void handleRenameFile(AsyncWebServerRequest *request) { String oldName = "/icons/", newName = "/icons/"; if (request->hasParam("old", true) && request->hasParam("new", true)) { oldName += request->getParam("old", true)->value(); newName += request->getParam("new", true)->value(); if (FileFS->exists(oldName)) { FileFS->rename(oldName, newName); } else { request->send(404); return; } } else { request->send(400); return; } request->send(200); } void handleDeleteFile(AsyncWebServerRequest *request) { String path = "/icons/"; if (request->hasParam("del")) { path += request->getParam("del")->value(); } else { request->send(400); return; } if (FileFS->exists(path)) { FileFS->remove(path); request->send(200); } else { request->send(404); } } void handleDownloadFile(AsyncWebServerRequest *request) { String path; if (request->hasParam("path")) { path = request->getParam("path")->value(); } else { request->send(400); return; } if (FileFS->exists(path)) { request->send(*FileFS, path, String(), true); } else { request->send(404); } } void handleGetPlay(AsyncWebServerRequest *request) { txtPrintWarn("handleGetPlay:\nrequest->url(): %s\nrequest->method(): %s\n", request->url().c_str(), (const char *)request->methodToString()); int codeStat = 200; String txtStat = "OK"; uint8_t command = 0; auto args = request->args(); for (size_t i = 0; i < args; i++) { Serial.printf("ARG[%s]: %s\n", request->argName(i).c_str(), request->arg(i).c_str()); if (request->argName(i) == "id") { command++; if (command > 1) { continue; } uint16_t id = atoi(request->arg(i).c_str()); if ((id <= 0) || (id > eeprom.GetTotalSta())) { codeStat = 400, txtStat = "ERROR ID!"; continue; } player.play((int16_t)id); } else if (request->argName(i) == "local") { command++; if (command > 1) { continue; } url_t *URI = url_parse(request->arg(i).c_str()); if ((URI == nullptr) || (request->arg(i) == emptyString)) { codeStat = 400, txtStat = "URL PARSE ERROR!"; continue; } else { url_free(URI); txtPrintError("URL: %s", request->arg(i).c_str()); player.play(request->arg(i).c_str(), player.LOCAL); } } else if (request->argName(i) == "stop") { command++; if (command > 1) { continue; } player.stop(); txtPrintError("STOP"); } else if (request->argName(i) == "next") { command++; if (command > 1) { continue; } txtPrintError("NEXT"); } else if (request->argName(i) == "prev") { command++; if (command > 1) { continue; } txtPrintError("PREV"); } } if (command > 1) { txtPrintError("COMMAND: %u", command); codeStat = 400, txtStat = "TOO MANY COMMANDS!"; } AsyncWebServerResponse *response = request->beginResponse(codeStat, "application/json", txtStat); request->send(response); } /////////////////////////////////////////////////////////////////////////////// void webServer::StartTask() { if (xTaskCreatePinnedToCore( &webServer::taskWrapper, NAME_NET, STACK_NET, this, PRIO_NET, nullptr /* &pxCreatedTask */, CPU_NET) != pdPASS) { txtPrintError("ERROR creating %s! Out of memory?", NAME_NET); } } void webServer::taskWrapper(void *param) { auto *runner = static_cast<webServer *>(param); runner->task_net(); } void webServer::task_net() { // ftpSrv.setCallback(_callback); // ftpSrv.setTransferCallback(_transferCallback); // Serial.println("Start FTP with user: 'ftp' and passwd: 'ftp'!"); // ftpSrv.begin(FileFS, PROGNAME, "ftp", "ftp"); // username, password for ftp. (default 21, 50009 for PASV) ftpSrv.begin("ftp", "ftp"); // username, password for ftp. (default 21, 50009 for PASV) delay(1); while (true) { loop(); ftpSrv.handleFTP(); // make sure in loop you call handleFTP()!! delay(1); telnet.loop(); } vTaskDelete(nullptr); } //