• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

ESP8266 и sqlite3

Вставьте в ваш цикл проверку оставшейся доступной памяти, у меня перестало вставлять, когда память уменьшилась до 10600 байт.
Serial.println(ESP.getFreeHeap());
А потом, когда файл стал больше 7 кБ, база пишет, что SQL error: disk I/O error, при попытке добавить новую запись.
Сейчас засек все данные.
Чистый диск. При запуске создается база. Размер файла БД 0, свободная память (ESP.getFreeHeap()) 28264.
Как добавилась первая запись размер файла стал 1024, свободная память 27472.
Со второй записью размер файла не изменился, память - 27520. Память свободную так и показывает 27520. На 81 записи память уменьшилась до 27472
Далее до 24 записи размер файла БД не менялся, потом сразу стал 2048 (увеличился на 1024).
Далее каждые 11 записей файл увеличивался на 512, память не менялась.
На 112 записи при размере файла в 6144 стала выдавать SQL error: out of memory.
Но интересно, что когда через несколько интервалов я сделал запрос select * from table
Результат был 122 записи и размер файла вырос до 7168.
А потом через какое-то время (очень быстро) произошел сброс
Код:
Exception (29):
epc1=0x4000df64 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000

>>>stack>>>

ctx: sys
sp: 3fffe410 end: 3fffffb0 offset: 0190
3fffe5a0:  40104857 3fff22e0 00000002 40100368 
3fffe5b0:  4025a227 3fff3aac 3ffedfe0 4025a1bc 
3fffe5c0:  00000002 4025a163 00000002 402592bc
очень длинная серия цифр. И так раза 3-4 подряд. А потом выдало
SQL error: database disk image is malformed
и больше к базе обратиться не получается. И этим почти всегда заканчивается.
 

EvgeniyS

Member
ESP.getFreeHeap() - возвращает общее количество свободной кучи (динамической памяти), но не отображает насколько фрагментирована куча. В вашем коде очень много динамических выделений памяти (класс String), sqlite, вероятно тоже. Подробнее о проблеме тут.
 
Очень интересная тема. Вчера статью всю с гуглем переводчиком прочел. даже удивительно, гугль с переводом справился отлично. Ну сейчас в тестовом скрипте уберу все String. Посмотрим что получится.
В общем я понял, что Heap хоть и кажет большое число, но заканчивается благодаря фрагментации. А я вот не понял, неужели нет какой-нить процедуры дефрагментации? Ну это вроде напрашивается, хотя может это и не реально сделать.
 

pvvx

Активный участник сообщества
Очень интересная тема. Вчера статью всю с гуглем переводчиком прочел. даже удивительно, гугль с переводом справился отлично. Ну сейчас в тестовом скрипте уберу все String. Посмотрим что получится.
В общем я понял, что Heap хоть и кажет большое число, но заканчивается благодаря фрагментации. А я вот не понял, неужели нет какой-нить процедуры дефрагментации? Ну это вроде напрашивается, хотя может это и не реально сделать.
Не реально. Только в CPU с аппаратной виртуализацией адресов (MMU). Т.е. на всех современных SoC, к которым ESP уже давно не относится.
 

pvvx

Активный участник сообщества
Посмотрим что получится.
Ничего хорошего. Через некоторое время работы всё равно с некой вероятностью произойдет дефрагментация несовместимая с работой системы и естественно или перезагрузка, или вылет по ошибке. Тут смотря как и кто написал код либ и всей системы.
Это беда динамической памяти модели heap4 или heap5 от RTOS. По этой причине для надежных устройств без MMU динамической памяти не используют, а используют статические буфера или одноразовое распределение "динамической" памяти, что аналогично статическому и используется в моделях ниже heap4 (по RTOS).
 
Ничего хорошего. Через некоторое время работы всё равно с некой вероятностью произойдет дефрагментация несовместимая с работой системы и естественно или перезагрузка, или вылет по ошибке. Тут смотря как и кто написал код либ и всей системы.
Это беда динамической памяти модели heap4 или heap5 от RTOS. По этой причине для надежных устройств без MMU динамической памяти не используют, а используют статические буфера или одноразовое распределение "динамической" памяти, что аналогично статическому и используется в моделях ниже heap4 (по RTOS).
Ну т.е. я правильно понимаю, что идею с sqlite надо выкинуть из головы. Не будет оно стабильно работать.
Тогда сразу спрошу. Я параллельно начал разбираться с JSON. Так вот если есть файл JSON, в котором несколько параметром и их нужно менять, с этим я разобрался. А вот как скажем добавлять в JSON строки? Ну т.е. строка - это дата, время и 4 показания датчиков.
Вручную я вчера разобрался с форматом JSON чтобы скажем дата это одно поле, оно день не меняется. Второе поле может быть время и тогда менятся оно будет вместе с показаниями датчиков. А можно сделать отдельно часы и отдельно минуты. Ну это уже не суть. Вот скажем такая строка:
[{"cyear":2021, "cmonth":12, "day":[{"cday":31, "hour":[{"chour":23, "min":[{"cmin":55, "sensors":[{"tin":28.5, "tout":-13.6, "vlagn":13.5, "davl":745.6}]}]}]}]}]
Я не разобрался, как добавлять к ней каждую минуту собственно само значение минуты и показания датчиков.
Второй вопрос. Вот скажем я хочу, чтобы эти данные хранились за 1 день. Можно ли и, если да, то как удалить самую старую запись.
Может конечно плохо искал, но не нашел. Нашел пример на PowerShell мужик подобной базой управляет и все.
 

EvgeniyS

Member
Файл JSON это не база данных а всего лишь текстовый файл, поэтому его надо сначала прочитать в буфер, распарсить, сделать необходимые изменения, обратно серилизовать в текст и записать в файл.
[{"cyear":2021, "cmonth":12, "day":[{"cday":31, "hour":[{"chour":23, "min":[{"cmin":55, "sensors":[{"tin":28.5, "tout":-13.6, "vlagn":13.5, "davl":745.6}]}]}]}]}]
Я думаю нет нужды делать такую вложенность в вашем случае, зачем усложнять?
 
Файл JSON это не база данных а всего лишь текстовый файл, поэтому его надо сначала прочитать в буфер, распарсить, сделать необходимые изменения, обратно серилизовать в текст и записать в файл.
Я думаю нет нужды делать такую вложенность в вашем случае, зачем усложнять?
Я думал так меньше места занимать будет.
Ну да ладно. Пусть будет такая запись.
Код:
{"cyear":2021, "cmonth":12, "cday":31, "chour":23, "cmin":55, "tin":28.5, "tout":-13.6, "vlagn":13.5, "davl":745.6}
Так вот как изменить в файле значение tIn скажем на 29(ну не важно сколько), я нашел. А как добавить еще одну строку (и в последствии другие строки) нет. И как удалить потом самую старую строку тоже не нашел.
 

EvgeniyS

Member
Я думал так меньше места занимать будет.
Ну да ладно. Пусть будет такая запись.
Код:
{"cyear":2021, "cmonth":12, "cday":31, "chour":23, "cmin":55, "tin":28.5, "tout":-13.6, "vlagn":13.5, "davl":745.6}
Так вот как изменить в файле значение tIn скажем на 29(ну не важно сколько), я нашел. А как добавить еще одну строку (и в последствии другие строки) нет. И как удалить потом самую старую строку тоже не нашел.
Добавить в файл можно, открыв его с параметром "а" или "а+", подробнее тут. С удалением сложнее, проще, наверное генерировать новые файлы именуя их по дате, например и контролировать их количество, удаляя старые файлы.
 
Добавить в файл можно, открыв его с параметром "а" или "а+", подробнее тут. С удалением сложнее, проще, наверное генерировать новые файлы именуя их по дате, например и контролировать их количество, удаляя старые файлы.
Спасибо!

И все же продолжаю лелеять надежду, что sqlite заработает. НО! Сломал всю голову. Не могу построить SQL запрос. ну где просто строка, там просто. А вот передать в эту строку int или byte ну никак. С утра сиже в инете, кучу вариантов перепробовал, никак не получается.
Вот что имею:
Код:
void sqlite_init() {
  sqlite3_initialize();
  byte imin = 0;
  byte ihour = 12;
  byte isec = 0;
  ts.add(0, 10000, [&](void*) {
    char query[133] = "";
    char zn[3]= ", ";
    char fin[3]= ");";
    char tIn[5] = "27.5";//(String)GettIn();
    char tOut[6] = "-4.3";//(String)GettOut();
    char humidity[5] = "21";//(String)Gethumidity();
    char pressure[6] = "745";//(String)Getpressure();
    char *chour = ihour;
    Serial.println(chour);
    char *cmin = (char*)imin;
    char str1[77] = "INSERT INTO day_p (cyear, cmonth, cday, chour, cmin, tin, tout, vlag, davl) ";
    char str2[22] = "VALUES (2021, 2, 28, ";
    strcat(query, str1);
    strcat(query, str2);
    strcat(query, chour);
    strcat(query, zn);
Serial.println(query);
    /*
    strcat(query, (char*)cmin);
    strcat(query, zn);
    strcat(query, tIn);
    strcat(query, zn);
    strcat(query, tOut);
    strcat(query, zn);
    strcat(query, humidity);
    strcat(query, zn);
    strcat(query, pressure);
    strcat(query, zn);
    strcat(query, fin);
    db_exec(query);
    isec = isec + 10;
    if (isec == 60) {
      isec = 0;
      imin++;
    }
    if (imin == 60) {
      imin = 0;
      ihour++;
    }*/
  }, nullptr, true);
}
Вот соответственно объединение str1 и str2 работает. Ну это я скорее для тренировки объединения char сделал. Потом идет int ihour. И вот его переделать в char, чтобы потом в запрос вставить ну никак не получается. Всегда у меня с этими char трудности были, я поэтому string и использовал. А double переделать я так понял еще сложнее!
 
Можно так к примеру:
C++:
char strHour[3];
uint8_t intHour = 14;
sprintf(strHour, "%u", intHour);
Задаю

int ihour = 12
char chour[3];
sprintf(chour, "%u", ihour);
Serial.println(chour);
Получаю вот такой результат - 1073732304

Что-то в этом роде пробовал я. Только там второй параметр указывали %d.
Примеров то в инете много и все пишут мол ХА ФИГНЯ. Но сколько ни пробовал, ни один не сработал.
 
ХМ. Все поотключал, тоже получилось. Буду искать откуда эта добавка прет. Блин весь день потерял!
 
В общем разобрался. Не там переменную определял.
Ну а в итоге все String убрал, ну теперь вместо 124 заходит 148 записей и снова Out of memory.
Вот код который получился:
Код:
#include <ESP8266WiFi.h>
#include <TickerScheduler.h>
#include <sqlite3.h>
#include <vfs.h>
#include <FS.h>

// Для файловой системы
File fsUploadFile;

//Планировщик задач (Число задач)
TickerScheduler ts(1);

    int ihour = 0;
    int imin = 0;
    int isec = 0;

void setup() {
  Serial.begin(115200);
  FS_init();
  sqlite_init();
}

void loop() {
  ts.update();
}

// Инициализация FFS
void FS_init() {
  char *fileName = "";
  system_update_cpu_freq(SYS_CPU_160MHZ); 
  if (!SPIFFS.begin()) {
    Serial.println("Failed to mount SPIFFS");
    return;
  }
  Dir dir = SPIFFS.openDir("/");
  while (dir.next()) {
    size_t fileSize = dir.fileSize();
    Serial.println(fileSize);
  }
}

void sqlite_init() {
  sqlite3_initialize();
  ts.add(0, 10000, [&](void*) {
    char query[180] = "";
    char zn[3]= ", ";
    char fin[3]= ");";
    char tIn[5] = "27.5";//(String)GettIn();
    char tOut[6] = "-4.3";//(String)GettOut();
    char humidity[5] = "21";//(String)Gethumidity();
    char pressure[6] = "745";//(String)Getpressure();
    char chour[3];
    sprintf(chour, "%u", ihour);
    char cmin[3];
    sprintf(cmin, "%u", imin);
    char str1[77] = "INSERT INTO day_p (cyear, cmonth, cday, chour, cmin, tin, tout, vlag, davl) ";
    char str2[22] = "VALUES (2021, 2, 28, ";
    char str3[47] = "SELECT COUNT(cyear) FROM day_p ORDER BY cyear;";
    strcat(query, str1);
    strcat(query, str2);
    strcat(query, chour);
    strcat(query, zn);
    strcat(query, cmin);
    strcat(query, zn);
    strcat(query, tIn);
    strcat(query, zn);
    strcat(query, tOut);
    strcat(query, zn);
    strcat(query, humidity);
    strcat(query, zn);
    strcat(query, pressure);
    strcat(query, fin);
    strcat(query, str3);
    //Serial.println(query);
    db_exec(query);
    isec = isec + 10;
    if (isec == 60) {
      isec = 0;
      imin++;
    }
    if (imin == 60) {
      imin = 0;
      ihour++;
    }
  }, nullptr, true);
}

void db_exec(const char *sql) {
   sqlite3 *db;
   int rc;
   char *fileName = "";
   const char *dbf = "/FLASH/db.db";
   char *ErrMsg = 0;
   const char* data = 0;
   Serial.println(sql);
   File db_file_obj;
   vfs_set_spiffs_file_obj(&db_file_obj);
   if (sqlite3_open(dbf, &db)) {
       Serial.print(F("Can't open database: "));
       Serial.println(sqlite3_errmsg(db));
       return;
   }
   else Serial.println("DB opened");
   rc = sqlite3_exec(db, sql, callback, (void*)data, &ErrMsg);
   if (rc != SQLITE_OK) {
       Serial.print(F("SQL error: "));
       Serial.println(ErrMsg);
       sqlite3_free(ErrMsg);
   }
   Serial.println(rc);
   sqlite3_close(db); 
   Dir dir = SPIFFS.openDir("/");
   while (dir.next()) {
     size_t fileSize = dir.fileSize();
     Serial.println(fileSize);
   }
   Serial.print("Память ");Serial.println(ESP.getFreeHeap());
}

static int callback(void *data, int argc, char **argv, char **azColName) {
  for (int i = 0; i < argc; i++){
    if (i > 0) {
      Serial.print(" | ");
    }
    Serial.print(argv[i]);
  }
  Serial.println();
  return 0;
}
Так что выходит дело еще в чем то. Уж не знаю, остается только действительно дождаться модуля под SD и проверить в этом ли дело. Ну так эксперимента ради. У нас то почти никто sqlite c esp8266 я так понимаю не юзает. У буржуем разве что.
Завтра буду JSON ковырять.
 

EvgeniyS

Member
Возможно, сама библиотека использует много динамических выделений памяти, я о существовани sqlite давненько знаю, но ни разу ее не использовал т.к. обязательное увеличение стека перед ее применением как-бы намекает, что esp8266 будет работать на пределе своих возможностей. В качестве эксперимента можно попробовать еще немного увеличить стек и протестировать тот же код.
 
Возможно, сама библиотека использует много динамических выделений памяти, я о существовани sqlite давненько знаю, но ни разу ее не использовал т.к. обязательное увеличение стека перед ее применением как-бы намекает, что esp8266 будет работать на пределе своих возможностей. В качестве эксперимента можно попробовать еще немного увеличить стек и протестировать тот же код.
Пробовал. Стоит как рекомендовано 6. Ставлю 7 или 8, сразу после загрузки скетча начинает перегружаться без остановки.
 
Добавить в файл можно, открыв его с параметром "а" или "а+", подробнее тут. С удалением сложнее, проще, наверное генерировать новые файлы именуя их по дате, например и контролировать их количество, удаляя старые файлы.
Вы к тому, что это работа с файлами, а не с содержимым JSON? Попробуем.
Я просто думал, что скажем внутри JSON есть какой-то массив и надо в него добавлять.
 
Вот я о чем. Вот скажем массив:
Код:
[{"cyear":2021, "cmonth":12, "cday":31, "chour":23, "cmin":55, "tin":28.5, "tout":-13.6, "vlagn":13.5, "davl":745.6},
{"cyear":2021, "cmonth":12, "cday":31, "chour":23, "cmin":56, "tin":28.3, "tout":-13.2, "vlagn":13.3, "davl":745.5}]
Ну это первые 2 записи. Потом соответственно случается 57 минута и надо все то же с новыми показателями датчиков записать.
Так я правильно понимаю, что надо взять файл, распарсить его, получить переменную скажем array, добавить в нее новый элемент и записать в файл? Все у меня получается кроме последнего действия.
 
Народ, подкажите, что не так. ЧИтаю файл в который записана строка, которую выше привел. Собственно содержимое естественно считывается, но не парсится.
Код:
#include <ArduinoJson.h>
#include <FS.h>

File fsUploadFile;

StaticJsonBuffer<256> jsonBuffer;

void setup() {
  Serial.begin(115200);
  delay(5);
  Serial.println("");
  if (!SPIFFS.begin()) {
    Serial.println("Failed to mount SPIFFS");
    return;
  }
  String fileData = readFile("2.json");
  char dataJson[fileData.length() + 1];
  strcpy(dataJson, fileData.c_str());
  Serial.println(dataJson);
  JsonObject& root = jsonBuffer.parseObject(dataJson);
  String CYear = root["cyear"][0].as<String>();
  String tIn = root["tin"][0].as<String>();
  Serial.print("Year--");Serial.print(CYear);Serial.print(";tIn--");Serial.println(tIn);
  CYear = root["cyear"][1].as<String>();
  tIn = root["tin"][1].as<String>();
  Serial.print("Year--");Serial.print(CYear);Serial.print(";tIn--");Serial.println(tIn);
}

void loop() {
  // put your main code here, to run repeatedly:

}

String readFile(String fileName) {
  File configFile = SPIFFS.open("/" + fileName, "r");
  if (!configFile) {
    return "Failed";
  }
  String temp = configFile.readString();
  configFile.close();
  return temp;
}
Выдает пустые значения вместо cyear и tin.
 
Сверху Снизу