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/items.json b/data_svelte/items.json index c5190460..9e0e2f57 100644 --- a/data_svelte/items.json +++ b/data_svelte/items.json @@ -382,7 +382,23 @@ }, { "global": 0, - "name": "26. PZEM 004t Напряжение", + "name": "26. Аналоговый счетчик импульсов", + "type": "Writing", + "subtype": "Impulse", + "id": "impulse", + "widget": "anydataDef", + "page": "Счетчики", + "descr": "Импульсов", + "needSave": 0, + "int": 1, + "pin": 16, + "pinMode": "INPUT", + "debounceDelay": 3, + "num": 26 + }, + { + "global": 0, + "name": "27. PZEM 004t Напряжение", "type": "Reading", "subtype": "Pzem004v", "id": "v", @@ -392,11 +408,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 26 + "num": 27 }, { "global": 0, - "name": "27. PZEM 004t Сила тока", + "name": "28. PZEM 004t Сила тока", "type": "Reading", "subtype": "Pzem004a", "id": "a", @@ -406,11 +422,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 27 + "num": 28 }, { "global": 0, - "name": "28. PZEM 004t Мощность", + "name": "29. PZEM 004t Мощность", "type": "Reading", "subtype": "Pzem004w", "id": "w", @@ -420,11 +436,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 28 + "num": 29 }, { "global": 0, - "name": "29. PZEM 004t Энергия", + "name": "30. PZEM 004t Энергия", "type": "Reading", "subtype": "Pzem004wh", "id": "wh", @@ -434,11 +450,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 29 + "num": 30 }, { "global": 0, - "name": "30. PZEM 004t Частота", + "name": "31. PZEM 004t Частота", "type": "Reading", "subtype": "Pzem004hz", "id": "hz", @@ -448,11 +464,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 30 + "num": 31 }, { "global": 0, - "name": "31. PZEM 004t Косинус", + "name": "32. PZEM 004t Косинус", "type": "Reading", "subtype": "Pzem004pf", "id": "pf", @@ -462,11 +478,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 31 + "num": 32 }, { "global": 0, - "name": "32. PZEM настройка", + "name": "33. PZEM настройка", "type": "Reading", "subtype": "Pzem004cmd", "id": "set", @@ -478,11 +494,11 @@ "changeaddr": 0, "setaddr": "0x01", "reset": 0, - "num": 32 + "num": 33 }, { "global": 0, - "name": "33. Часы реального времени", + "name": "34. Часы реального времени", "type": "Reading", "subtype": "RTC", "id": "rtc", @@ -498,11 +514,11 @@ "int": 5, "btn-setUTime": "0", "btn-setSysTime": "nil", - "num": 33 + "num": 34 }, { "global": 0, - "name": "34. Sht20 Температура", + "name": "35. Sht20 Температура", "type": "Reading", "subtype": "Sht20t", "id": "tmp2", @@ -511,11 +527,11 @@ "descr": "Температура", "int": 15, "round": 1, - "num": 34 + "num": 35 }, { "global": 0, - "name": "35. Sht20 Влажность", + "name": "36. Sht20 Влажность", "type": "Reading", "subtype": "Sht20h", "id": "Hum2", @@ -524,11 +540,11 @@ "descr": "Влажность", "int": 15, "round": 1, - "num": 35 + "num": 36 }, { "global": 0, - "name": "36. Sht30 Температура", + "name": "37. Sht30 Температура", "type": "Reading", "subtype": "Sht30t", "id": "tmp30", @@ -537,11 +553,11 @@ "descr": "SHT30 Температура", "int": 15, "round": 1, - "num": 36 + "num": 37 }, { "global": 0, - "name": "37. Sht30 Влажность", + "name": "38. Sht30 Влажность", "type": "Reading", "subtype": "Sht30h", "id": "Hum30", @@ -550,12 +566,12 @@ "descr": "SHT30 Влажность", "int": 15, "round": 1, - "num": 37 + "num": 38 }, { "global": 0, - "name": "38. HC-SR04 Ультразвуковой дальномер", - "num": 38, + "name": "39. HC-SR04 Ультразвуковой дальномер", + "num": 39, "type": "Reading", "subtype": "Sonar", "id": "sonar", @@ -567,7 +583,7 @@ "int": 5 }, { - "name": "39. UART", + "name": "40. UART", "type": "Reading", "subtype": "UART", "page": "", @@ -579,14 +595,14 @@ "line": 2, "speed": 9600, "eventFormat": 0, - "num": 39 + "num": 40 }, { "header": "Исполнительные устройства" }, { "global": 0, - "name": "40. Кнопка подключенная к пину", + "name": "41. Кнопка подключенная к пину", "type": "Writing", "subtype": "ButtonIn", "id": "btn", @@ -601,11 +617,11 @@ "debounceDelay": 50, "fixState": 0, "inv": 0, - "num": 40 + "num": 41 }, { "global": 0, - "name": "41. Управление пином", + "name": "42. Управление пином", "type": "Writing", "subtype": "ButtonOut", "needSave": 0, @@ -616,11 +632,11 @@ "int": 0, "inv": 0, "pin": 2, - "num": 41 + "num": 42 }, { "global": 0, - "name": "42. Сервопривод", + "name": "43. Сервопривод", "type": "Writing", "subtype": "IoTServo", "id": "servo", @@ -631,11 +647,11 @@ "pin": 12, "apin": -1, "amap": "0, 4096, 0, 180", - "num": 42 + "num": 43 }, { "global": 0, - "name": "43. Расширитель портов Mcp23017", + "name": "44. Расширитель портов Mcp23017", "type": "Reading", "subtype": "Mcp23017", "id": "Mcp", @@ -645,11 +661,11 @@ "int": "0", "addr": "0x20", "index": 1, - "num": 43 + "num": 44 }, { "global": 0, - "name": "44. MP3 плеер", + "name": "45. MP3 плеер", "type": "Reading", "subtype": "Mp3", "id": "mp3", @@ -659,11 +675,11 @@ "int": 1, "pins": "14,12", "volume": 20, - "num": 44 + "num": 45 }, { "global": 0, - "name": "45. Сенсорная кнопка", + "name": "46. Сенсорная кнопка", "type": "Writing", "subtype": "Multitouch", "id": "impulse", @@ -677,11 +693,11 @@ "pinMode": "INPUT", "debounceDelay": 50, "PWMDelay": 500, - "num": 45 + "num": 46 }, { "global": 0, - "name": "46. Расширитель портов Pcf8574", + "name": "47. Расширитель портов Pcf8574", "type": "Reading", "subtype": "Pcf8574", "id": "Pcf", @@ -691,11 +707,11 @@ "int": "0", "addr": "0x20", "index": 1, - "num": 46 + "num": 47 }, { "global": 0, - "name": "47. PWM ESP8266", + "name": "48. PWM ESP8266", "type": "Writing", "subtype": "Pwm8266", "id": "pwm", @@ -707,11 +723,11 @@ "freq": 5000, "val": 0, "apin": -1, - "num": 47 + "num": 48 }, { "global": 0, - "name": "48. Телеграм-Лайт", + "name": "49. Телеграм-Лайт", "type": "Writing", "subtype": "TelegramLT", "id": "tg", @@ -720,14 +736,14 @@ "descr": "", "token": "", "chatID": "", - "num": 48 + "num": 49 }, { "header": "Экраны" }, { "global": 0, - "name": "49. LCD экран 2004", + "name": "50. LCD экран 2004", "type": "Reading", "subtype": "Lcd2004", "id": "Lcd", @@ -739,10 +755,10 @@ "size": "20,4", "coord": "0,0", "id2show": "id датчика", - "num": 49 + "num": 50 }, { - "name": "50. LCD экран 1602", + "name": "51. LCD экран 1602", "type": "Reading", "subtype": "Lcd2004", "id": "Lcd", @@ -754,6 +770,6 @@ "size": "16,2", "coord": "0,0", "id2show": "id датчика", - "num": 50 + "num": 51 } ] \ No newline at end of file diff --git a/data_svelte/widgets.json b/data_svelte/widgets.json index 4c681bff..adf8aae5 100644 --- a/data_svelte/widgets.json +++ b/data_svelte/widgets.json @@ -41,7 +41,25 @@ "label": "Ватты", "widget": "anydata", "after": "Wt", - "icon": "speedometer" + "icon": "speedometer", + "color": [ + { + "level": 0, + "value": "" + }, + { + "level": 200, + "value": "#009933" + }, + { + "level": 2000, + "value": "#FF9900" + }, + { + "level": 4000, + "value": "red" + } + ] }, { "name": "anydataWth", @@ -62,7 +80,42 @@ "label": "Температура", "widget": "anydata", "after": "°С", - "icon": "thermometer" + "icon": "thermometer", + "font": "OCR A Std", + "color": [ + { + "level": -20, + "value": "#0000CC" + }, + { + "level": -10, + "value": "#0000CC" + }, + { + "level": 0, + "value": "#0000CC" + }, + { + "level": 12, + "value": "#3366FF" + }, + { + "level": 16, + "value": "#33CCFF" + }, + { + "level": 18, + "value": "#009933" + }, + { + "level": 30, + "value": "#FF9900" + }, + { + "level": 40, + "value": "red" + } + ] }, { "name": "anydataMm", @@ -225,11 +278,18 @@ }, { "name": "anydatamWt", - "label": "миллиВатты", + "label": "миллиВатты", "widget": "anydata", "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/myProfile.json b/myProfile.json index 8bfe873d..17ee601d 100644 --- a/myProfile.json +++ b/myProfile.json @@ -136,6 +136,10 @@ "path": "src/modules/sensors/Hx711", "active": false }, + { + "path": "src/modules/sensors/Impulse", + "active": true + }, { "path": "src/modules/sensors/Ina219", "active": false diff --git a/platformio.ini b/platformio.ini index 7ba76a98..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 = + + + + + + + + @@ -332,6 +334,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/API.cpp b/src/modules/API.cpp index 4de43be5..12480f10 100644 --- a/src/modules/API.cpp +++ b/src/modules/API.cpp @@ -14,6 +14,7 @@ void* getAPI_Bme280(String subtype, String params); void* getAPI_Bmp280(String subtype, String params); void* getAPI_Dht1122(String subtype, String params); void* getAPI_Ds18b20(String subtype, String params); +void* getAPI_Impulse(String subtype, String params); void* getAPI_Pzem004(String subtype, String params); void* getAPI_RTC(String subtype, String params); void* getAPI_Sht20(String subtype, String params); @@ -47,6 +48,7 @@ if ((tmpAPI = getAPI_Bme280(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_Bmp280(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_Dht1122(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_Ds18b20(subtype, params)) != nullptr) return tmpAPI; +if ((tmpAPI = getAPI_Impulse(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_Pzem004(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_RTC(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_Sht20(subtype, params)) != nullptr) return tmpAPI; diff --git a/src/modules/exec/Multitouch/Multitouch.cpp b/src/modules/exec/Multitouch/Multitouch.cpp index 9e6b30ba..383d5be4 100644 --- a/src/modules/exec/Multitouch/Multitouch.cpp +++ b/src/modules/exec/Multitouch/Multitouch.cpp @@ -6,23 +6,22 @@ extern IoTGpio IoTgpio; class Multitouch : public IoTItem { private: - int _pin; int _int; - int _inv; - String _pinMode; - int _lastButtonState = LOW; - unsigned long _lastDebounceTime = 0; unsigned long timing; - long _debounceDelay = 50; long _PWMDelay = 500; - int _buttonState; - int _reading; int _count = 0; int duration = 0; + int _pin; + bool _execLevel, _fixState, _inv, _buttonState, _reading; + bool _lastButtonState = LOW; + unsigned long _lastDebounceTime = 0; + int _debounceDelay = 50; + public: Multitouch(String parameters) : IoTItem(parameters) { + String _pinMode; jsonRead(parameters, "pin", _pin); jsonRead(parameters, "pinMode", _pinMode); jsonRead(parameters, "debounceDelay", _debounceDelay); @@ -31,15 +30,20 @@ public: jsonRead(parameters, "inv", _inv); _round = 0; - IoTgpio.pinMode(_pin, INPUT); - if (_pinMode == "INPUT_PULLUP") - IoTgpio.digitalWrite(_pin, HIGH); - else if (_pinMode == "INPUT_PULLDOWN") + if (_pinMode == F("INPUT")) + IoTgpio.pinMode(_pin, INPUT); + else if (_pinMode == F("INPUT_PULLUP")) + IoTgpio.pinMode(_pin, INPUT_PULLUP); + else if (_pinMode == F("INPUT_PULLDOWN")) + { + IoTgpio.pinMode(_pin, INPUT); IoTgpio.digitalWrite(_pin, LOW); + } value.valD = _buttonState = IoTgpio.digitalRead(_pin); // сообщаем всем о стартовом статусе без генерации события regEvent(_buttonState, "", false, false); + SerialPrint("I", F("Multitouch"), "_buttonState " + String(_buttonState)); } void loop() @@ -49,35 +53,34 @@ public: { _lastDebounceTime = millis(); } - if ((millis() - _lastDebounceTime) > _debounceDelay) { - if (millis() - timing > _int && _reading == _inv && millis() - _lastDebounceTime > _PWMDelay) - { - timing = millis(); - duration = millis() - _lastDebounceTime - _PWMDelay; - value.valD = duration / 50; - regEvent(value.valD, "Multitouch"); - _count = -1; - } - if (_reading != _buttonState) { - _buttonState = _reading; _count++; duration = 0; + _buttonState = _reading; } - if (1 < _count && millis() > _lastDebounceTime + _PWMDelay) { value.valD = _count / 2; - regEvent(value.valD, "Multitouch"); + regEvent(value.valD, F("Multitouch")); _count = 0; } + + if (millis() - timing > _int && _reading == _inv && millis() - _lastDebounceTime > _PWMDelay) + { + SerialPrint("I", F("Multitouch"), "Считаем задержку"); + timing = millis(); + duration = millis() - _lastDebounceTime - _PWMDelay; + value.valD = duration / _int + 1; + regEvent(value.valD, F("Multitouch")); + _count = -1; + } } + _lastButtonState = _reading; } - ~Multitouch(){}; }; diff --git a/src/modules/exec/Multitouch/modinfo.json b/src/modules/exec/Multitouch/modinfo.json index b83a1f9e..d596113e 100644 --- a/src/modules/exec/Multitouch/modinfo.json +++ b/src/modules/exec/Multitouch/modinfo.json @@ -25,7 +25,7 @@ "authorGit": "https://github.com/avaksru", "specialThanks": "", "moduleName": "Multitouch", - "moduleVersion": "1.0", + "moduleVersion": "2.0", "usedRam": { "esp32_4mb": 15, "esp8266_4mb": 15 @@ -48,8 +48,6 @@ "esp8266_1mb": [], "esp8266_1mb_ota": [], "esp8285_1mb": [], - "esp8285_1mb_ota": [], - "esp8266_2mb": [], - "esp8266_2mb_ota": [] + "esp8285_1mb_ota": [] } } 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/modules/sensors/Acs712/Acs712.cpp b/src/modules/sensors/Acs712/Acs712.cpp index 79d9bc76..ed4984e2 100644 --- a/src/modules/sensors/Acs712/Acs712.cpp +++ b/src/modules/sensors/Acs712/Acs712.cpp @@ -3,6 +3,14 @@ extern IoTGpio IoTgpio; +#ifdef ESP32 + #define SC_ADC 4095. //Scale ADC +#else + #define SC_ADC 1023. //Scale ADC +#endif + +#define DEF_NAN 50 //количество отсчетов АПЦ которые мы принимаем за отсутствие датчика (в идеале 0, но все равно Acs712 выдает минимум 0,5В) + class Acs712 : public IoTItem { private: @@ -10,56 +18,97 @@ private: const unsigned long _sampleTime = 100000UL; // sample over 100ms, it is an exact number of cycles for both 50Hz and 60Hz mains const unsigned long _numSamples = 250UL; // choose the number of samples to divide sampleTime exactly, but low enough for the ADC to keep up const unsigned long _sampleInterval = _sampleTime / _numSamples; // the sampling interval, must be longer than then ADC conversion time - int _adc_zero1; //Переменная автоматической калибровки + int _adc_zero1; // Переменная автоматической калибровки + int _fl_rms; // 1 - подсчет средне-квадратического тока (переменный), 0 - подсчет средне-арифмитического тока (постоянный) + int _sens = 100; //Чувствительность датчика тока: 5A = 185mВ/A, 20A = 100mВ/A, 30A = 66mВ/A + int _vref; //"Vref (мВ) - Опороное наряжение питания Acs712, по умолчанию = 5000мВ", + float k ; //Чувствительность(разрешение) Acs712 по току, сколько тока в одном отсчете АЦП, k=VACP/sens (30А->74mА, 20A->49mA, 5A->26mA для esp8266) + bool f_nan = false; //Флаг отсутствия входа на АЦП + float vacp; //Напряжение в мВ для смещения одного разряда АЦП esp8266 = 4.887, esp32 = 1.221; vacp = vcc*1000/1023 public: - Acs712(String parameters) : IoTItem(parameters) { String tmp; jsonRead(parameters, "pin", tmp); _pin = tmp.toInt(); - _adc_zero1 = determineVQ(_pin); + jsonRead(parameters, "adczero", tmp); + _adc_zero1 = tmp.toInt(); // determineVQ(_pin); + jsonRead(parameters, "rms", tmp); + _fl_rms = tmp.toInt(); + jsonRead(parameters, "sens", tmp); + _sens = tmp.toInt(); + jsonRead(parameters, "vref", tmp); + _vref = tmp.toInt(); + vacp = _vref/SC_ADC; + k = vacp / (float)_sens; //коэффециент для домножения измерений АЦП } - + void doByInterval() { - - unsigned long currentAcc = 0; - unsigned int count = 0; - unsigned long prevMicros = micros() - _sampleInterval; - while (count < _numSamples) + f_nan = false; + unsigned long currentAcc = 0; + unsigned int count = 0; + unsigned long prevMicros = micros() - _sampleInterval; + while (count < _numSamples) + { + if (micros() - prevMicros >= _sampleInterval) { - if (micros() - prevMicros >= _sampleInterval) - { - int adc_raw = IoTgpio.analogRead(_pin) - _adc_zero1; + int adc_raw = IoTgpio.analogRead(_pin); + if (adc_raw > DEF_NAN) f_nan = true; //Если за цикл измерений не было АЦП больше 50, то считаем что нет датчика + adc_raw -= _adc_zero1; + if (_fl_rms == 0) + currentAcc += (unsigned long)abs(adc_raw); + else currentAcc += (unsigned long)(adc_raw * adc_raw); - ++count; - prevMicros += _sampleInterval; - } + ++count; + prevMicros += _sampleInterval; } - #ifdef ESP32 - value.valD = int(sqrt((float)currentAcc / (float)_numSamples) * (75.7576 / 4095.0)); - #else - value.valD = int(sqrt((float)currentAcc / (float)_numSamples) * (75.7576 / 1023.0)); - #endif + } + + if (_fl_rms == 0) + { +#ifdef ESP32 + value.valD = ((float)currentAcc / (float)_numSamples) * k; +#else + value.valD = ((float)currentAcc / (float)_numSamples) * k; +#endif + } + else + { +#ifdef ESP32 + value.valD = (sqrt((float)currentAcc / (float)_numSamples) * k); +#else + value.valD = (sqrt((float)currentAcc / (float)_numSamples) * k); +#endif + } + if (f_nan) regEvent(value.valD, "Acs712"); + else + regEvent(NAN, "Acs712"); } - + + void onModuleOrder(String &key, String &value) + { + if (key == "setZero") + { + _adc_zero1 = determineVQ(_pin); + SerialPrint("i", F("Acs712"), "User run calibration ADC zero: " + String(_adc_zero1)); + // TODO wtitejson to config.json????? + } + } + int determineVQ(int PIN) { long VQ = 0; - // read 5000 samples to stabilise value - for (int i = 0; i < 5000; i++) + for (int i = 0; i < 100; i++) { VQ += IoTgpio.analogRead(PIN); - //delay(1); // depends on sampling (on filter capacitor), can be 1/80000 (80kHz) max. } - VQ /= 5000; + VQ /= 100; return int(VQ); } - ~Acs712(){}; }; diff --git a/src/modules/sensors/Acs712/modinfo.json b/src/modules/sensors/Acs712/modinfo.json index b9b15347..a3540924 100644 --- a/src/modules/sensors/Acs712/modinfo.json +++ b/src/modules/sensors/Acs712/modinfo.json @@ -3,7 +3,7 @@ "configItem": [ { "name": "Acs712 Ток", - "type": "Reading", + "type": "Reading", "subtype": "Acs712", "id": "amp", "widget": "anydataAmp", @@ -11,26 +11,42 @@ "descr": "Ток", "round": 3, "pin": 39, - "int": 5 + "int": 5, + "rms": 1, + "vref": 5000, + "sens": 100, + "adczero" : 512, + "btn-setZero": "nil" } ], "about": { - "authorName": "Yuriy Kuneev", - "authorContact": "https://t.me/Kuneev07", - "authorGit": "", + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mitchel", + "authorGit": "https://github.com/Mit4el", "exampleURL": "https://iotmanager.org/wiki", "specialThanks": "", "moduleName": "Acs712", - "moduleVersion": "1.0", - "moduleDesc": "Позволяет получить текущее значение тока на аналоговом пине с помощью модуля Acs712.", + "moduleVersion": "2.0", + "moduleDesc": "Позволяет получить текущее значение тока на аналоговом пине с помощью модуля Acs712. Не забываем про делитель для входа на АЦП/", "propInfo": { - "pin": "Аналоговый GPIO номер, к которому подключен датчик.", - "int": "Количество секунд между опросами датчика." + "pin": "Аналоговый GPIO номер, к которому подключен датчик. Для esp8266 0", + "int": "Количество секунд между опросами датчика.", + "rms": "1 - подсчет средне-квадратического тока (переменный), 0 - подсчет средне-арифмитического тока (постоянный)", + "vref": "Vref (мВ) - Опороное наряжение питания Acs712, по умолчанию = 5000мВ", + "sens": "Чувствительность датчика тока: 5A = 185mВ/A , 20A = 100mВ/A , 30A = 66mВ/A ", + "adczero" : "Переменная калибровки нулевого значения отсчетов АЦП при нулевой нагрузке. Для ESP8266 - 512, Для ESP32 -2048, это 2.5В = 0А (1,65 с делителем) для Acs712 20A и 30A при стабильном токе 5В", + "btn-setZero": "Кнопка калибровки нулевого значения отсчетов АЦП при нулевой нагрузке. Нагрузка в момент калибровки должна быть отключена! После перезагрузки будет установлено в значение по умолчанию adczero. Для сохранение смотрим лог, и изменияем adczero" } }, "defActive": true, "usedLibs": { "esp32_4mb": [], - "esp8266_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/modules/sensors/Impulse/Impulse.cpp b/src/modules/sensors/Impulse/Impulse.cpp new file mode 100644 index 00000000..e84a1123 --- /dev/null +++ b/src/modules/sensors/Impulse/Impulse.cpp @@ -0,0 +1,87 @@ +#include "Global.h" +#include "classes/IoTItem.h" + +extern IoTGpio IoTgpio; + +class Impulse : public IoTItem +{ +private: + int _int; + int _pin; + bool _buttonState, _reading; + bool _lastButtonState = LOW; + unsigned long _lastDebounceTime = 0; + int _debounceDelay = 50; + int _count = 0; + unsigned long timing; + +public: + Impulse(String parameters) : IoTItem(parameters) + { + String _pinMode; + jsonRead(parameters, F("pin"), _pin); + jsonRead(parameters, F("pinMode"), _pinMode); + jsonRead(parameters, F("debounceDelay"), _debounceDelay); + jsonRead(parameters, "int", _int); + _round = 0; + + if (_pinMode == F("INPUT")) + IoTgpio.pinMode(_pin, INPUT); + else if (_pinMode == F("INPUT_PULLUP")) + IoTgpio.pinMode(_pin, INPUT_PULLUP); + else if (_pinMode == F("INPUT_PULLDOWN")) + { + IoTgpio.pinMode(_pin, INPUT); + IoTgpio.digitalWrite(_pin, LOW); + } + + value.valD = _buttonState = IoTgpio.digitalRead(_pin); + regEvent(_buttonState, "", false, false); + } + + void loop() + { + _reading = IoTgpio.digitalRead(_pin); + if (_reading != _lastButtonState) + { + + _lastDebounceTime = millis(); + } + + if ((millis() - _lastDebounceTime) > _debounceDelay) + { + if (_reading != _buttonState) + { + _buttonState = _reading; + _count++; + } + if (_count == 1) + { + timing = millis(); + } + if (millis() - timing > _int * 1000 && _count > 1) + { + timing = millis(); + value.valD = _count; + regEvent(value.valD, F("Impulse")); + _count = 0; + } + } + + _lastButtonState = _reading; + } + + ~Impulse(){}; +}; + +void *getAPI_Impulse(String subtype, String param) +{ + if (subtype == F("Impulse")) + { + return new Impulse(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/sensors/Impulse/modinfo.json b/src/modules/sensors/Impulse/modinfo.json new file mode 100644 index 00000000..e35ecf29 --- /dev/null +++ b/src/modules/sensors/Impulse/modinfo.json @@ -0,0 +1,50 @@ +{ + "menuSection": "Сенсоры", + "configItem": [ + { + "global": 0, + "name": "Аналоговый счетчик импульсов", + "type": "Writing", + "subtype": "Impulse", + "id": "impulse", + "widget": "anydataDef", + "page": "Счетчики", + "descr": "Импульсов", + "needSave": 0, + "int": 1, + "pin": 16, + "pinMode": "INPUT", + "debounceDelay": 3, + "multiply": 1 + } + ], + "about": { + "authorName": "AVAKS", + "authorContact": "https://t.me/@avaks_dev", + "authorGit": "https://github.com/avaksru", + "specialThanks": "", + "moduleName": "Impulse", + "moduleVersion": "2.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Аналоговый счетчик импульсов. Датчик Холла ", + "moduleDesc": "Считает количество импульсов за период времени", + "propInfo": { + "int": "Период сбора импульсов в секундах", + "pin": "Укажите GPIO номер пина для чтения импульсов", + "pinMode": "Может быть INPUT_PULLUP INPUT_PULLDOWN INPUT", + "debounceDelay": "Время обработки дребезга (миллисекунд)" + } + }, + "defActive": true, + "usedLibs": { + "esp32_4mb": [], + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [], + "esp8285_1mb": [], + "esp8285_1mb_ota": [] + } +} \ No newline at end of file diff --git a/src/modules/sensors/Pzem004t/modinfo.json b/src/modules/sensors/Pzem004t/modinfo.json index 56a1fd58..697015f9 100644 --- a/src/modules/sensors/Pzem004t/modinfo.json +++ b/src/modules/sensors/Pzem004t/modinfo.json @@ -119,7 +119,7 @@ "moduleDesc": "Считает потраченную электроэнергию, измеряет напряжение, частоту, силу тока и прочие параметры", "propInfo": { "addr": "Адрес modbus", - "int": "Количество секунд между опросами датчика. Желателно устанавливать разные интервалы для параметров что бы опросы происходили в разное время.", + "int": "Количество секунд между опросами датчика. Желателно устанавливать одинаковые интервалы для параметров (для одного адреса Pzem) что опрос происходил один раз, остальные из 500мс буфера.", "changeaddr": "Поставьте этот параметр равным 1 и перезагрузите esp - будет установлен адрес указанный в setaddr. Смотрите в логе результат: [i] Pzem address set: 0x01", "setaddr": "Новый адрес который нужно назначить", "reset": "Поставьте этот параметр равным 1 и pzem будет сброшен к нулю. Смотрите в логе результат: [i] Pzem reset done" 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();