diff --git a/data_svelte/edit.htm.gz b/data_svelte/edit.htm.gz index d41d10c1..06931fac 100644 Binary files a/data_svelte/edit.htm.gz and b/data_svelte/edit.htm.gz differ diff --git a/data_svelte/widgets.json b/data_svelte/widgets.json index 8522af99..adf8aae5 100644 --- a/data_svelte/widgets.json +++ b/data_svelte/widgets.json @@ -283,6 +283,13 @@ "after": "mWt", "icon": "speedometer" }, + { + "name": "anydataCm", + "label": "Сантиметры", + "widget": "anydata", + "after": "cm", + "icon": "speedometer" + }, { "name": "nil", "label": "Без виджета" diff --git a/data_svelte_lite/edit.htm.gz b/data_svelte_lite/edit.htm.gz index d41d10c1..06931fac 100644 Binary files a/data_svelte_lite/edit.htm.gz and b/data_svelte_lite/edit.htm.gz differ diff --git a/include/StandWebServer.h b/include/StandWebServer.h index 67281907..de8fa3d7 100644 --- a/include/StandWebServer.h +++ b/include/StandWebServer.h @@ -4,12 +4,17 @@ #ifdef STANDARD_WEB_SERVER extern void standWebServerInit(); extern bool handleFileRead(String path); -extern String getContentType(String filename); +//extern String getContentType(String filename); //#ifdef REST_FILE_OPERATIONS extern void handleFileUpload(); extern void handleFileDelete(); extern void handleFileCreate(); extern void handleFileList(); -void printDirectory(File dir, String& out); +//void printDirectory(File dir, String& out); +extern void handleStatus(); +extern void handleGetEdit(); +extern void replyOK (); +extern void handleNotFound (); + //#endif #endif diff --git a/platformio.ini b/platformio.ini index fcc0ec51..f5e22a8e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -94,7 +94,7 @@ build_src_filter = [env:esp8266_2mb_ota] lib_deps = ${common_env_data.lib_deps_external} - ${env:esp8266_2mb_fromitems.lib_deps} + ${env:esp8266_2mb_ota_fromitems.lib_deps} ESPAsyncUDP build_flags = -Desp8266_2mb_ota="esp8266_2mb_ota" framework = arduino @@ -110,7 +110,7 @@ build_src_filter = + + + - ${env:esp8266_2mb_fromitems.build_src_filter} + ${env:esp8266_2mb_ota_fromitems.build_src_filter} [env:esp8285_1mb] lib_deps = @@ -232,6 +232,7 @@ build_src_filter = [env:esp8266_2mb_ota_fromitems] lib_deps = + adafruit/Adafruit BME280 Library plerup/EspSoftwareSerial build_src_filter = + @@ -241,6 +242,7 @@ build_src_filter = + + + + + + + + diff --git a/src/StandWebServer.cpp b/src/StandWebServer.cpp index aa609150..5f899613 100644 --- a/src/StandWebServer.cpp +++ b/src/StandWebServer.cpp @@ -1,9 +1,17 @@ #include "StandWebServer.h" #ifdef STANDARD_WEB_SERVER -File fsUploadFile; +File uploadFile; +String unsupportedFiles = String(); -void standWebServerInit() { +static const char TEXT_PLAIN[] PROGMEM = "text/plain"; +static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR"; +static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound"; +//static bool fsOK; +//const char* fsName = "LittleFS"; + +void standWebServerInit() +{ // Кэшировать файлы для быстрой работы HTTP.serveStatic("/bundle.js", FileFS, "/", "max-age=31536000"); // кеширование на 1 год HTTP.serveStatic("/bundle.css", FileFS, "/", "max-age=31536000"); // кеширование на 1 год @@ -41,18 +49,18 @@ void standWebServerInit() { HTTP.on("/set", HTTP_GET, []() { if (HTTP.hasArg(F("routerssid")) && WiFi.getMode() == WIFI_AP) { - jsonWriteStr(settingsFlashJson, F("routerssid"), HTTP.arg(F("routerssid"))); - syncSettingsFlashJson(); - HTTP.send(200, "text/plain", "ok"); - } + jsonWriteStr(settingsFlashJson, F("routerssid"), HTTP.arg(F("routerssid"))); + syncSettingsFlashJson(); + HTTP.send(200, "text/plain", "ok"); + } if (HTTP.hasArg(F("routerpass")) && WiFi.getMode() == WIFI_AP) { - jsonWriteStr(settingsFlashJson, F("routerpass"), HTTP.arg(F("routerpass"))); - syncSettingsFlashJson(); - HTTP.send(200, "text/plain", "ok"); - } + jsonWriteStr(settingsFlashJson, F("routerpass"), HTTP.arg(F("routerpass"))); + syncSettingsFlashJson(); + HTTP.send(200, "text/plain", "ok"); + } - }); + }); // Добавляем функцию Update для перезаписи прошивки по WiFi при 1М(256K FileFS) и выше // httpUpdater.setup(&HTTP); @@ -60,193 +68,640 @@ void standWebServerInit() { // Запускаем HTTP сервер HTTP.begin(); - //#ifdef REST_FILE_OPERATIONS - // SPIFFS.begin(); - // { - // Dir dir = SPIFFS.openDir("/"); - // while (dir.next()) { - // String fileName = dir.fileName(); - // size_t fileSize = dir.fileSize(); - // } - // } // HTTP страницы для работы с FFS - // list directory + //////////////////////////////// + // WEB SERVER INIT + + // Filesystem status + HTTP.on("/status", HTTP_GET, handleStatus); + + // List directory HTTP.on("/list", HTTP_GET, handleFileList); - //загрузка редактора editor - HTTP.on("/edit", HTTP_GET, []() { - if (!HTTP.args()) { - if (!handleFileRead("/edit.htm")) HTTP.send(404, "text/plain", "FileNotFound"); - } + // Load editor + HTTP.on("/edit", HTTP_GET, handleGetEdit); - if (HTTP.hasArg("list")) { - handleFileList(); - } - - if (HTTP.hasArg("edit")) { - if (!handleFileRead(HTTP.arg("edit"))) HTTP.send(404, "text/plain", "FileNotFound"); - } - - if (HTTP.hasArg("download")) { - if (!handleFileRead(HTTP.arg("download"))) HTTP.send(404, "text/plain", "FileNotFound"); - } - }); - - //Создание файла + // Create file HTTP.on("/edit", HTTP_PUT, handleFileCreate); - //Удаление файла + // Delete file HTTP.on("/edit", HTTP_DELETE, handleFileDelete); - //Изменение файла - HTTP.on( - "/edit", HTTP_POST, []() { - HTTP.send(200, "text/plain", ""); - }, - handleFileUpload); + // Upload file + // - first callback is called after the request has ended with all parsed arguments + // - second callback handles file upload at that location + HTTP.on("/edit", HTTP_POST, replyOK, handleFileUpload); - // called when the url is not defined here - HTTP.onNotFound([]() { - if (!handleFileRead(HTTP.uri())) - HTTP.send(404, "text/plain", "FileNotFound"); - }); + // Default handler for all URIs not defined above + // Use it to read files from filesystem + HTTP.onNotFound(handleNotFound); } -bool handleFileRead(String path) { - path = "/" + path; - if (path.endsWith("/")) path += "index.html"; - String contentType = getContentType(path); - String pathWithGz = path + ".gz"; - if (FileFS.exists(pathWithGz) || FileFS.exists(path)) { - if (FileFS.exists(pathWithGz)) - path += ".gz"; +//////////////////////////////// +// Utils to return HTTP codes, and determine content-type + +void replyOK() +{ + HTTP.send(200, FPSTR(TEXT_PLAIN), ""); +} + +void replyOKWithMsg(String msg) +{ + HTTP.send(200, FPSTR(TEXT_PLAIN), msg); +} + +void replyNotFound(String msg) +{ + HTTP.send(404, FPSTR(TEXT_PLAIN), msg); +} + +void replyBadRequest(String msg) +{ +// DBG_OUTPUT_PORT.println(msg); + HTTP.send(400, FPSTR(TEXT_PLAIN), msg + "\r\n"); +} + +void replyServerError(String msg) +{ +// DBG_OUTPUT_PORT.println(msg); + HTTP.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n"); +} + +/* + Return the FS type, status and size info +*/ +void handleStatus() +{ +// DBG_OUTPUT_PORT.println("handleStatus"); + String json; + json.reserve(128); + + json = "{\"type\":\""; + json += FS_NAME; + json += "\", \"isOk\":"; + + #ifdef ESP8266 + FSInfo fs_info; + + FileFS.info(fs_info); + json += F("\"true\", \"totalBytes\":\""); + json += fs_info.totalBytes; + json += F("\", \"usedBytes\":\""); + json += fs_info.usedBytes; + json += "\""; +#endif +#ifdef ESP32 + json += F("\"true\", \"totalBytes\":\""); + json += String(FileFS.totalBytes()); + json += F("\", \"usedBytes\":\""); + json += String(FileFS.usedBytes()); + json += "\""; +#endif + + json += F(",\"unsupportedFiles\":\""); + json += unsupportedFiles; + json += "\"}"; + + HTTP.send(200, "application/json", json); +} + +#ifdef ESP32 +String getContentType(String filename) { + if (HTTP.hasArg("download")) { + return "application/octet-stream"; + } else if (filename.endsWith(".htm")) { + return "text/html"; + } else if (filename.endsWith(".html")) { + return "text/html"; + } else if (filename.endsWith(".css")) { + return "text/css"; + } else if (filename.endsWith(".js")) { + return "application/javascript"; + } else if (filename.endsWith(".png")) { + return "image/png"; + } else if (filename.endsWith(".gif")) { + return "image/gif"; + } else if (filename.endsWith(".jpg")) { + return "image/jpeg"; + } else if (filename.endsWith(".ico")) { + return "image/x-icon"; + } else if (filename.endsWith(".xml")) { + return "text/xml"; + } else if (filename.endsWith(".pdf")) { + return "application/x-pdf"; + } else if (filename.endsWith(".zip")) { + return "application/x-zip"; + } else if (filename.endsWith(".gz")) { + return "application/x-gzip"; + } + return "text/plain"; +} +#endif + +/* + Read the given file from the filesystem and stream it back to the client +*/ +bool handleFileRead(String path) +{ +// DBG_OUTPUT_PORT.println(String("handleFileRead: ") + path); + if (path.endsWith("/")) + { + path += "index.html"; + } + + String contentType; + if (HTTP.hasArg("download")) + { + contentType = F("application/octet-stream"); + } + else + { +#ifdef ESP32 + contentType = getContentType(path); +#endif +#ifdef ESP8266 + contentType = mime::getContentType(path); +#endif + } + + if (!FileFS.exists(path)) + { + // File not found, try gzip version + path = path + ".gz"; + } + if (FileFS.exists(path)) + { File file = FileFS.open(path, "r"); - if (contentType == "application/octet-stream") - HTTP.sendHeader("Content-Disposition", "attachment;filename=" + (String)file.name()); - HTTP.streamFile(file, contentType); + if (HTTP.streamFile(file, contentType) != file.size()) + { + // DBG_OUTPUT_PORT.println("Sent less data than expected!"); + } file.close(); return true; } + return false; } -String getContentType(String filename) { - if (HTTP.hasArg("download")) - return "application/octet-stream"; - else if (filename.endsWith(".htm")) - return "text/html"; - else if (filename.endsWith(".html")) - return "text/html"; - else if (filename.endsWith(".txt")) - return "text/plain"; - else if (filename.endsWith(".json")) - return "application/json"; - else if (filename.endsWith(".css")) - return "text/css"; - else if (filename.endsWith(".js")) - return "application/javascript"; - else if (filename.endsWith(".png")) - return "image/png"; - else if (filename.endsWith(".gif")) - return "image/gif"; - else if (filename.endsWith(".jpg")) - return "image/jpeg"; - else if (filename.endsWith(".ico")) - return "image/x-icon"; - else if (filename.endsWith(".xml")) - return "text/xml"; - else if (filename.endsWith(".pdf")) - return "application/x-pdf"; - else if (filename.endsWith(".zip")) - return "application/x-zip"; - else if (filename.endsWith(".gz")) - return "application/x-gzip"; - return "text/plain"; +/* + As some FS (e.g. LittleFS) delete the parent folder when the last child has been removed, + return the path of the closest parent still existing +*/ +String lastExistingParent(String path) +{ + while (!path.isEmpty() && !FileFS.exists(path)) + { + if (path.lastIndexOf('/') > 0) + { + path = path.substring(0, path.lastIndexOf('/')); + } + else + { + path = String(); // No slash => the top folder does not exist + } + } +// DBG_OUTPUT_PORT.println(String("Last existing parent: ") + path); + return path; } -// Здесь функции для работы с файловой системой -void handleFileUpload() { - if (HTTP.uri() != "/edit") return; - HTTPUpload& upload = HTTP.upload(); - if (upload.status == UPLOAD_FILE_START) { +/* + Handle a file upload request +*/ +void handleFileUpload() +{ + if (HTTP.uri() != "/edit") + { + return; + } + HTTPUpload &upload = HTTP.upload(); + if (upload.status == UPLOAD_FILE_START) + { String filename = upload.filename; - if (!filename.startsWith("/")) filename = "/" + filename; - fsUploadFile = FileFS.open(filename, "w"); - filename = String(); - } else if (upload.status == UPLOAD_FILE_WRITE) { - // Serial.print("handleFileUpload Data: "); Serial.println(upload.currentSize); - if (fsUploadFile) - fsUploadFile.write(upload.buf, upload.currentSize); - } else if (upload.status == UPLOAD_FILE_END) { - if (fsUploadFile) - fsUploadFile.close(); + // Make sure paths always start with "/" + if (!filename.startsWith("/")) + { + filename = "/" + filename; + } +// DBG_OUTPUT_PORT.println(String("handleFileUpload Name: ") + filename); + uploadFile = FileFS.open(filename, "w"); + if (!uploadFile) + { + return replyServerError(F("CREATE FAILED")); + } +// DBG_OUTPUT_PORT.println(String("Upload: START, filename: ") + filename); + } + else if (upload.status == UPLOAD_FILE_WRITE) + { + if (uploadFile) + { + size_t bytesWritten = uploadFile.write(upload.buf, upload.currentSize); + if (bytesWritten != upload.currentSize) + { + return replyServerError(F("WRITE FAILED")); + } + } +// DBG_OUTPUT_PORT.println(String("Upload: WRITE, Bytes: ") + upload.currentSize); + } + else if (upload.status == UPLOAD_FILE_END) + { + if (uploadFile) + { + uploadFile.close(); + } +// DBG_OUTPUT_PORT.println(String("Upload: END, Size: ") + upload.totalSize); } } -void handleFileDelete() { - if (HTTP.args() == 0) return HTTP.send(500, "text/plain", "BAD ARGS"); + +#ifdef ESP8266 +void deleteRecursive(String path) +{ + File file = FileFS.open(path, "r"); + bool isDir = file.isDirectory(); + file.close(); + + // If it's a plain file, delete it + if (!isDir) + { + FileFS.remove(path); + return; + } + Dir dir = FileFS.openDir(path); + while (dir.next()) + { + deleteRecursive(path + '/' + dir.fileName()); + } + + // Then delete the folder itself + FileFS.rmdir(path); +} +#endif + +#ifdef ESP32 +struct treename{ + uint8_t type; + char *name; +}; + + +void deleteRecursive( String path ){ + fs::File dir = FileFS.open( path ); + + if(!dir.isDirectory()){ + Serial.printf("%s is a file\n", path); + dir.close(); + Serial.printf( "result of removing file %s: %d\n", path, FileFS.remove( path ) ); + return; + } + + Serial.printf("%s is a directory\n", path); + + fs::File entry, nextentry; + + while ( entry = dir.openNextFile() ){ + +if ( entry.isDirectory() ){ + deleteRecursive( entry.path() ); + } else{ + String tmpname = path+"/"+strdup( entry.name() ); // buffer file name + entry.close(); + Serial.printf( "result of removing file %s: %d\n", tmpname, FileFS.remove( tmpname ) ); + } + + } + + dir.close(); + Serial.printf( "result of removing directory %s: %d\n", path, FileFS.rmdir( path ) ); + +} +#endif +/* + Handle a file deletion request + Operation | req.responseText + ---------------+-------------------------------------------------------------- + Delete file | parent of deleted file, or remaining ancestor + Delete folder | parent of deleted folder, or remaining ancestor +*/ +void handleFileDelete() +{ String path = HTTP.arg(0); - if (path == "/") - return HTTP.send(500, "text/plain", "BAD PATH"); + if (path.isEmpty() || path == "/") + { + return replyBadRequest("BAD PATH"); + } + +// DBG_OUTPUT_PORT.println(String("handleFileDelete: ") + path); if (!FileFS.exists(path)) - return HTTP.send(404, "text/plain", "FileNotFound"); - FileFS.remove(path); - HTTP.send(200, "text/plain", ""); - path = String(); + { + return replyNotFound(FPSTR(FILE_NOT_FOUND)); + } + deleteRecursive(path); + + + + replyOKWithMsg(lastExistingParent(path)); } -void handleFileCreate() { - if (HTTP.args() == 0) - return HTTP.send(500, "text/plain", "BAD ARGS"); - String path = HTTP.arg(0); +/* + Handle the creation/rename of a new file + Operation | req.responseText + ---------------+-------------------------------------------------------------- + Create file | parent of created file + Create folder | parent of created folder + Rename file | parent of source file + Move file | parent of source file, or remaining ancestor + Rename folder | parent of source folder + Move folder | parent of source folder, or remaining ancestor +*/ +void handleFileCreate() +{ + String path = HTTP.arg("path"); + if (path.isEmpty()) + { + return replyBadRequest(F("PATH ARG MISSING")); + } + +#ifdef USE_SPIFFS + if (checkForUnsupportedPath(path).length() > 0) + { + return replyServerError(F("INVALID FILENAME")); + } +#endif + if (path == "/") - return HTTP.send(500, "text/plain", "BAD PATH"); + { + return replyBadRequest("BAD PATH"); + } if (FileFS.exists(path)) - return HTTP.send(500, "text/plain", "FILE EXISTS"); - File file = FileFS.open(path, "w"); - if (file) - file.close(); + { + return replyBadRequest(F("PATH FILE EXISTS")); + } + + String src = HTTP.arg("src"); + if (src.isEmpty()) + { + // No source specified: creation +// DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path); + if (path.endsWith("/")) + { + // Create a folder + path.remove(path.length() - 1); + if (!FileFS.mkdir(path)) + { + return replyServerError(F("MKDIR FAILED")); + } + } + else + { + // Create a file + File file = FileFS.open(path, "w"); + if (file) + { +#ifdef ESP8266 + file.write((const char *)0); +#endif +#ifdef ESP32 + file.write(0); +#endif + file.close(); + } + else + { + return replyServerError(F("CREATE FAILED")); + } + } + if (path.lastIndexOf('/') > -1) + { + path = path.substring(0, path.lastIndexOf('/')); + } + replyOKWithMsg(path); + } else - return HTTP.send(500, "text/plain", "CREATE FAILED"); - HTTP.send(200, "text/plain", ""); - path = String(); + { + // Source specified: rename + if (src == "/") + { + return replyBadRequest("BAD SRC"); + } + if (!FileFS.exists(src)) + { + return replyBadRequest(F("SRC FILE NOT FOUND")); + } + +// DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path + " from " + src); + + if (path.endsWith("/")) + { + path.remove(path.length() - 1); + } + if (src.endsWith("/")) + { + src.remove(src.length() - 1); + } + if (!FileFS.rename(src, path)) + { + return replyServerError(F("RENAME FAILED")); + } + replyOKWithMsg(lastExistingParent(src)); + } } -void handleFileList() { - File dir = FileFS.open("/", "r"); - String output = "["; - File entry; - while (entry = dir.openNextFile()) { - if (output != "[") output += ','; - bool isDir = entry.isDirectory(); +/* + Return the list of files in the directory specified by the "dir" query string parameter. + Also demonstrates the use of chunked responses. +*/ +#ifdef ESP8266 +void handleFileList() +{ + if (!HTTP.hasArg("dir")) + { + return replyBadRequest(F("DIR ARG MISSING")); + } + + String path = HTTP.arg("dir"); + if (path != "/" && !FileFS.exists(path)) + { + return replyBadRequest("BAD PATH"); + } + +// DBG_OUTPUT_PORT.println(String("handleFileList: ") + path); + Dir dir = FileFS.openDir(path); + path.clear(); + + // use HTTP/1.1 Chunked response to avoid building a huge temporary string + if (!HTTP.chunkedResponseModeStart(200, "text/json")) + { + HTTP.send(505, F("text/html"), F("HTTP1.1 required")); + return; + } + + // use the same string for every line + String output; + output.reserve(64); + while (dir.next()) + { +#ifdef USE_SPIFFS + String error = checkForUnsupportedPath(dir.fileName()); + if (error.length() > 0) + { +// DBG_OUTPUT_PORT.println(String("Ignoring ") + error + dir.fileName()); + continue; + } +#endif + if (output.length()) + { + // send string from previous iteration + // as an HTTP chunk + HTTP.sendContent(output); + output = ','; + } + else + { + output = '['; + } + output += "{\"type\":\""; - output += (isDir) ? "dir" : "file"; - output += "\",\"name\":\""; - output += String(entry.name()); + if (dir.isDirectory()) + { + output += "dir"; + } + else + { + output += F("file\",\"size\":\""); + output += dir.fileSize(); + } + + output += F("\",\"name\":\""); + // Always return names without leading "/" + if (dir.fileName()[0] == '/') + { + output += &(dir.fileName()[1]); + } + else + { + output += dir.fileName(); + } + output += "\"}"; - entry.close(); } + + // send last string output += "]"; - //Serial.println(output); - HTTP.send(200, "text/json", output); + HTTP.sendContent(output); + HTTP.chunkedResponseFinalize(); +} +#endif + +#ifdef ESP32 +void handleFileList() { + if (!HTTP.hasArg("dir")) { + HTTP.send(500, "text/plain", "BAD ARGS"); + return; + } + + String path = HTTP.arg("dir"); +// DBG_OUTPUT_PORT.println("handleFileList: " + path); + + + File root = FileFS.open(path); + path = String(); + + String output = "["; + if(root.isDirectory()){ + File file = root.openNextFile(); + while(file){ + if (output != "[") { + output += ','; + } + output += "{\"type\":\""; + // output += (file.isDirectory()) ? "dir" : "file"; + if (file.isDirectory()) + { + output += "dir"; + } + else + { + output += F("file\",\"size\":\""); + output += file.size(); + } + + output += "\",\"name\":\""; + output += String(file.name()); + output += "\"}"; + file = root.openNextFile(); + } + } + output += "]"; + HTTP.send(200, "text/json", output); + +} +#endif + + +/* + The "Not Found" handler catches all URI not explicitly declared in code + First try to find and return the requested file from the filesystem, + and if it fails, return a 404 page with debug information +*/ +void handleNotFound() +{ +#ifdef ESP8266 + String uri = ESP8266WebServer::urlDecode(HTTP.uri()); // required to read paths with blanks +#endif +#ifdef ESP32 + String uri = WebServer::urlDecode(HTTP.uri()); // required to read paths with blanks +#endif + if (handleFileRead(uri)) + { + return; + } + + // Dump debug data + String message; + message.reserve(100); + message = F("Error: File not found\n\nURI: "); + message += uri; + message += F("\nMethod: "); + message += (HTTP.method() == HTTP_GET) ? "GET" : "POST"; + message += F("\nArguments: "); + message += HTTP.args(); + message += '\n'; + for (uint8_t i = 0; i < HTTP.args(); i++) + { + message += F(" NAME:"); + message += HTTP.argName(i); + message += F("\n VALUE:"); + message += HTTP.arg(i); + message += '\n'; + } + message += "path="; + message += HTTP.arg("path"); + message += '\n'; +// DBG_OUTPUT_PORT.print(message); + + return replyNotFound(message); } -void printDirectory(File dir, String& out) { - while (true) { - File entry = dir.openNextFile(); - if (!entry) { - break; - } - if (entry.isDirectory()) { - out += entry.name(); - out += "/"; - printDirectory(entry, out); - } else { - out += entry.name(); - out += "\r\n"; - } +/* + This specific handler returns the index.htm (or a gzipped version) from the /edit folder. + If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version + embedded in the program code. + Otherwise, fails with a 404 page with debug information +*/ +void handleGetEdit() +{ + if (handleFileRead(F("/edit.htm"))) + { + return; } + +#ifdef INCLUDE_FALLBACK_INDEX_HTM + server.sendHeader(F("Content-Encoding"), "gzip"); + server.send(200, "text/html", index_htm_gz, index_htm_gz_len); +#else + replyNotFound(FPSTR(FILE_NOT_FOUND)); +#endif } #endif diff --git a/src/modules/sensors/A02Distance/A02Distance.cpp b/src/modules/sensors/A02Distance/A02Distance.cpp new file mode 100644 index 00000000..a3a9ac33 --- /dev/null +++ b/src/modules/sensors/A02Distance/A02Distance.cpp @@ -0,0 +1,99 @@ + +#include "Global.h" +#include "classes/IoTItem.h" + +#include "modules/sensors/UART/Uart.h" + +#define READ_TIMEOUT 100 + +class A02Distance : public IoTItem +{ +private: +public: + A02Distance(String parameters) : IoTItem(parameters) + { + if (myUART) + { + } + } + +//Периодическое выполнение программы, в int секунд, которые зададим в конфигурации + void doByInterval() + { + if (myUART) + { + static uint8_t data[4]; + + if (recieve(data, 4) == 4) + { + if (data[0] == 0xff) + { + int sum; + sum = (data[0] + data[1] + data[2]) & 0x00FF; + if (sum == data[3]) + { + float distance = (data[1] << 8) + data[2]; + if (distance > 30) + { + value.valD = distance / 10; + //SerialPrint("i", F("A02Distance"), "distance = " + String(value.valD) + "cm"); + regEvent(value.valD, "A02Distance"); + } + else + { + SerialPrint("E", "A02Distance", "Below the lower limit"); + regEvent(NAN, "A02Distance"); + } + } + else + { + regEvent(NAN, "A02Distance"); + SerialPrint("E", "A02Distance", "Distance data error"); + } + } + } else { + regEvent(NAN, "A02Distance"); + SerialPrint("E", "A02Distance", "Recieve data error"); + } + } else + { + regEvent(NAN, "A02Distance"); + SerialPrint("E", "A02Distance", "Not find UART"); + } + + } + + + //Приём данных из COM порта + uint16_t recieve(uint8_t *resp, uint16_t len) + { + ((SoftwareSerial *)myUART)->listen(); // Start software serial listen + unsigned long startTime = millis(); // Start time for Timeout + uint8_t index = 0; // Bytes we have read + while ((index < len) && (millis() - startTime < READ_TIMEOUT)) + { + if (myUART->available() > 0) + { + uint8_t c = (uint8_t)myUART->read(); + resp[index++] = c; + } + } + return index; + } + + ~A02Distance(){}; +}; + + +//Функиця ядра, чтобы нашел наш модуль +void *getAPI_A02Distance(String subtype, String param) +{ + if (subtype == F("A02Distance")) + { + return new A02Distance(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/sensors/A02Distance/modinfo.json b/src/modules/sensors/A02Distance/modinfo.json new file mode 100644 index 00000000..4e6d2368 --- /dev/null +++ b/src/modules/sensors/A02Distance/modinfo.json @@ -0,0 +1,40 @@ +{ + "menuSection": "Сенсоры", + "configItem": [ + { + "name": "A02 Дальность", + "type": "Reading", + "subtype": "A02Distance", + "id": "dist", + "widget": "anydataCm", + "page": "Сенсоры", + "descr": "Дальность", + "int": 5, + "round": 1 + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mitchel", + "authorGit": "https://github.com/Mit4el", + "exampleURL": "https://iotmanager.org/wiki", + "specialThanks": "", + "moduleName": "A02Distance", + "moduleVersion": "0.1", + "moduleDesc": "A0221AU, A02YYUW Ультразвуковой датчик. Позволяет получить дальность с ультрозвуковых датчиков A0221AU, A02YYUW", + "propInfo": { + "int": "Количество секунд между опросами датчика." + } + }, + "defActive": true, + "usedLibs": { + "esp32_4mb": [], + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [], + "esp8285_1mb": [], + "esp8285_1mb_ota": [], + "esp8266_2mb": [], + "esp8266_2mb_ota": [] + } +} \ No newline at end of file diff --git a/src/utils/FileUtils.cpp b/src/utils/FileUtils.cpp index ccee03a1..f59316d5 100644 --- a/src/utils/FileUtils.cpp +++ b/src/utils/FileUtils.cpp @@ -124,6 +124,8 @@ const String readFile(const String& filename, size_t max_size) { size_t size = file.size(); if (size > max_size) { file.close(); + if (path == "/config.json") + return "[]"; return "large"; } String temp = file.readString();