Перейти к содержимому

rLog :: универсальный логгер для ESPxx и Arduino

Представляю Вашему вниманию еще одну маленькую библиотечку. Для чего она нужна?

Пожалуй самый популярный способ “отладки” программ для Arduino – добавление в код скетча текстовых отладочных сообщений, а затем их отслеживание через подключение по COM-порту. Что-то вроде этого:

HTTPClient https;
if (https.begin(wifiClient, httpRequest)) { 
  int httpCode = https.GET();
  if (httpCode > 0) {
   if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
     result = true;
     Serial.printf("TLG :: Send \"%s\": OK (%d)\n", message.c_str(), httpCode);
   }
   else {
    Serial.printf("TLG :: Send \"%s\": FAILED (%d) %s\n", message.c_str(), httpCode, https.errorToString(httpCode).c_str());
   };
  } 
  else {
    Serial.println(F("TLG :: Send FAILED: Unable to connect!"));
  };
  https.end();
} 
else {
 Serial.println(F("TLG :: Send FAILED: Unable to create HTTPS client!"));
};

Приведенный выше код содержит множество отладочных Serial.println(…), позволяющих через COM-порт определить, на каком этапе что пошло не так. Все это просто и хорошо, но ровно до того момента, когда устройство оказывается полностью отлажено и собрано, то есть готово “в продакшен”. В “боевой” обстановке устройство обычно работает вдали от компьютера и вывод в serial только мешает – занимает и процессорные ресурсы, и память. Хорошо бы иметь возможность отключать вывод отладочных сообщений, а при возможных доработках проекта – включать.

Именно такой принцип заложен в библиотеку, которая “встроена” в Framework Arduino для ESP32: “esp32-hal-log.h” (в фреймворке ESP-IDF есть более “навороченная” версия – “esp_log.h”). Причем отключать “отладку” можно “частями” – чем ниже заданный уровень – тем меньше сообщений Вы увидите. Например: ничего, только ошибки, ошибки + предупреждения, ошибки + предупреждения + сообщения и т.д. Большая часть построена на макросах, а это означает, что сообщения выше заданного уровня будут полностью исключены из кода. Очень удобно. И достаточно просто.

 


Так зачем же еще изобретать велосипед? Есть несколько “но”:

  • Во-первых, включив уровень “DEBUG” в настройках проекта, Вы увидите не только “свои” сообщения, но и кучу отладочных сообщений из всех подключенных библиотек фреймворка. Хотелось бы как-то разделить все-таки. То есть подавить “системные” сообщения, оставив только нужные.
  • Во-вторых, эти библиотеки не будут работать на ESP8266, и уж тем более на AVR. А из-за этого код какой-нибудь “своей” библиотеки перестает работать на другом контроллере только из-за отладки. Конечно, можно нагородить леса типа с помощью макросов #if … #endif, но это сильно загромождает код и усложняет написание программы. Хотелось бы какого-то однообразия.
  • Ну и наконец – Вы не сможете изменить настройки sdkconfig.h, если используется только “framework-arduinoespressif32”, он поставляется уже сконфигурированным. То есть Вы не сможете самостоятельно изменить уровень отладки.

Исходя из этого мне ничего не оставалось, кроме как самым наглейшим образом передрать код “esp32-hal-log.h”, портировать его на ESP8266 и AVR, и сделать отдельные настройки для всего этого. Вот так и получилась эта библиотека. Использовать ее можно на ESP8266, ESP32, AVR и других контроллерах, в Arduino IDE и PlatformIO, в том числе в фреймворке ESP-IDF.

Библиотека бесплатная, Open source, скачать ее можно с GitHub с помощью кнопки ниже.

Как использовать

Есть несколько уровней отладочных сообщений:

  • RLOG_LEVEL_NONE – Сообщения не выводятся
  • RLOG_LEVEL_ERROR – Критические ошибки, программный модуль не может восстановиться самостоятельно
  • RLOG_LEVEL_WARN – Состояния ошибки, из которых были приняты меры по устранению
  • RLOG_LEVEL_INFO – Информационные сообщения, описывающие нормальный ход событий
  • RLOG_LEVEL_DEBUG – Дополнительная информация, которая не требуется для нормального использования (значения, указатели, размеры и т.д.).
  • RLOG_LEVEL_VERBOSE – Большие фрагменты отладочной информации или частые сообщения, которые потенциально могут переполнить вывод.

Им соответствуют несколько функций для вывода этих сообщений:

  • rlog_e(tag, format, …) – Сообщения уровня RLOG_LEVEL_ERROR
  • rlog_w(tag, format, …) – Сообщения уровня RLOG_LEVEL_WARN
  • rlog_i(tag, format, …) – Сообщения уровня RLOG_LEVEL_INFO
  • rlog_d(tag, format, …) – Сообщения уровня RLOG_LEVEL_DEBUG
  • rlog_v(tag, format, …) – Сообщения уровня RLOG_LEVEL_VERBOSE

где: tag – идентификатор, с помощью которого удобно обозначать модуль, откуда пришло сообщение (например “mqtt” или “telegram”) format – собственно сообщение с символами форматирования (или без них) – переменные, передаваемые в строку форматирования (например “Value = %d\n”, 10). Их может и не быть вовсе.

Соответственно, чем выше уровень отладки в Вашем проекте, тем больше сообщений Вы увидите.
Уровень отладки задается с помощью определения #define CONFIG_RLOG_PROJECT_LEVEL RLOG_LEVEL_XXXX, где RLOG_LEVEL_XXXX – этот самый уровень и есть. Кроме этого, есть возможность включить вывод системного времени и цветных ASCII-кодов в сообщения. Эти опции также включаются с помощью макросов.
Например при CONFIG_RLOG_PROJECT_LEVEL == RLOG_LEVEL_INFO в код попадут только макросы-функции rlog_e, rlog_w, rlog_i, а остальные будут игнорированы.
Для CONFIG_RLOG_PROJECT_LEVEL == RLOG_LEVEL_DEBUG будут отображаться ещё и rlog_d.
А при CONFIG_RLOG_PROJECT_LEVEL == RLOG_LEVEL_NONE все отладочные сообщения будет исключены.

Настройка уровня вывода

Осталось придумать, как определить этот самый CONFIG_RLOG_PROJECT_LEVEL

Если весь Ваш код содержится в одном файле – то с этим не будет никаких проблем. Просто добавьте воды #define CONFIG_RLOG_PROJECT_LEVEL RLOG_LEVEL_INFO в начале файла и проблема будет решена.

Сложнее, если Вы будете дробить код по разным файлам. В этом случае целесообразно вынести определения макросов в отдельный файл, например “project_config.h”. Если все файлы проекта находятся в одной папке, то проблем не возникнет.

Проблема будет, если Вы будете подключать какие-то библиотеки, которые находятся в каталогах, отличных от каталога проекта (общие библиотеки, которые используется в нескольких проектах одновременно). В этом случае компилятор при сборке не найдет Ваш “project_config.h”. Нужно ему подсказать, где искать. В PlatformIO это можно сделать с помощью опции -Iкаталог, например так:

[env]
build_flags = -Isrc 

 

Вуаля. При компиляции библиотек компилятор обязательно заглянет в каталог src, найдет Ваш файл “project_config.h” и подключит его к коду.

А вот в Arduino IDE я не нашел другого выхода, кроме как свалить все в одну кучу и хорошенько перемешать, то есть скопировать все библиотеки в каталог проекта. Впрочем, я не сильно и старался.

 


Примеры

Примеры для различных платформ и фреймворков Вы можете найти в архиве с исходниками (см. ссылку в конце статьи) в каталоге examples.

#include "Arduino.h"
#include "project_config.h"
#include "rLog.h"

void setup() {
  // put your setup code here, to run once:
  Serial.begin(19200);
  Serial.println();

  rlog_v("DEMO", "Test message, level: %s", "VERBOSE");
  rlog_d("DEMO", "Test message, level: %s", "DEBUG");
  rlog_i("DEMO", "Test message, level: %s", "INFORMATION");
  rlog_w("DEMO", "Test message, level: %s", "WARNING");
  rlog_e("DEMO", "Test message, level: %s", "ERROR");
}

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

 

Результаты компиляции для различных платформ:

Arduino Nano + Framework Arduino

Результаты компиляции для Arduini Nano + Framework Arduino

Тестовый вывод для Arduini Nano + Framework Arduino

ESP8266 + Framework Arduino

Результаты компиляции для ESP8266 + Framework Arduino

Тестовый вывод для ESP8266 + Framework Arduino

ESP32 + Framework Arduino

Результаты компиляции для ESP32 + Framework Arduino

Тестовый вывод для ESP32 + Framework Arduino

ESP32 + Framework ESP-IDF

Результаты компиляции для ESP32 + Framework ESP-IDF

Тестовый вывод для ESP32 + Framework ESP-IDF


 


Скачать библиотеку:

Библиотека бесплатная, Open source, скачать ее можно с GitHub с помощью кнопки ниже. В архиве Вы также найдете примеры скетча для Arduino IDE и PlatformIO – загляните в папку examples.

Значок
rLog :: библиотека и демо-проекты для Arduino & PlatformIO

Открыть на GitHub


✳️ Другие мои библиотеки для PlatformIO и ArduinoIDE

💠 Полный архив статей вы найдете здесь


Пожалуйста, оцените статью:
[ 5 из 5, всего 1 оценок ]

16 комментариев для “rLog :: универсальный логгер для ESPxx и Arduino”

  1. Валентин

    Отличная библиотека, взял на вооружение.
    Но как напечатать ip адрес, маску, mac – адрес?

    1. Добрый день. Зависит от того, откуда вы берете данные.
      Например можно так:

      esp_netif_ip_info_t local_ip = wifiLocalIP();
      if (local_ip.ip.addr != 0) {
      uint8_t * loc_ip = (uint8_t*)&(local_ip.ip);
      rlog_d(logTAG, “%d.%d.%d.%d”, loc_ip[0], loc_ip[1], loc_ip[2], loc_ip[3]);
      }

  2. Столкнулся со странной проблемой. Никак не хочет библиотека показывать число типа float.
    Переменная “a” несколько раз преобразуется по ходу вычислений. Вывожу лог:
    rlog_d(“func()”, “1 a = %f”, a);
    Serial.print(“1 a = “);Serial.println(a);

    00:00:00 [D] func() :: 1 a = ?
    1 a = 9108906.00
    00:00:00 [D] func() :: 2 a = ?
    2 a = -9.00
    00:00:00 [D] func() :: 3 a = ?
    3 a = -0.09
    Arduino IDE 1.8.19, плата Arduino Nano (Atmega 328p)

    1. Хм, много раз выводил float и double – никаких проблем не наблюдалось. Может у вас все таки не float, а что-то иное?

    1. Serial.print использует принципиально другой метод преобразования числа в строку. Они НЕ МОГУТ быть одинаковыми.

      Данная библиотека использует универсальную форматную vsnprintf, которая, кстати, входит в стандарт Си – https://learn.microsoft.com/ru-ru/cpp/c-runtime-library/vprintf-functions?view=msvc-160
      Я подозреваю, что для вашего МК она может быть реализована криво – вопросы не ко мне, а к производителю фреймворка для вашего МК. На ESP32 она работает отлично.

      Я так же подозреваю, что вы использовали какой-то недопустимый тип данных под переменную. Serial.print пофиг на типы данных, она проглотит все. А вот vsnprintf – далеко не без разницы.

      Я сотни раз использовал вывод чисел с плавающей запятой – и все всегда работало. Впрочем, у меня сейчас даже нет указанного вами контроллера, чтобы проверить ваши слова – я все раздал друзьям или использовал в проектах. Остались только ESP. Да и нет там разницы особой – можете сами заглянуть в исходники:

      // And finally we can print a message
      #if __RLOG_SERIAL__
      Serial.print(temp);
      #else
      printf(“%s”, temp);
      #endif

  3. Я не понимаю, что такое “недопустимый тип данных”. Ну вот кусок кода, который с проблемой.

    uint32_t sumX = 0;
    uint32_t sumY = 0;
    uint32_t sumX2 = 0;
    uint32_t sumXY = 0;

    for (int i = 0; i < 6; i++) { // для всех элементов массива
    sumX += _time_array[i];//=15 сумма значений оси X
    sumY += _pressure_array[i];//сумма значений оси Y
    sumX2 += _time_array[i] * _time_array[i];//квадрат суммы значений оси X
    sumXY += _time_array[i] * _pressure_array[i];
    }

    rlog_v("func()", "sumX = %d", sumX);
    rlog_v("func()", "sumY = %lu", sumY);
    rlog_v("func()", "sumX2 = %d", sumX2);
    rlog_v("func()", "sumXY = %lu", sumXY);

    float a = 0;
    a = 6.0 * (float)sumXY; // расчёт коэффициента наклона прямой
    rlog_d("func()", "1 a = %f", a);
    Serial.print("1 a = ");Serial.println(a);

    a = a – (float)sumX * (float)sumY;
    rlog_d("func()", "2 a = %f", a);
    Serial.print("2 a = ");Serial.println(a);

    a = a / (6.0 * (float)sumX2 – (float)sumX * (float)sumX);
    rlog_d("func()", "3 a = %f", a);
    Serial.print("3 a = ");Serial.println(a);

    int16_t delta = (int)floor(a * 6); // расчёт изменения давления
    rlog_d("func()", "delta = %d", delta);

    Вывод лога взял чуть заранее, чтоб было понято что за числа.

    00:00:00 [D] func() :: _pressure_array[0] = 102075
    00:00:00 [D] func() :: _pressure_array[1] = 102075
    00:00:00 [D] func() :: _pressure_array[2] = 102075
    00:00:00 [D] func() :: _pressure_array[3] = 102075
    00:00:00 [D] func() :: _pressure_array[4] = 102075
    00:00:00 [D] func() :: _pressure_array[5] = 102072
    00:00:00 [V] func() :: sumX = 15
    00:00:00 [V] func() :: sumY = 612447
    00:00:00 [V] func() :: sumX2 = 55
    00:00:00 [V] func() :: sumXY = 1531110
    00:00:00 [D] func() :: 1 a = ?
    1 a = 9186660.00
    00:00:00 [D] func() :: 2 a = ?
    2 a = -45.00
    00:00:00 [D] func() :: 3 a = ?
    3 a = -0.43
    00:00:00 [D] func() :: delta = -3

    Что я делаю не так?

  4. Аркадий

    Логгирование – вещь очень полезная, а встроенный лог, на мой взгляд излишне детализирован.
    Я так понимаю, вначале нужно выбрать или отключить встроенные сообщения, чтобы не отвлекали.
    И второй момент: Чем обусловлено динамическое выделение буфера сообщения? На мой взгляд, это медленнее и может приводить к фрагментации кучи. Если выделить статический буфер с некоторым запасом? И обращение к буферу всё равно происходит за мьютексом. Может, я что-то не увидел?

    1. Да, вы все правильно поняли – “свой” вариант как раз и нужен для более простого подавления системных сообщений. Конечно, этого можно добиться и другими способами, но когда-то мне казалось что так проще.

      Динамический буфер – это не моя придумка. Примерно так же реализован механизм в оригинальном ESP_LOG. Это я у них и подсмотрел.

      Что касается динамической памяти. Таки да, я почти не применяю статические буферы. Почти нигде. И это практически не влияет на фрагментацию памяти – и мои устройства стабильно работают годами без сбоев, если я их сам не перегружаю, конечно. Нужно только постоянно держать в голове простую вещь – долгоживущие данные нужно выделять либо статически, либо как можно раньше. А такой короткоживущий блок как строка лога – это вообще “ни о чем”, он выделился и спустя микросекунды удалился. Да и не нужен этот буфер может быть совсем.
      Еще одна причина – при наличии внешней PSRAM я принудительно загоняю строки туда (кроме логов).

      1. Аркадий

        Я сейчас глянул внимательно ваш исходник – там новый буфер выделяется только если его длина превысит 256 байт, а так, используется статический – хорошая мысль. Насчёт PSRAM – можно прямо указать, где malloc() будет брать память? И второй вопрос – я так понимаю, речь идёт о переменных строках – это для экономии основной RAM?

        1. Возможно, я уже и не помню. Вообще, я в следующей версии комплекта библиотек я планирую отказаться от “своего” лога. Причина проста – универсальность применения другими читателями

          Да, можно. А, поскольку я часто и много формирую строки, например JSON-пакеты, то разумно временно хранить их в PSRAM. Да, это чуть медленнее, но если длина буфера не превышает “окно” в адресном пространстве, то не сильно и заметно. Да в автоматике типа “вкл”-“выкл” это не заметно вообще.

          1. Аркадий

            Мне ваш лог нравится ещё и тем, что там временная отметка в виде времени суток (можно добавить и дату) – а это гораздо удобнее, чем миллисекунды от старта, особенно на медленных процессах и если лог планируется где-то сохранять

            1. Это да, что есть то есть. Хотя в описании ESP_LOG и декларируется вывод времени в нормальном виде, но я так и не смог это настроить.

              Но тут “заковыка”… Прошивки и библиотеки “пошли в народ”, кто-то пытается адаптировать их под себя. И при этом часто жалуются, что какая-то библиотека тащит за собой много лишнего. Да и при написании статей приходится исправлять текст. Я думаю над этим, но пока ничего не придумал здравого.

    2. Аркадий

      Кстати, сегодня случайно обнаружил, что за вывод системного времени в часах, минутах, секундах и миллисекундах в оригинальных функциях лога отвечает параметр CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM=y в файле sdkconfig.
      В редакторе конфигурации этот параметр (“Log Timestamps”) есть в секции “Log output”.
      Проверил – работает.
      Однако включается эта опция не для всех сообщений
      I (1585) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
      I (1595) wifi:idx:0 (ifx:0, 4e:5d:4e:4b:c4:10), tid:0, ssn:2, winSize:64
      I (1635) wifi:AP’s beacon interval = 102400 us, DTIM period = 1
      I (00:00:02.654) esp_netif_handlers: sta ip: 192.168.0.183, mask: 255.255.255.0, gw: 192.168.0.1
      I (00:00:02.655) wifi station: got ip:192.168.0.183
      I (00:00:02.659) wifi station: connected to ap SSID:******** password:********
      I (03:00:02.669) main_task: Returned from app_main()
      I (12:00:48.293) wifi station: Time synchronization completed, current time: 30.10.2024 12:00:48

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *