diff --git a/PrepareProject.py b/PrepareProject.py index 7e8a495d..6d480783 100644 --- a/PrepareProject.py +++ b/PrepareProject.py @@ -199,6 +199,7 @@ for section, modules in profJson['modules'].items(): configItemsJson['num'] = itemsCount configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] itemsCount = itemsCount + 1 + configItemsJson['moduleName'] = moduleJson['about']['moduleName'] itemsJson.append(configItemsJson) else: # В первую очередь ищем по имени deviceName, чтобы для данной платы можно было уточнить либы. Если не нашли плату по имени в usedLibs пробуем найти её по типу deviceType if deviceType in moduleJson['usedLibs']: # проверяем поддерживает ли модуль текущее устройство @@ -208,9 +209,10 @@ for section, modules in profJson['modules'].items(): allLibs = allLibs + "\n" + libPath for configItemsJson in moduleJson['configItem']: configItemsJson['num'] = itemsCount - configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] - itemsCount = itemsCount + 1 - itemsJson.append(configItemsJson) + configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] + itemsCount = itemsCount + 1 + itemsJson.append(configItemsJson) + configItemsJson['moduleName'] = moduleJson['about']['moduleName'] with open("data_svelte/items.json", "w", encoding='utf-8') as write_file: json.dump(itemsJson, write_file, ensure_ascii=False, indent=4, sort_keys=False) diff --git a/data_full/build/bundle.css.gz b/data_full/build/bundle.css.gz index 29ab1fa8..19d2f663 100644 Binary files a/data_full/build/bundle.css.gz and b/data_full/build/bundle.css.gz differ diff --git a/data_full/build/bundle.js.gz b/data_full/build/bundle.js.gz index 105e6deb..d4dc70e4 100644 Binary files a/data_full/build/bundle.js.gz and b/data_full/build/bundle.js.gz differ diff --git a/data_full/index.html b/data_full/index.html index bbcd166f..407b5bd5 100644 --- a/data_full/index.html +++ b/data_full/index.html @@ -4,7 +4,7 @@ - IoT Manager 4.5.5 + IoT Manager 4.6.0 diff --git a/data_svelte/build/bundle.css.gz b/data_svelte/build/bundle.css.gz index 29ab1fa8..19d2f663 100644 Binary files a/data_svelte/build/bundle.css.gz and b/data_svelte/build/bundle.css.gz differ diff --git a/data_svelte/build/bundle.js.gz b/data_svelte/build/bundle.js.gz index 105e6deb..d4dc70e4 100644 Binary files a/data_svelte/build/bundle.js.gz and b/data_svelte/build/bundle.js.gz differ diff --git a/data_svelte/index.html b/data_svelte/index.html index bbcd166f..407b5bd5 100644 --- a/data_svelte/index.html +++ b/data_svelte/index.html @@ -4,7 +4,7 @@ - IoT Manager 4.5.5 + IoT Manager 4.6.0 diff --git a/data_svelte/widgets.json b/data_svelte/widgets.json index 21a983fc..ecd0a7e8 100644 --- a/data_svelte/widgets.json +++ b/data_svelte/widgets.json @@ -177,6 +177,39 @@ "maxCount": 86400, "type": "bar" }, + { + "name": "chart4", + "label": "График Часовой", + "widget": "chart", + "dateFormat": "HH:mm", + "maxCount": 3600, + "type": "bar" + }, + { + "name": "chart5", + "label": "График двойной", + "widget": "chart", + "series": [ + "Температура, С", + "Влажность, %" + ], + "dateFormat": "HH:mm", + "maxCount": 86400, + "pointRadius": 0 + }, + { + "name": "chart6", + "label": "График тройной", + "widget": "chart", + "series": [ + "Температура, С", + "Влажность, %", + "Давление, кПа" + ], + "dateFormat": "HH:mm", + "maxCount": 86400, + "pointRadius": 0 + }, { "name": "fillgauge", "label": "Бочка", @@ -321,10 +354,6 @@ "widget": "anydata", "after": "°", "icon": "speedometer" - }, - { - "name": "nil", - "label": "Без виджета" }, { "name": "anydataBar", @@ -332,5 +361,9 @@ "widget": "anydata", "after": "Kg/cm²", "icon": "speedometer" + }, + { + "name": "nil", + "label": "Без виджета" } ] \ No newline at end of file diff --git a/include/Const.h b/include/Const.h index 155bcac5..410b89e6 100644 --- a/include/Const.h +++ b/include/Const.h @@ -2,7 +2,8 @@ #include "BuildTime.h" // Версия прошивки -#define FIRMWARE_VERSION 460 + +#define FIRMWARE_VERSION 462 #ifdef esp8266_1mb_ota #define FIRMWARE_NAME "esp8266_1mb_ota" diff --git a/include/Global.h b/include/Global.h index 0df14fe3..278630f0 100644 --- a/include/Global.h +++ b/include/Global.h @@ -10,6 +10,7 @@ #include #ifdef LIBRETINY +#include #include #include #ifdef STANDARD_WEB_SERVER diff --git a/include/StandWebServer.h b/include/StandWebServer.h index 419a4538..db177ca2 100644 --- a/include/StandWebServer.h +++ b/include/StandWebServer.h @@ -10,6 +10,8 @@ extern void handleFileUpload(); extern void handleFileDelete(); extern void handleFileCreate(); extern void handleLocalOTA(); +extern void handleUpdateOTA(); +extern void handleCors(); extern void handleLocalOTA_Handler(); extern void handleFileList(); //void printDirectory(File dir, String& out); diff --git a/myProfile.json b/myProfile.json index 9a32b8bc..9df5a735 100644 --- a/myProfile.json +++ b/myProfile.json @@ -170,6 +170,14 @@ "path": "src/modules/virtual/Loging", "active": true }, + { + "path": "src/modules/virtual/Loging2", + "active": false + }, + { + "path": "src/modules/virtual/Loging3", + "active": false + }, { "path": "src/modules/virtual/LogingDaily", "active": true @@ -428,7 +436,7 @@ }, { "path": "src/modules/exec/Buzzer", - "active": true + "active": false }, { "path": "src/modules/exec/EctoControlAdapter", @@ -506,6 +514,10 @@ "path": "src/modules/exec/SysExt", "active": false }, + { + "path": "src/modules/exec/Tca9555", + "active": false + }, { "path": "src/modules/exec/Telegram", "active": false diff --git a/src/Main.cpp b/src/Main.cpp index 18fd4a31..d512c7c5 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -224,9 +224,9 @@ void setup() { stopErrorMarker(SETUPINET_ERRORMARKER); - bool postMsgTelegram; - if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1; - sendDebugTraceAndFreeMemory(postMsgTelegram); + // bool postMsgTelegram; + // if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1; + // sendDebugTraceAndFreeMemory(postMsgTelegram); initErrorMarker(SETUPLAST_ERRORMARKER); @@ -276,9 +276,9 @@ void setup() { }, nullptr, true); - // ловим пинги от WS (5сек) и дисконнектим если их нет (20сек) + // ловим пинги от WS (2сек) и дисконнектим если их нет 3 раза 3сек*2прохода = 6сек ts.add( - PiWS, 6000, [&](void*) { + PiWS, 3000, [&](void*) { if (isNetworkActive()) { for (size_t i = 0; i < WEBSOCKETS_CLIENT_MAX; i++) { diff --git a/src/StandWebServer.cpp b/src/StandWebServer.cpp index f28ed01d..4ff4c90c 100644 --- a/src/StandWebServer.cpp +++ b/src/StandWebServer.cpp @@ -9,6 +9,12 @@ 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"; +// Типы обновлений +enum UpdateType { + FIRMWARE, + FILESYSTEM + }; + void standWebServerInit() { // Кэшировать файлы для быстрой работы @@ -88,11 +94,18 @@ void standWebServerInit() { // - 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); - + // отображение страницы с полем ввода для сервера обновления HTTP.on("/localota", HTTP_GET, handleLocalOTA); - + // непосредственно ОТА обновление со стороннего сервера HTTP.on("/localota_handler", HTTP_GET, handleLocalOTA_Handler); + // Обработка обновления от WS drag&drop + HTTP.on("/update", HTTP_POST, []() { + HTTP.send(200); // Для CORS + }, handleUpdateOTA); + + HTTP.on("/update", HTTP_OPTIONS, handleCors); + // Default handler for all URIs not defined above // Use it to read files from filesystem HTTP.onNotFound(handleNotFound); @@ -164,6 +177,66 @@ void handleLocalOTA() { String page = "
"; HTTP.send(200, "text/html", page);} + +void handleCors() { + HTTP.sendHeader("Access-Control-Allow-Origin", "*"); + HTTP.send(200); + } + + void handleUpdateOTA() { + UpdateType typeOTAfile = FIRMWARE; + HTTPUpload& upload = HTTP.upload(); + if (upload.filename != "firmware.bin" && upload.filename != "littlefs.bin") + { + SerialPrint("E", F("OTA"), "Неверное имя файла: " + upload.filename); + return; + } + if (upload.filename == "firmware.bin") + { + typeOTAfile = FIRMWARE; + } else if (upload.filename == "littlefs.bin") + { + typeOTAfile = FILESYSTEM; + } + #ifdef ESP8266 + size_t size = upload.totalSize; + int updatePartition = (typeOTAfile == FIRMWARE)? U_FLASH : U_FS; //U_FS + //#endif + #else //ESP32 + size_t size = UPDATE_SIZE_UNKNOWN; + int updatePartition = (typeOTAfile == FIRMWARE)? U_FLASH : U_SPIFFS; //U_FS + #endif + if (upload.status == UPLOAD_FILE_START) { + //Serial.print("Начало загрузки: "); + //Serial.println(upload.filename); + SerialPrint("i", F("OTA"), "Начало загрузки файла: " + upload.filename); + if (!Update.begin(size, updatePartition)) { // UPDATE_SIZE_UNKNOWN 0xFFFFFFFF + Update.end(); + SerialPrint("E", F("OTA"), "Ошибка: Недостаточно памяти"); + HTTP.send(500, "text/plain", "Ошибка: Недостаточно памяти"); + return; + } + } + else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Update.end(); + SerialPrint("E", F("OTA"), "Ошибка записи данных"); + HTTP.send(500, "text/plain", "Ошибка записи данных"); + return; + } + } + else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { // true - перезагрузка после обновления + SerialPrint("i", F("OTA"), "Обновление завершено"); + HTTP.send(200, "text/plain", "Обновление успешно"); + ESP.restart(); + } else { + Update.end(); + SerialPrint("E", F("OTA"), "Ошибка завершения обновления"); + HTTP.send(500, "text/plain", "Ошибка завершения обновления"); + } + } + } void handleLocalOTA_Handler() { String serverValue = HTTP.arg("server"); upgrade_firmware(3,serverValue); diff --git a/src/WsServer.cpp b/src/WsServer.cpp index a4f5268a..0d2b3301 100644 --- a/src/WsServer.cpp +++ b/src/WsServer.cpp @@ -59,9 +59,9 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) //----------------------------------------------------------------------// // Страница веб интерфейса dashboard //----------------------------------------------------------------------// - if (headerStr == "p|") { - standWebSocket.sendTXT(num, "p|"); - //Serial.printf("Ping client: %u\n", num); + if (headerStr == "/pi|") { + standWebSocket.sendTXT(num, "/po|"); + Serial.printf("Ping client: %u\n", num); ws_clients[num]=1; } // публикация всех виджетов @@ -233,6 +233,50 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) sendStringToWs("settin", settingsFlashJson, num); } + if (headerStr == "/localt|") { + String timeStr = String((char*)payload + 8); + //Serial.println("Время с фронта: /localt|" + timeStr); + + // Обрезаем дробную часть, если есть + int dotIndex = timeStr.indexOf('.'); + if (dotIndex != -1) { + timeStr = timeStr.substring(0, dotIndex); + } + + // Парсим UNIX-время в секундах + time_t unixTime = (time_t)timeStr.toInt(); + + // Создаём структуру timeval + timeval tv; + tv.tv_sec = unixTime; // Секунды эпохи + tv.tv_usec = 0; // Микросекунды + + // Устанавливаем время + if (settimeofday(&tv, NULL) == 0) { + //Serial.printf("Время установлено: %ld\n", unixTime); + #ifdef LIBRETINY + SerialPrint("i", F("Time"), "Время установлено из браузера: "); + #else + SerialPrint("i", F("Time"), "Время установлено из браузера: " + String(unixTime)); + #endif + } else { + #ifdef LIBRETINY + //Serial.printf("Ошибка установки времени: %ld\n", unixTime); + SerialPrint("i", F("=>WS"), "Ошибка установки времени: "); + #else + SerialPrint("i", F("=>WS"), "Ошибка установки времени: " + String(unixTime)); + #endif + } + // timeval tv2{0, 0}; + // timezone tz = timezone{0, 0}; + // time_t epoch = 0; + // if (gettimeofday(&tv2, &tz) != -1) { + // epoch = tv2.tv_sec; + // } + // unixTime = epoch; + // SerialPrint("I", F("NTP"), "TIME " + String(unixTime)); + } + //----------------------------------------------------------------------// // Страница веб интерфейса dev //----------------------------------------------------------------------// diff --git a/src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp b/src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp index ea4700cc..18511cc6 100644 --- a/src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp +++ b/src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp @@ -8,7 +8,6 @@ // #include "Stream.h" #include - // class ModbusUart; Stream *_modbusUART = nullptr; @@ -21,7 +20,6 @@ uint8_t _DIR_PIN = 0; #define MODBUS_TX_PIN 19 // Tx pin #define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication - void modbusPreTransmission() { // delay(500); @@ -52,7 +50,7 @@ private: int protocol = SERIAL_8N1; uint8_t _addr = 0xF0; // Адрес слейва от 1 до 247 uint8_t _type = 0x14; // Тип устройства: 0x14 – адаптер OpenTherm (вторая версия); 0x11 – адаптер OpenTherm (первая версия, снята с производства) - bool _debugLevel; // Дебаг + uint8_t _debugLevel; // Дебаг ModbusMaster node; uint8_t _debug; @@ -136,36 +134,39 @@ public: { SerialPrint("E", "EctoControlAdapter", "Не подходящее устройство, type: " + String(type, HEX)); } + getModelVersion(); + getBoilerInfo(); + getBoilerStatus(); } else if (_addr == 0) { // если адреса нет, то шлем широковещательный запрос адреса uint8_t addr = node.readAddresEctoControl(); SerialPrint("I", "EctoControlAdapter", "readAddresEctoControl, addr: " + String(addr, HEX) + " - Enter to configuration"); } - getModelVersion(); - getBoilerInfo(); - getBoilerStatus(); } void doByInterval() { - // readBoilerInfo(); - getBoilerStatus(); + if (_addr > 0) + { + // readBoilerInfo(); + getBoilerStatus(); - getCodeError(); - getCodeErrorExt(); - if (info.adapterType == 0) - getFlagErrorOT(); - // getFlowRate(); - // getMaxSetCH(); - // getMaxSetDHW(); - // getMinSetCH(); - // getMinSetDHW(); - getModLevel(); - getPressure(); - getTempCH(); - getTempDHW(); - getTempOutside(); + getCodeError(); + getCodeErrorExt(); + if (info.adapterType == 0) + getFlagErrorOT(); + // getFlowRate(); + // getMaxSetCH(); + // getMaxSetDHW(); + // getMinSetCH(); + // getMinSetDHW(); + getModLevel(); + getPressure(); + getTempCH(); + getTempDHW(); + getTempOutside(); + } } void loop() @@ -176,10 +177,6 @@ public: IoTValue execute(String command, std::vector ¶m) { - if (command == "getModelVersion") - { - getModelVersion(); - } if (command == "getModelVersion") { getModelVersion(); @@ -324,13 +321,12 @@ public: tlgrmItem->sendTelegramMsg(false, msg); } - ~EctoControlAdapter() { - }; + ~EctoControlAdapter(){}; bool writeFunctionModBus(const uint16_t ®, uint16_t &data) { // set word 0 of TX buffer to least-significant word of counter (bits 15..0) - //node.setTransmitBuffer(1, lowWord(data)); + // node.setTransmitBuffer(1, lowWord(data)); // set word 1 of TX buffer to most-significant word of counter (bits 31..16) node.setTransmitBuffer(0, data); // slave: write TX buffer to (2) 16-bit registers starting at register 0 @@ -348,6 +344,7 @@ public: bool readFunctionModBus(const uint16_t ®, uint16_t &reading) { + if (_addr == 0) return false; // float retValue = 0; if (_modbusUART) { diff --git a/src/modules/exec/EctoControlAdapter/ModbusEC.cpp b/src/modules/exec/EctoControlAdapter/ModbusEC.cpp index 029e448b..d9d0818e 100644 --- a/src/modules/exec/EctoControlAdapter/ModbusEC.cpp +++ b/src/modules/exec/EctoControlAdapter/ModbusEC.cpp @@ -1,6 +1,9 @@ #include "ModbusEC.h" +#define COUNT_BIT_AVAIL 5 +#define COUNT_BIT_AVAIL_46F 4 + ModbusMaster::ModbusMaster(void) { _idle = 0; @@ -256,8 +259,8 @@ uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress, /** Modbus function 0x06 Write Single Register. -This function code is used to write a single holding register in a -remote device. The request specifies the address of the register to be +This function code is used to write a single holding register in a +remote device. The request specifies the address of the register to be written. Registers are addressed starting at zero. @param u16WriteAddress address of the holding register (0x0000..0xFFFF) @@ -266,7 +269,7 @@ written. Registers are addressed starting at zero. @ingroup register */ uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, - uint16_t u16WriteValue) + uint16_t u16WriteValue) { _u16WriteAddress = u16WriteAddress; _u16WriteQty = 0; @@ -274,7 +277,6 @@ uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, return ModbusMasterTransaction(ku8MBWriteSingleRegister); } - /** Modbus function 0x10 Write Multiple Registers. @@ -308,7 +310,8 @@ uint8_t ModbusMaster::readAddresEctoControl() { _u16ReadAddress = 0x00; _u16ReadQty = 1; - return ModbusMasterTransaction(ku8MBProgRead46); + ModbusMasterTransaction(ku8MBProgRead46); + return getResponseBuffer(0x00); } uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr) { @@ -393,8 +396,17 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) { u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); } + // if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47) + // { + // u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC); + // u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC); + // } + // else + // { u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC); u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC); + // } + u8ModbusADU[u8ModbusADUSize] = 0; // flush receive buffer before transmitting request @@ -427,7 +439,9 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) #if __MODBUSMASTER_DEBUG__ digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, true); #endif - u8ModbusADU[u8ModbusADUSize++] = _serial->read(); + uint8_t req = _serial->read(); + u8ModbusADU[u8ModbusADUSize++] = req; + Serial.print(req, HEX); u8BytesLeft--; #if __MODBUSMASTER_DEBUG__ digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false); @@ -448,26 +462,34 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) } // evaluate slave ID, function code once enough bytes have been read - if (u8ModbusADUSize == 5) + uint8_t count; + if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47) + count = COUNT_BIT_AVAIL_46F; + else + count = COUNT_BIT_AVAIL; + + if (u8ModbusADUSize == count) { - // verify response is for correct Modbus slave - if (u8ModbusADU[0] != _u8MBSlave) + if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47) { - // Serial.print(u8ModbusADU[0], HEX); - // Serial.print(" != "); - // Serial.println(_u8MBSlave, HEX); - - u8MBStatus = ku8MBInvalidSlaveID; - break; - } + // verify response is for correct Modbus slave + if (u8ModbusADU[0] != _u8MBSlave) + { + // Serial.print(u8ModbusADU[0], HEX); + // Serial.print(" != "); + // Serial.println(_u8MBSlave, HEX); - // verify response is for correct Modbus function code (mask exception bit 7) - if ((u8ModbusADU[1] & 0x7F) != u8MBFunction) - { - u8MBStatus = ku8MBInvalidFunction; - break; - } + u8MBStatus = ku8MBInvalidSlaveID; + break; + } + // verify response is for correct Modbus function code (mask exception bit 7) + if ((u8ModbusADU[1] & 0x7F) != u8MBFunction) + { + u8MBStatus = ku8MBInvalidFunction; + break; + } + } // check whether Modbus exception occurred; return Modbus Exception Code if (bitRead(u8ModbusADU[1], 7)) { @@ -485,6 +507,16 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) case ku8MBWriteMultipleRegisters: u8BytesLeft = 3; break; + + case ku8MBProgRead46: + u8BytesLeft = 1; + break; + + case ku8MBProgWrite47: + u8BytesLeft = 1; + break; + + default: } } if ((millis() - u32StartTime) > ku16MBResponseTimeout) @@ -493,24 +525,26 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) } } - // verify response is large enough to inspect further - if (!u8MBStatus && u8ModbusADUSize >= 5) + if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47) { - // calculate CRC - u16CRC = 0xFFFF; - for (i = 0; i < (u8ModbusADUSize - 2); i++) + // verify response is large enough to inspect further + if (!u8MBStatus && u8ModbusADUSize >= COUNT_BIT_AVAIL) { - u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); - } + // calculate CRC + u16CRC = 0xFFFF; + for (i = 0; i < (u8ModbusADUSize - 2); i++) + { + u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); + } - // verify CRC - if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] || - highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])) - { - u8MBStatus = ku8MBInvalidCRC; + // verify CRC + if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] || + highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])) + { + u8MBStatus = ku8MBInvalidCRC; + } } } - // disassemble ADU into words if (!u8MBStatus) { @@ -530,6 +564,12 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) } break; case ku8MBProgRead46: + Serial.print("ku8MBProgRead46"); + for (i = 0; i < (u8ModbusADUSize); i++) + { + Serial.println(u8ModbusADU[i], HEX); + } + _u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2]; _u8ResponseBufferLength = 1; break; diff --git a/src/modules/virtual/Loging2/Loging2.cpp b/src/modules/virtual/Loging2/Loging2.cpp new file mode 100644 index 00000000..3749bbb8 --- /dev/null +++ b/src/modules/virtual/Loging2/Loging2.cpp @@ -0,0 +1,495 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPConfiguration.h" +#include "NTP.h" + +void *getAPI_Date2(String params); + +class Loging2 : public IoTItem +{ +private: + String logid1; + String logid2; + String id; + String tmpValue; + String filesList = ""; + + int _publishType = -2; + int _wsNum = -1; + + int points; + // int keepdays; + + IoTItem *dateIoTItem; + + String prevDate = ""; + bool firstTimeInit = true; + + // long interval; + +public: + Loging2(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("logid1"), logid1); + jsonRead(parameters, F("logid2"), logid2); + jsonRead(parameters, F("id"), id); + jsonRead(parameters, F("points"), points); + if (points > 300) + { + points = 300; + SerialPrint("E", F("Loging2"), "'" + id + "' user set more points than allowed, value reset to 300"); + } + long interval; + jsonRead(parameters, F("int"), interval); // в минутах + setInterval(interval * 60); + // jsonRead(parameters, F("keepdays"), keepdays, false); + + // создадим экземпляр класса даты + dateIoTItem = (IoTItem *)getAPI_Date2("{\"id\": \"" + id + "-date\",\"int\":\"20\",\"subtype\":\"date\"}"); + IoTItems.push_back(dateIoTItem); + SerialPrint("I", F("Loging2"), "created date instance " + id); + } + + void doByInterval() + { + // если объект логгирования не был создан + if (!isItemExist(logid1)) + { + SerialPrint("E", F("Loging2"), "'" + id + "' loging object not exist, return"); + return; + } + + String value = getItemValue(logid1); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging2"), "'" + id + "' loging value is empty, return"); + return; + } + String value2 = getItemValue(logid2); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging2"), "'" + id + "' loging value is empty, return"); + return; + } + + // если время не было получено из интернета + if (!isTimeSynch) + { + SerialPrint("E", F("Loging2"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + + regEvent(value, F("Loging2")); + + String logData2; + + jsonWriteInt(logData2, "x", unixTime, false); + jsonWriteFloat(logData2, "y1", value.toFloat(), false); + jsonWriteFloat(logData2, "y2", value2.toFloat(), false); + + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") + { + SerialPrint("E", F("Loging2"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData2); + return; + } + else + { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { + SerialPrint("E", F("Loging2"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData2); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging2"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) + { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData2); + // если больше или поменялась дата то создадим следующий файл + } + else + { + createNewFileWithData(logData2); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } +/* + void SetDoByInterval(String valse) + { + String value = valse; + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' loging value is empty, return"); + return; + } + // если время не было получено из интернета + if (!isTimeSynch) + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + regEvent(value, F("Loging2Event")); + String logData2; + jsonWriteInt(logData2, "x", unixTime, false); + jsonWriteFloat(logData2, "y1", value.toFloat(), false); + jsonWriteFloat(logData2, "y2", value.toFloat(), false); + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData2); + return; + } + else + { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData2); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging2Event"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) + { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData2); + // если больше или поменялась дата то создадим следующий файл + } + else + { + createNewFileWithData(logData2); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } +*/ + void createNewFileWithData(String &logData) + { + logData = logData + ","; + String path = "/lg2/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt + // создадим пустой файл + if (writeEmptyFile(path) != "success") + { + SerialPrint("E", F("Loging2"), "'" + id + "' file writing error, return"); + return; + } + + // запишем в него данные + if (addFile(path, logData) != "success") + { + SerialPrint("E", F("Loging2"), "'" + id + "' data writing error, return"); + return; + } + // запишем путь к нему в базу данных + if (saveDataDB(id, path) != "success") + { + SerialPrint("E", F("Loging2"), "'" + id + "' db file writing error, return"); + return; + } + SerialPrint("i", F("Loging2"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); + } + + void addNewDataToExistingFile(String &path, String &logData) + { + logData = logData + ","; + if (addFile(path, logData) != "success") + { + SerialPrint("i", F("Loging2"), "'" + id + "' file writing error, return"); + return; + }; + SerialPrint("i", F("Loging2"), "'" + id + "' loging in file http://" + WiFi.localIP().toString() + path); + } + + // данная функция уже перенесена в ядро и будет удалена в последствии + bool hasDayChanged() + { + bool changed = false; + String currentDate = getTodayDateDotFormated(); + if (!firstTimeInit) + { + if (prevDate != currentDate) + { + changed = true; + SerialPrint("i", F("NTP"), F("Change day event")); +#if defined(ESP8266) + FileFS.gc(); +#endif +#if defined(ESP32) +#endif + } + } + firstTimeInit = false; + prevDate = currentDate; + return changed; + } + + void publishValue() + { + String dir = "/lg2/" + id; + filesList = getFilesList(dir); + + SerialPrint("i", F("Loging2"), "file list: " + filesList); + + int f = 0; + + bool noData = true; + + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + + path = "/lg2/" + id + path; + + f++; + + unsigned long fileUnixTimeLocal = getFileUnixLocalTime(path); + + unsigned long reqUnixTime = strDateToUnix(getItemValue(id + "-date")); + if (fileUnixTimeLocal > reqUnixTime && fileUnixTimeLocal < reqUnixTime + 86400) + { + noData = false; + String json = getAdditionalJson(); + if (_publishType == TO_MQTT) + { + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + else if (_publishType == TO_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + } + else if (_publishType == TO_MQTT_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + SerialPrint("i", F("Loging2"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", sent"); + } + else + { + SerialPrint("i", F("Loging2"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", skipped"); + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + // если данных нет отправляем пустой грфик + if (noData) + { + clearValue(); + } + } + + String getAdditionalJson() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; + return json; + } + + void publishChartToWsSinglePoint(String value) + { + String topic = mqttRootDevice + "/" + id; + String value2 = getItemValue(logid2); + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + ",\"y2\":" + value2 + "}]}"; + sendStringToWs("chartb", json, -1); + } + + void clearValue() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}"; + sendStringToWs("chartb", json, -1); + } + + void clearHistory() + { + String dir = "/lg2/" + id; + cleanDirectory(dir); + } + + void deleteLastFile() + { + IoTFSInfo tmp = getFSInfo(); + SerialPrint("i", "Loging2", String(tmp.freePer) + " % free flash remaining"); + if (tmp.freePer <= 20.00) + { + String dir = "/lg/" + id; + filesList = getFilesList(dir); + int i = 0; + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + path = dir + path; + i++; + if (i == 1) + { + removeFile(path); + SerialPrint("!", "Loging2", String(i) + ") " + path + " => oldest files been deleted"); + return; + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + } + } + + void setPublishDestination(int publishType, int wsNum) + { + _publishType = publishType; + _wsNum = wsNum; + } + + String getValue() + { + return ""; + } + /* + void loop() { + if (enableDoByInt) { + currentMillis = millis(); + difference = currentMillis - prevMillis; + if (difference >= interval) { + prevMillis = millis(); + if (interval != 0) { + this->doByInterval(); + } + } + } + } + */ + void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true) + { + String userDate = getItemValue(id + "-date"); + String currentDate = getTodayDateDotFormated(); + // отправляем в график данные только когда выбран сегодняшний день + if (userDate == currentDate) + { + // generateEvent(_id, value); + // publishStatusMqtt(_id, value); + + publishChartToWsSinglePoint(value); + // SerialPrint("i", "Sensor " + consoleInfo, "'" + _id + "' data: " + value + "'"); + } + } + + // просто максимальное количество точек + int calculateMaxCount() + { + return 86400; + } + + // путь вида: /lg/log/1231231.txt + unsigned long getFileUnixLocalTime(String path) + { + return gmtTimeToLocal(selectToMarkerLast(deleteToMarkerLast(path, "."), "/").toInt() + START_DATETIME); + } + /* + void setValue(const IoTValue &Value, bool genEvent = true) + { + value = Value; + this->SetDoByInterval(String(value.valD)); + SerialPrint("i", "Loging2", "setValue:" + String(value.valD)); + regEvent(value.valS, "Loging2", false, genEvent); + } +*/ +}; + +void *getAPI_Loging2(String subtype, String param) +{ + if (subtype == F("Loging2")) + { + return new Loging2(param); + } + else + { + return nullptr; + } +} + +class Date : public IoTItem +{ +private: + bool firstTime = true; + +public: + String id; + Date(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("id"), id); + value.isDecimal = false; + } + + void setValue(const String &valStr, bool genEvent = true) + { + value.valS = valStr; + setValue(value, genEvent); + } + + void setValue(const IoTValue &Value, bool genEvent = true) + { + value = Value; + regEvent(value.valS, "", false, genEvent); + // отправка данных при изменении даты + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->getSubtype() == "Loging2") + { + if ((*it)->getID() == selectToMarker(id, "-")) + { + (*it)->setPublishDestination(TO_MQTT_WS, -1); + (*it)->publishValue(); + } + } + } + } + + void setTodayDate() + { + setValue(getTodayDateDotFormated()); + SerialPrint("E", F("Loging2"), "today date set " + getTodayDateDotFormated()); + } + + void doByInterval() + { + if (isTimeSynch) + { + if (firstTime) + { + setTodayDate(); + firstTime = false; + } + } + } +}; + +void *getAPI_Date2(String param) +{ + return new Date(param); +} diff --git a/src/modules/virtual/Loging2/modinfo.json b/src/modules/virtual/Loging2/modinfo.json new file mode 100644 index 00000000..0841e795 --- /dev/null +++ b/src/modules/virtual/Loging2/modinfo.json @@ -0,0 +1,48 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "Двойной график", + "type": "Writing", + "subtype": "Loging2", + "id": "log2", + "widget": "chart5", + "page": "Графики", + "descr": "Датчик", + "num": 1, + "int": 5, + "logid1": "t", + "logid2": "h", + "points": 300, + "series1": "Температура, С", + "series2": "Влажность, %" + } + ], + "about": { + "authorName": "Serghei Crasnicov", + "authorContact": "https://t.me/Serghei63", + "authorGit": "https://github.com/Serghei63", + "specialThanks": "@itsid1 @Valiuhaaa Serg", + "moduleName": "Loging2", + "moduleVersion": "0.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Логирование в график", + "moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов", + "propInfo": { + "int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут", + "logid1": "ID 1 величины которую будем логировать (температура)", + "logid2": "ID 2 величины которую будем логировать (влажность)", + "points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр" + } + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [] + + } +} \ No newline at end of file diff --git a/src/modules/virtual/Loging3/Loging3.cpp b/src/modules/virtual/Loging3/Loging3.cpp new file mode 100644 index 00000000..fa9254f3 --- /dev/null +++ b/src/modules/virtual/Loging3/Loging3.cpp @@ -0,0 +1,498 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPConfiguration.h" +#include "NTP.h" + +void *getAPI_Date3(String params); + +class Loging3 : public IoTItem +{ +private: + String logid1; + String logid2; + String logid3; + String id; + String tmpValue; + String filesList = ""; + + int _publishType = -2; + int _wsNum = -1; + + int points; + // int keepdays; + + IoTItem *dateIoTItem; + + String prevDate = ""; + bool firstTimeInit = true; + + // long interval; + +public: + Loging3(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("logid1"), logid1); + jsonRead(parameters, F("logid2"), logid2); + jsonRead(parameters, F("logid3"), logid3); + jsonRead(parameters, F("id"), id); + jsonRead(parameters, F("points"), points); + if (points > 300) + { + points = 300; + SerialPrint("E", F("Loging3"), "'" + id + "' user set more points than allowed, value reset to 300"); + } + + long interval; + jsonRead(parameters, F("int"), interval); // в минутах + setInterval(interval * 60); + // jsonRead(parameters, F("keepdays"), keepdays, false); + + // создадим экземпляр класса даты + dateIoTItem = (IoTItem *)getAPI_Date3("{\"id\": \"" + id + "-date\",\"int\":\"20\",\"subtype\":\"date\"}"); + IoTItems.push_back(dateIoTItem); + SerialPrint("I", F("Loging3"), "created date instance " + id); + } + + void doByInterval() + { + // если объект логгирования не был создан + if (!isItemExist(logid1)) + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging object not exist, return"); + return; + } + + String value = getItemValue(logid1); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging value is empty, return"); + return; + } + String value2 = getItemValue(logid2); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging value is empty, return"); + return; + } + String value3 = getItemValue(logid3); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging value is empty, return"); + return; + } + + // если время не было получено из интернета + if (!isTimeSynch) + { + SerialPrint("E", F("Loging"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + + regEvent(value, F("Loging3")); + + // String logData2; + String logData3; + + jsonWriteInt(logData3, "x", unixTime, false); + jsonWriteFloat(logData3, "y1", value.toFloat(), false); + jsonWriteFloat(logData3, "y2", value2.toFloat(), false); + jsonWriteFloat(logData3, "y3", value3.toFloat(), false); + + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData3); + return; + } + else + { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { + SerialPrint("E", F("Loging3"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData3); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging3"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) + { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData3); + // если больше или поменялась дата то создадим следующий файл + } + else + { + createNewFileWithData(logData3); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } + /* + void SetDoByInterval(String valse) { + String value = valse; + // если значение логгирования пустое + if (value == "") { + SerialPrint("E", F("Loging3Event"), "'" + id + "' loging value is empty, return"); + return; + } + // если время не было получено из интернета + if (!isTimeSynch) { + SerialPrint("E", F("Loging3Event"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + regEvent(value, F("Loging3Event")); + String logData3; + jsonWriteInt(logData3, "x", unixTime, false); + jsonWriteFloat(logData3, "y1", value.toFloat(), false); + jsonWriteFloat(logData3, "y2", value.toFloat(), false); + jsonWriteFloat(logData3, "y3", value.toFloat(), false); + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") { + SerialPrint("E", F("Loging3Event"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData3); + return; + } else { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) { + SerialPrint("E", F("Loging3Event"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData3); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging3Event"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData3); + // если больше или поменялась дата то создадим следующий файл + } else { + createNewFileWithData(logData3); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } + */ + void createNewFileWithData(String &logData) + { + logData = logData + ","; + String path = "/lg3/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt + // создадим пустой файл + if (writeEmptyFile(path) != "success") + { + SerialPrint("E", F("Loging"), "'" + id + "' file writing error, return"); + return; + } + + // запишем в него данные + if (addFile(path, logData) != "success") + { + SerialPrint("E", F("Loging3"), "'" + id + "' data writing error, return"); + return; + } + // запишем путь к нему в базу данных + if (saveDataDB(id, path) != "success") + { + SerialPrint("E", F("Loging3"), "'" + id + "' db file writing error, return"); + return; + } + SerialPrint("i", F("Loging3"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); + } + + void addNewDataToExistingFile(String &path, String &logData) + { + logData = logData + ","; + if (addFile(path, logData) != "success") + { + SerialPrint("i", F("Loging3"), "'" + id + "' file writing error, return"); + return; + }; + SerialPrint("i", F("Loging3"), "'" + id + "' loging in file http://" + WiFi.localIP().toString() + path); + } + + // данная функция уже перенесена в ядро и будет удалена в последствии + bool hasDayChanged() + { + bool changed = false; + String currentDate = getTodayDateDotFormated(); + if (!firstTimeInit) + { + if (prevDate != currentDate) + { + changed = true; + SerialPrint("i", F("NTP"), F("Change day event")); +#if defined(ESP8266) + FileFS.gc(); +#endif +#if defined(ESP32) +#endif + } + } + firstTimeInit = false; + prevDate = currentDate; + return changed; + } + + void publishValue() + { + String dir = "/lg3/" + id; + filesList = getFilesList(dir); + + SerialPrint("i", F("Loging3"), "file list: " + filesList); + + int f = 0; + + bool noData = true; + + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + + path = "/lg3/" + id + path; + + f++; + + unsigned long fileUnixTimeLocal = getFileUnixLocalTime(path); + + unsigned long reqUnixTime = strDateToUnix(getItemValue(id + "-date")); + if (fileUnixTimeLocal > reqUnixTime && fileUnixTimeLocal < reqUnixTime + 86400) + { + noData = false; + String json = getAdditionalJson(); + if (_publishType == TO_MQTT) + { + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + else if (_publishType == TO_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + } + else if (_publishType == TO_MQTT_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + SerialPrint("i", F("Loging3"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", sent"); + } + else + { + SerialPrint("i", F("Loging3"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", skipped"); + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + // если данных нет отправляем пустой грфик + if (noData) + { + clearValue(); + } + } + + String getAdditionalJson() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; + return json; + } + + void publishChartToWsSinglePoint(String value) + { + String topic = mqttRootDevice + "/" + id; + String value2 = getItemValue(logid2); + String value3 = getItemValue(logid3); + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + ",\"y2\":" + value2 + ",\"y3\":" + value3 + "}]}"; + sendStringToWs("chartb", json, -1); + } + + void clearValue() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}"; + sendStringToWs("chartb", json, -1); + } + + void clearHistory() + { + String dir = "/lg3/" + id; + cleanDirectory(dir); + } + + void deleteLastFile() + { + IoTFSInfo tmp = getFSInfo(); + SerialPrint("i", "Loging3", String(tmp.freePer) + " % free flash remaining"); + if (tmp.freePer <= 20.00) + { + String dir = "/lg3/" + id; + filesList = getFilesList(dir); + int i = 0; + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + path = dir + path; + i++; + if (i == 1) + { + removeFile(path); + SerialPrint("!", "Loging3", String(i) + ") " + path + " => oldest files been deleted"); + return; + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + } + } + + void setPublishDestination(int publishType, int wsNum) + { + _publishType = publishType; + _wsNum = wsNum; + } + + String getValue() + { + return ""; + } + /* + void loop() { + if (enableDoByInt) { + currentMillis = millis(); + difference = currentMillis - prevMillis; + if (difference >= interval) { + prevMillis = millis(); + if (interval != 0) { + this->doByInterval(); + } + } + } + } + */ + void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true) + { + String userDate = getItemValue(id + "-date"); + String currentDate = getTodayDateDotFormated(); + // отправляем в график данные только когда выбран сегодняшний день + if (userDate == currentDate) + { + // generateEvent(_id, value); + // publishStatusMqtt(_id, value); + + publishChartToWsSinglePoint(value); + // SerialPrint("i", "Sensor " + consoleInfo, "'" + _id + "' data: " + value + "'"); + } + } + + // просто максимальное количество точек + int calculateMaxCount() + { + return 86400; + } + + // путь вида: /lg/log/1231231.txt + unsigned long getFileUnixLocalTime(String path) + { + return gmtTimeToLocal(selectToMarkerLast(deleteToMarkerLast(path, "."), "/").toInt() + START_DATETIME); + } + /* + void setValue(const IoTValue &Value, bool genEvent = true) { + value = Value; + this->SetDoByInterval(String(value.valD)); + SerialPrint("i", "Loging3", "setValue:" + String(value.valD)); + regEvent(value.valS, "Loging3", false, genEvent); + } + */ +}; + +void *getAPI_Loging3(String subtype, String param) +{ + if (subtype == F("Loging3")) + { + return new Loging3(param); + } + else + { + return nullptr; + } +} + +class Date : public IoTItem +{ +private: + bool firstTime = true; + +public: + String id; + Date(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("id"), id); + value.isDecimal = false; + } + + void setValue(const String &valStr, bool genEvent = true) + { + value.valS = valStr; + setValue(value, genEvent); + } + + void setValue(const IoTValue &Value, bool genEvent = true) + { + value = Value; + regEvent(value.valS, "", false, genEvent); + // отправка данных при изменении даты + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->getSubtype() == "Loging3") + { + if ((*it)->getID() == selectToMarker(id, "-")) + { + (*it)->setPublishDestination(TO_MQTT_WS, -1); + (*it)->publishValue(); + } + } + } + } + + void setTodayDate() + { + setValue(getTodayDateDotFormated()); + SerialPrint("E", F("Loging3"), "today date set " + getTodayDateDotFormated()); + } + + void doByInterval() + { + if (isTimeSynch) + { + if (firstTime) + { + setTodayDate(); + firstTime = false; + } + } + } +}; + +void *getAPI_Date3(String param) +{ + return new Date(param); +} diff --git a/src/modules/virtual/Loging3/modinfo.json b/src/modules/virtual/Loging3/modinfo.json new file mode 100644 index 00000000..11a9420d --- /dev/null +++ b/src/modules/virtual/Loging3/modinfo.json @@ -0,0 +1,51 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "Тройной график", + "type": "Writing", + "subtype": "Loging3", + "id": "log3", + "widget": "chart6", + "page": "Графики", + "descr": "Датчик", + "num": 1, + "int": 5, + "logid1": "t", + "logid2": "h", + "logid3": "p", + "points": 300, + "series1": "Температура, С", + "series2": "Влажность, %", + "series3": "Давление, кПа" + } + ], + "about": { + "authorName": "Serghei Crasnicov", + "authorContact": "https://t.me/Serghei63", + "authorGit": "https://github.com/Serghei63", + "specialThanks": "@itsid1 @Valiuhaaa Serg", + "moduleName": "Loging3", + "moduleVersion": "0.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Логирование в график", + "moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов", + "propInfo": { + "int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут", + "logid1": "ID 1 величины которую будем логировать (температура)", + "logid2": "ID 2 величины которую будем логировать (влажность)", + "logid3": "ID 3 величины которую будем логировать (давление)", + "points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр" + } + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [] + + } +} \ No newline at end of file diff --git a/src/modules/virtual/LogingHourly/LogingHourly.cpp b/src/modules/virtual/LogingHourly/LogingHourly.cpp index 4d4ed935..74ff9c74 100644 --- a/src/modules/virtual/LogingHourly/LogingHourly.cpp +++ b/src/modules/virtual/LogingHourly/LogingHourly.cpp @@ -160,7 +160,11 @@ public: SerialPrint("E", F("LogingHourly"), "'" + id + "' db file writing error, return"); return; } + #ifdef LIBRETINY + SerialPrint("i", F("LogingHourly"), "'" + id + "' file created http://" + ipToString(WiFi.localIP()) + path); + #else SerialPrint("i", F("LogingHourly"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); + #endif } void addNewDataToExistingFile(String &path, String &logData) @@ -171,7 +175,11 @@ public: SerialPrint("i", F("LogingHourly"), "'" + id + "' file writing error, return"); return; }; + #ifdef LIBRETINY + SerialPrint("i", F("LogingHourly"), "'" + id + "' LogingHourly in file http://" + ipToString(WiFi.localIP()) + path); + #else SerialPrint("i", F("LogingHourly"), "'" + id + "' LogingHourly in file http://" + WiFi.localIP().toString() + path); + #endif } const String getTimeLocal_hh() { diff --git a/src/utils/WiFiUtils.cpp b/src/utils/WiFiUtils.cpp index 6ebcc37d..25cb5547 100644 --- a/src/utils/WiFiUtils.cpp +++ b/src/utils/WiFiUtils.cpp @@ -3,6 +3,7 @@ #if defined(ESP32) #include #endif +#include "DebugTrace.h" #define TRIESONE 20 // количество секунд ожидания подключения к одной сети из несколких #define TRIES 30 // количество секунд ожидания подключения сети если она одна @@ -50,6 +51,10 @@ void WiFiEvent(arduino_event_t *event) mqttInit(); SerialPrint("i", F("WIFI"), F("Network Init")); + bool postMsgTelegram; + if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1; + sendDebugTraceAndFreeMemory(postMsgTelegram); + // Отключаем AP при успешном подключении WiFi.softAPdisconnect(true); break; @@ -70,6 +75,7 @@ void WiFiEvent(arduino_event_t *event) } else { // если попытки подключения исчерпаны, то переходим в AP + sendDebugTraceAndFreeMemory(false); startAPMode(); } break;