Merge pull request #40 from Mit4el/dev_newWeb

Dev new web
This commit is contained in:
Mit4el
2025-07-24 22:47:01 +03:00
committed by GitHub
23 changed files with 1396 additions and 85 deletions

View File

@@ -199,6 +199,7 @@ for section, modules in profJson['modules'].items():
configItemsJson['num'] = itemsCount configItemsJson['num'] = itemsCount
configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name']
itemsCount = itemsCount + 1 itemsCount = itemsCount + 1
configItemsJson['moduleName'] = moduleJson['about']['moduleName']
itemsJson.append(configItemsJson) itemsJson.append(configItemsJson)
else: # В первую очередь ищем по имени deviceName, чтобы для данной платы можно было уточнить либы. Если не нашли плату по имени в usedLibs пробуем найти её по типу deviceType else: # В первую очередь ищем по имени deviceName, чтобы для данной платы можно было уточнить либы. Если не нашли плату по имени в usedLibs пробуем найти её по типу deviceType
if deviceType in moduleJson['usedLibs']: # проверяем поддерживает ли модуль текущее устройство if deviceType in moduleJson['usedLibs']: # проверяем поддерживает ли модуль текущее устройство
@@ -208,9 +209,10 @@ for section, modules in profJson['modules'].items():
allLibs = allLibs + "\n" + libPath allLibs = allLibs + "\n" + libPath
for configItemsJson in moduleJson['configItem']: for configItemsJson in moduleJson['configItem']:
configItemsJson['num'] = itemsCount configItemsJson['num'] = itemsCount
configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name']
itemsCount = itemsCount + 1 itemsCount = itemsCount + 1
itemsJson.append(configItemsJson) itemsJson.append(configItemsJson)
configItemsJson['moduleName'] = moduleJson['about']['moduleName']
with open("data_svelte/items.json", "w", encoding='utf-8') as write_file: 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) json.dump(itemsJson, write_file, ensure_ascii=False, indent=4, sort_keys=False)

Binary file not shown.

Binary file not shown.

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>IoT Manager 4.5.5</title> <title>IoT Manager 4.6.0</title>
<link rel="icon" type="image/png" href="/favicon.ico" /> <link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="/build/bundle.css?4550" /> <link rel="stylesheet" href="/build/bundle.css?4550" />

Binary file not shown.

Binary file not shown.

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>IoT Manager 4.5.5</title> <title>IoT Manager 4.6.0</title>
<link rel="icon" type="image/png" href="/favicon.ico" /> <link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="/build/bundle.css?4550" /> <link rel="stylesheet" href="/build/bundle.css?4550" />

View File

@@ -177,6 +177,39 @@
"maxCount": 86400, "maxCount": 86400,
"type": "bar" "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", "name": "fillgauge",
"label": "Бочка", "label": "Бочка",
@@ -321,10 +354,6 @@
"widget": "anydata", "widget": "anydata",
"after": "°", "after": "°",
"icon": "speedometer" "icon": "speedometer"
},
{
"name": "nil",
"label": "Без виджета"
}, },
{ {
"name": "anydataBar", "name": "anydataBar",
@@ -332,5 +361,9 @@
"widget": "anydata", "widget": "anydata",
"after": "Kg/cm²", "after": "Kg/cm²",
"icon": "speedometer" "icon": "speedometer"
},
{
"name": "nil",
"label": "Без виджета"
} }
] ]

View File

@@ -2,7 +2,8 @@
#include "BuildTime.h" #include "BuildTime.h"
// Версия прошивки // Версия прошивки
#define FIRMWARE_VERSION 460
#define FIRMWARE_VERSION 462
#ifdef esp8266_1mb_ota #ifdef esp8266_1mb_ota
#define FIRMWARE_NAME "esp8266_1mb_ota" #define FIRMWARE_NAME "esp8266_1mb_ota"

View File

@@ -10,6 +10,7 @@
#include <list> #include <list>
#ifdef LIBRETINY #ifdef LIBRETINY
#include <Update.h>
#include <vector> #include <vector>
#include <typedef.h> #include <typedef.h>
#ifdef STANDARD_WEB_SERVER #ifdef STANDARD_WEB_SERVER

View File

@@ -10,6 +10,8 @@ extern void handleFileUpload();
extern void handleFileDelete(); extern void handleFileDelete();
extern void handleFileCreate(); extern void handleFileCreate();
extern void handleLocalOTA(); extern void handleLocalOTA();
extern void handleUpdateOTA();
extern void handleCors();
extern void handleLocalOTA_Handler(); extern void handleLocalOTA_Handler();
extern void handleFileList(); extern void handleFileList();
//void printDirectory(File dir, String& out); //void printDirectory(File dir, String& out);

View File

@@ -170,6 +170,14 @@
"path": "src/modules/virtual/Loging", "path": "src/modules/virtual/Loging",
"active": true "active": true
}, },
{
"path": "src/modules/virtual/Loging2",
"active": false
},
{
"path": "src/modules/virtual/Loging3",
"active": false
},
{ {
"path": "src/modules/virtual/LogingDaily", "path": "src/modules/virtual/LogingDaily",
"active": true "active": true
@@ -428,7 +436,7 @@
}, },
{ {
"path": "src/modules/exec/Buzzer", "path": "src/modules/exec/Buzzer",
"active": true "active": false
}, },
{ {
"path": "src/modules/exec/EctoControlAdapter", "path": "src/modules/exec/EctoControlAdapter",
@@ -506,6 +514,10 @@
"path": "src/modules/exec/SysExt", "path": "src/modules/exec/SysExt",
"active": false "active": false
}, },
{
"path": "src/modules/exec/Tca9555",
"active": false
},
{ {
"path": "src/modules/exec/Telegram", "path": "src/modules/exec/Telegram",
"active": false "active": false

View File

@@ -224,9 +224,9 @@ void setup() {
stopErrorMarker(SETUPINET_ERRORMARKER); stopErrorMarker(SETUPINET_ERRORMARKER);
bool postMsgTelegram; // bool postMsgTelegram;
if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1; // if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1;
sendDebugTraceAndFreeMemory(postMsgTelegram); // sendDebugTraceAndFreeMemory(postMsgTelegram);
initErrorMarker(SETUPLAST_ERRORMARKER); initErrorMarker(SETUPLAST_ERRORMARKER);
@@ -276,9 +276,9 @@ void setup() {
}, },
nullptr, true); nullptr, true);
// ловим пинги от WS (5сек) и дисконнектим если их нет (20сек) // ловим пинги от WS (2сек) и дисконнектим если их нет 3 раза 3сек*2прохода = 6сек
ts.add( ts.add(
PiWS, 6000, [&](void*) { PiWS, 3000, [&](void*) {
if (isNetworkActive()) { if (isNetworkActive()) {
for (size_t i = 0; i < WEBSOCKETS_CLIENT_MAX; i++) for (size_t i = 0; i < WEBSOCKETS_CLIENT_MAX; i++)
{ {

View File

@@ -9,6 +9,12 @@ static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR";
static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound"; static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound";
// static bool fsOK; // static bool fsOK;
// const char* fsName = "LittleFS"; // const char* fsName = "LittleFS";
// Типы обновлений
enum UpdateType {
FIRMWARE,
FILESYSTEM
};
void standWebServerInit() { void standWebServerInit() {
// Кэшировать файлы для быстрой работы // Кэшировать файлы для быстрой работы
@@ -88,11 +94,18 @@ void standWebServerInit() {
// - first callback is called after the request has ended with all parsed arguments // - first callback is called after the request has ended with all parsed arguments
// - second callback handles file upload at that location // - second callback handles file upload at that location
HTTP.on("/edit", HTTP_POST, replyOK, handleFileUpload); HTTP.on("/edit", HTTP_POST, replyOK, handleFileUpload);
// отображение страницы с полем ввода для сервера обновления
HTTP.on("/localota", HTTP_GET, handleLocalOTA); HTTP.on("/localota", HTTP_GET, handleLocalOTA);
// непосредственно ОТА обновление со стороннего сервера
HTTP.on("/localota_handler", HTTP_GET, handleLocalOTA_Handler); 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 // Default handler for all URIs not defined above
// Use it to read files from filesystem // Use it to read files from filesystem
HTTP.onNotFound(handleNotFound); HTTP.onNotFound(handleNotFound);
@@ -164,6 +177,66 @@ void handleLocalOTA() {
String page = "<form action='/localota' method='POST'><label for='server'>Server Address:</label><input type='text' name='server' value='http://192.168.1.2:5500'><input type='submit' value='Update'></form>"; String page = "<form action='/localota' method='POST'><label for='server'>Server Address:</label><input type='text' name='server' value='http://192.168.1.2:5500'><input type='submit' value='Update'></form>";
HTTP.send(200, "text/html", 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() { void handleLocalOTA_Handler() {
String serverValue = HTTP.arg("server"); String serverValue = HTTP.arg("server");
upgrade_firmware(3,serverValue); upgrade_firmware(3,serverValue);

View File

@@ -59,9 +59,9 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length)
//----------------------------------------------------------------------// //----------------------------------------------------------------------//
// Страница веб интерфейса dashboard // Страница веб интерфейса dashboard
//----------------------------------------------------------------------// //----------------------------------------------------------------------//
if (headerStr == "p|") { if (headerStr == "/pi|") {
standWebSocket.sendTXT(num, "p|"); standWebSocket.sendTXT(num, "/po|");
//Serial.printf("Ping client: %u\n", num); Serial.printf("Ping client: %u\n", num);
ws_clients[num]=1; 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); 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 // Страница веб интерфейса dev
//----------------------------------------------------------------------// //----------------------------------------------------------------------//

View File

@@ -8,7 +8,6 @@
// #include "Stream.h" // #include "Stream.h"
#include <vector> #include <vector>
// class ModbusUart; // class ModbusUart;
Stream *_modbusUART = nullptr; Stream *_modbusUART = nullptr;
@@ -21,7 +20,6 @@ uint8_t _DIR_PIN = 0;
#define MODBUS_TX_PIN 19 // Tx pin #define MODBUS_TX_PIN 19 // Tx pin
#define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication #define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication
void modbusPreTransmission() void modbusPreTransmission()
{ {
// delay(500); // delay(500);
@@ -52,7 +50,7 @@ private:
int protocol = SERIAL_8N1; int protocol = SERIAL_8N1;
uint8_t _addr = 0xF0; // Адрес слейва от 1 до 247 uint8_t _addr = 0xF0; // Адрес слейва от 1 до 247
uint8_t _type = 0x14; // Тип устройства: 0x14 адаптер OpenTherm (вторая версия); 0x11 адаптер OpenTherm (первая версия, снята с производства) uint8_t _type = 0x14; // Тип устройства: 0x14 адаптер OpenTherm (вторая версия); 0x11 адаптер OpenTherm (первая версия, снята с производства)
bool _debugLevel; // Дебаг uint8_t _debugLevel; // Дебаг
ModbusMaster node; ModbusMaster node;
uint8_t _debug; uint8_t _debug;
@@ -136,36 +134,39 @@ public:
{ {
SerialPrint("E", "EctoControlAdapter", "Не подходящее устройство, type: " + String(type, HEX)); SerialPrint("E", "EctoControlAdapter", "Не подходящее устройство, type: " + String(type, HEX));
} }
getModelVersion();
getBoilerInfo();
getBoilerStatus();
} }
else if (_addr == 0) else if (_addr == 0)
{ // если адреса нет, то шлем широковещательный запрос адреса { // если адреса нет, то шлем широковещательный запрос адреса
uint8_t addr = node.readAddresEctoControl(); uint8_t addr = node.readAddresEctoControl();
SerialPrint("I", "EctoControlAdapter", "readAddresEctoControl, addr: " + String(addr, HEX) + " - Enter to configuration"); SerialPrint("I", "EctoControlAdapter", "readAddresEctoControl, addr: " + String(addr, HEX) + " - Enter to configuration");
} }
getModelVersion();
getBoilerInfo();
getBoilerStatus();
} }
void doByInterval() void doByInterval()
{ {
// readBoilerInfo(); if (_addr > 0)
getBoilerStatus(); {
// readBoilerInfo();
getBoilerStatus();
getCodeError(); getCodeError();
getCodeErrorExt(); getCodeErrorExt();
if (info.adapterType == 0) if (info.adapterType == 0)
getFlagErrorOT(); getFlagErrorOT();
// getFlowRate(); // getFlowRate();
// getMaxSetCH(); // getMaxSetCH();
// getMaxSetDHW(); // getMaxSetDHW();
// getMinSetCH(); // getMinSetCH();
// getMinSetDHW(); // getMinSetDHW();
getModLevel(); getModLevel();
getPressure(); getPressure();
getTempCH(); getTempCH();
getTempDHW(); getTempDHW();
getTempOutside(); getTempOutside();
}
} }
void loop() void loop()
@@ -176,10 +177,6 @@ public:
IoTValue execute(String command, std::vector<IoTValue> &param) IoTValue execute(String command, std::vector<IoTValue> &param)
{ {
if (command == "getModelVersion")
{
getModelVersion();
}
if (command == "getModelVersion") if (command == "getModelVersion")
{ {
getModelVersion(); getModelVersion();
@@ -324,13 +321,12 @@ public:
tlgrmItem->sendTelegramMsg(false, msg); tlgrmItem->sendTelegramMsg(false, msg);
} }
~EctoControlAdapter() { ~EctoControlAdapter(){};
};
bool writeFunctionModBus(const uint16_t &reg, uint16_t &data) bool writeFunctionModBus(const uint16_t &reg, uint16_t &data)
{ {
// set word 0 of TX buffer to least-significant word of counter (bits 15..0) // 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) // set word 1 of TX buffer to most-significant word of counter (bits 31..16)
node.setTransmitBuffer(0, data); node.setTransmitBuffer(0, data);
// slave: write TX buffer to (2) 16-bit registers starting at register 0 // slave: write TX buffer to (2) 16-bit registers starting at register 0
@@ -348,6 +344,7 @@ public:
bool readFunctionModBus(const uint16_t &reg, uint16_t &reading) bool readFunctionModBus(const uint16_t &reg, uint16_t &reading)
{ {
if (_addr == 0) return false;
// float retValue = 0; // float retValue = 0;
if (_modbusUART) if (_modbusUART)
{ {

View File

@@ -1,6 +1,9 @@
#include "ModbusEC.h" #include "ModbusEC.h"
#define COUNT_BIT_AVAIL 5
#define COUNT_BIT_AVAIL_46F 4
ModbusMaster::ModbusMaster(void) ModbusMaster::ModbusMaster(void)
{ {
_idle = 0; _idle = 0;
@@ -256,8 +259,8 @@ uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress,
/** /**
Modbus function 0x06 Write Single Register. Modbus function 0x06 Write Single Register.
This function code is used to write a single holding register in a 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 remote device. The request specifies the address of the register to be
written. Registers are addressed starting at zero. written. Registers are addressed starting at zero.
@param u16WriteAddress address of the holding register (0x0000..0xFFFF) @param u16WriteAddress address of the holding register (0x0000..0xFFFF)
@@ -266,7 +269,7 @@ written. Registers are addressed starting at zero.
@ingroup register @ingroup register
*/ */
uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
uint16_t u16WriteValue) uint16_t u16WriteValue)
{ {
_u16WriteAddress = u16WriteAddress; _u16WriteAddress = u16WriteAddress;
_u16WriteQty = 0; _u16WriteQty = 0;
@@ -274,7 +277,6 @@ uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
return ModbusMasterTransaction(ku8MBWriteSingleRegister); return ModbusMasterTransaction(ku8MBWriteSingleRegister);
} }
/** /**
Modbus function 0x10 Write Multiple Registers. Modbus function 0x10 Write Multiple Registers.
@@ -308,7 +310,8 @@ uint8_t ModbusMaster::readAddresEctoControl()
{ {
_u16ReadAddress = 0x00; _u16ReadAddress = 0x00;
_u16ReadQty = 1; _u16ReadQty = 1;
return ModbusMasterTransaction(ku8MBProgRead46); ModbusMasterTransaction(ku8MBProgRead46);
return getResponseBuffer(0x00);
} }
uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr) uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr)
{ {
@@ -393,8 +396,17 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
{ {
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); 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++] = lowByte(u16CRC);
u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC); u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC);
// }
u8ModbusADU[u8ModbusADUSize] = 0; u8ModbusADU[u8ModbusADUSize] = 0;
// flush receive buffer before transmitting request // flush receive buffer before transmitting request
@@ -427,7 +439,9 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
#if __MODBUSMASTER_DEBUG__ #if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, true); digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, true);
#endif #endif
u8ModbusADU[u8ModbusADUSize++] = _serial->read(); uint8_t req = _serial->read();
u8ModbusADU[u8ModbusADUSize++] = req;
Serial.print(req, HEX);
u8BytesLeft--; u8BytesLeft--;
#if __MODBUSMASTER_DEBUG__ #if __MODBUSMASTER_DEBUG__
digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false); 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 // 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 (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47)
if (u8ModbusADU[0] != _u8MBSlave)
{ {
// Serial.print(u8ModbusADU[0], HEX); // verify response is for correct Modbus slave
// Serial.print(" != "); if (u8ModbusADU[0] != _u8MBSlave)
// Serial.println(_u8MBSlave, HEX); {
// Serial.print(u8ModbusADU[0], HEX);
u8MBStatus = ku8MBInvalidSlaveID; // Serial.print(" != ");
break; // Serial.println(_u8MBSlave, HEX);
}
// verify response is for correct Modbus function code (mask exception bit 7) u8MBStatus = ku8MBInvalidSlaveID;
if ((u8ModbusADU[1] & 0x7F) != u8MBFunction) break;
{ }
u8MBStatus = ku8MBInvalidFunction;
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 // check whether Modbus exception occurred; return Modbus Exception Code
if (bitRead(u8ModbusADU[1], 7)) if (bitRead(u8ModbusADU[1], 7))
{ {
@@ -485,6 +507,16 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
case ku8MBWriteMultipleRegisters: case ku8MBWriteMultipleRegisters:
u8BytesLeft = 3; u8BytesLeft = 3;
break; break;
case ku8MBProgRead46:
u8BytesLeft = 1;
break;
case ku8MBProgWrite47:
u8BytesLeft = 1;
break;
default:
} }
} }
if ((millis() - u32StartTime) > ku16MBResponseTimeout) if ((millis() - u32StartTime) > ku16MBResponseTimeout)
@@ -493,24 +525,26 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
} }
} }
// verify response is large enough to inspect further if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47)
if (!u8MBStatus && u8ModbusADUSize >= 5)
{ {
// calculate CRC // verify response is large enough to inspect further
u16CRC = 0xFFFF; if (!u8MBStatus && u8ModbusADUSize >= COUNT_BIT_AVAIL)
for (i = 0; i < (u8ModbusADUSize - 2); i++)
{ {
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 // verify CRC
if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] || if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] ||
highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])) highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1]))
{ {
u8MBStatus = ku8MBInvalidCRC; u8MBStatus = ku8MBInvalidCRC;
}
} }
} }
// disassemble ADU into words // disassemble ADU into words
if (!u8MBStatus) if (!u8MBStatus)
{ {
@@ -530,6 +564,12 @@ uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
} }
break; break;
case ku8MBProgRead46: case ku8MBProgRead46:
Serial.print("ku8MBProgRead46");
for (i = 0; i < (u8ModbusADUSize); i++)
{
Serial.println(u8ModbusADU[i], HEX);
}
_u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2]; _u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2];
_u8ResponseBufferLength = 1; _u8ResponseBufferLength = 1;
break; break;

View File

@@ -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<IoTItem *>::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);
}

View File

@@ -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*": []
}
}

View File

@@ -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<IoTItem *>::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);
}

View File

@@ -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*": []
}
}

View File

@@ -160,7 +160,11 @@ public:
SerialPrint("E", F("LogingHourly"), "'" + id + "' db file writing error, return"); SerialPrint("E", F("LogingHourly"), "'" + id + "' db file writing error, return");
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); SerialPrint("i", F("LogingHourly"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path);
#endif
} }
void addNewDataToExistingFile(String &path, String &logData) void addNewDataToExistingFile(String &path, String &logData)
@@ -171,7 +175,11 @@ public:
SerialPrint("i", F("LogingHourly"), "'" + id + "' file writing error, return"); SerialPrint("i", F("LogingHourly"), "'" + id + "' file writing error, return");
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); SerialPrint("i", F("LogingHourly"), "'" + id + "' LogingHourly in file http://" + WiFi.localIP().toString() + path);
#endif
} }
const String getTimeLocal_hh() const String getTimeLocal_hh()
{ {

View File

@@ -3,6 +3,7 @@
#if defined(ESP32) #if defined(ESP32)
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
#endif #endif
#include "DebugTrace.h"
#define TRIESONE 20 // количество секунд ожидания подключения к одной сети из несколких #define TRIESONE 20 // количество секунд ожидания подключения к одной сети из несколких
#define TRIES 30 // количество секунд ожидания подключения сети если она одна #define TRIES 30 // количество секунд ожидания подключения сети если она одна
@@ -50,6 +51,10 @@ void WiFiEvent(arduino_event_t *event)
mqttInit(); mqttInit();
SerialPrint("i", F("WIFI"), F("Network Init")); SerialPrint("i", F("WIFI"), F("Network Init"));
bool postMsgTelegram;
if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1;
sendDebugTraceAndFreeMemory(postMsgTelegram);
// Отключаем AP при успешном подключении // Отключаем AP при успешном подключении
WiFi.softAPdisconnect(true); WiFi.softAPdisconnect(true);
break; break;
@@ -70,6 +75,7 @@ void WiFiEvent(arduino_event_t *event)
} }
else else
{ // если попытки подключения исчерпаны, то переходим в AP { // если попытки подключения исчерпаны, то переходим в AP
sendDebugTraceAndFreeMemory(false);
startAPMode(); startAPMode();
} }
break; break;