*web* 2x, 3x chart, d&d OTA, Info

This commit is contained in:
Mit4el
2025-03-28 20:43:10 +03:00
parent 769d6fc7f1
commit efc4da0bb6
15 changed files with 1030 additions and 20 deletions

View File

@@ -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)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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": "Без виджета"
}
]

View File

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

View File

@@ -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);

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 bool fsOK;
// const char* fsName = "LittleFS";
// Типы обновлений
enum UpdateType {
FIRMWARE,
FILESYSTEM
};
void standWebServerInit() {
// Кэшировать файлы для быстрой работы
@@ -88,10 +94,12 @@ 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);
@@ -176,13 +184,28 @@ void handleCors() {
}
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;
}
int updatePartition = (typeOTAfile == FIRMWARE)? U_FLASH : U_SPIFFS;
if (upload.status == UPLOAD_FILE_START) {
Serial.print("Начало загрузки: ");
Serial.println(upload.filename);
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
//Serial.print("Начало загрузки: ");
//Serial.println(upload.filename);
SerialPrint("i", F("OTA"), "Начало загрузки файла: " + upload.filename);
if (!Update.begin(UPDATE_SIZE_UNKNOWN, updatePartition)) {
Update.end();
SerialPrint("E", F("OTA"), "Ошибка: Недостаточно памяти");
HTTP.send(500, "text/plain", "Ошибка: Недостаточно памяти");
return;
}
@@ -190,17 +213,19 @@ void handleCors() {
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", "Обновление успешно");
Serial.println("Обновление завершено");
ESP.restart();
} else {
Update.end();
SerialPrint("E", F("OTA"), "Ошибка завершения обновления");
HTTP.send(500, "text/plain", "Ошибка завершения обновления");
}
}

View File

@@ -235,7 +235,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length)
if (headerStr == "/localt|") {
String timeStr = String((char*)payload + 8);
Serial.println("Время с фронта: /localt|" + timeStr);
//Serial.println("Время с фронта: /localt|" + timeStr);
// Обрезаем дробную часть, если есть
int dotIndex = timeStr.indexOf('.');
@@ -253,9 +253,11 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length)
// Устанавливаем время
if (settimeofday(&tv, NULL) == 0) {
Serial.printf("Время установлено: %ld\n", unixTime);
//Serial.printf("Время установлено: %ld\n", unixTime);
SerialPrint("i", F("Time"), "Время установлено из браузера: " + String(unixTime));
} else {
Serial.printf("Ошибка установки времени: %ld\n", unixTime);
//Serial.printf("Ошибка установки времени: %ld\n", unixTime);
SerialPrint("i", F("=>WS"), "Ошибка установки времени: " + String(unixTime));
}
// timeval tv2{0, 0};
// timezone tz = timezone{0, 0};

View File

@@ -0,0 +1,415 @@
#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);
interval = 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 json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + ",\"y2\":" + value + "}]}";
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,427 @@
#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);
interval = 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 json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + ",\"y2\":" + value + ",\"y3\":" + value + "}]}";
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

@@ -3,6 +3,7 @@
#if defined(ESP32)
#include <esp_task_wdt.h>
#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;