[](https://github.com/GyverLibs/StringUtils/releases/latest/download/StringUtils.zip)
[](https://alexgyver.ru/)
[](https://alexgyver.ru/support_alex/)
[](https://github-com.translate.goog/GyverLibs/StringUtils?_x_tr_sl=ru&_x_tr_tl=en)
[](https://t.me/GyverLibs)
# StringUtils
Набор инструментов для работы со строками
- Быстрые функции конвертации
- Парсинг, разбивание по разделителям
- Несколько классов-конвертеров данных в строку и обратно для использования в других библиотеках
- Кодирование и раскодирование base64, url, unicode, йцукен/qwerty-раскладки
### Совместимость
Совместима со всеми Arduino платформами (используются Arduino-функции)
## Содержание
- [Документация](#docs)
- [Версии](#versions)
- [Установка](#install)
- [Баги и обратная связь](#feedback)
## Документация
### su::Text
Класс-обёртка для всех типов строк. Может быть создана в конструкторе из:
- `"const char"` - строки
- `char[]` - строки
- `F("f-строки")`
- `PROGMEM` - строки
- `String` - строки
Особенности:
- Хранит тип и длину строки
- Позволяет **печататься**, **конвертироваться** в любой целочисленный формат и **сравниваться** с переменными всех стандартных типов, а также сравниваться с любыми другими строками
- Вывод в подстроки разными способами, поиск и разделение
- **Не может изменять исходную строку**, все операции только "для чтения"
- **Не создаёт копию строки** и работает с оригинальной строкой, т.е. *оригинальная строка должна быть в памяти на время существования Text*
- Если создана из String строки, то оригинальная String строка не должна меняться в процессе работы экземпляра Text
```cpp
// ====== КОНСТРУКТОР ======
su::Text(String& str);
su::Text(const String& str);
su::Text(const __FlashStringHelper* str, int16_t len = 0);
su::Text(const char* str, int16_t len = 0, bool pgm = 0);
// ======== СИСТЕМА ========
bool valid(); // Статус строки, существует или нет
bool pgm(); // Строка из Flash памяти
uint16_t length(); // Длина строки
uint16_t lengthUnicode();// Длина строки с учётом unicode символов
uint16_t readLen(); // посчитать и вернуть длину строки (const)
void calcLen(); // пересчитать и запомнить длину строки (non-const)
Type type(); // Тип строки
const char* str(); // Получить указатель на строку. Всегда вернёт ненулевой указатель
const char* end(); // указатель на конец строки. Всегда вернёт ненулевой указатель
bool terminated(); // строка валидна и оканчивается \0
// ======== ХЭШ ========
size_t hash(); // хэш строки size_t
uint32_t hash32(); // хэш строки 32 бит
// ======== PRINT ========
size_t printTo(Print& p); // Напечатать в Print (c учётом длины)
// ======== СРАВНЕНИЕ И ПОИСК ========
// сравнивается со всеми типами строк через ==
// Сравнить со строкой, начиная с индекса
bool compare(Text s, uint16_t from = 0);
// Сравнить со строкой, начиная с индекса, с указанием количества символов
bool compareN(Text s, uint16_t amount, uint16_t from = 0);
// Найти позицию символа в строке, начиная с индекса
int16_t indexOf(char sym, uint16_t from = 0);
// Найти позицию строки в строке
int16_t indexOf(Text txt, uint16_t from = 0);
// Найти позицию символа в строке с конца
int16_t lastIndexOf(char sym);
// Найти позицию строки в строке с конца
int16_t lastIndexOf(Text txt);
// найти символ и получить указатель на первое вхождение
const char* find(char sym, uint16_t from = 0);
// начинается со строки
bool startsWith(const Text& txt);
// заканчивается строкой
bool endsWith(const Text& txt);
// ======== РАЗДЕЛЕНИЕ И ПАРСИНГ ========
// вернёт новую строку с убранными пробельными символами с начала и конца
Text trim();
// Посчитать количество подстрок, разделённых символом (количество разделителей +1)
uint16_t count(char sym);
// Посчитать количество подстрок, разделённых строками (количество разделителей +1)
uint16_t count(Text txt);
// Разделить по символу-разделителю в массив любого типа
uint16_t split(T* arr, uint16_t len, char div);
// Получить подстроку из списка по индексу
Text getSub(uint16_t idx, char div);
// Получить подстроку из списка по индексу
Text getSub(uint16_t idx, Text div);
// выделить подстроку (начало, конец не включая). Отрицательные индексы работают с конца строки
Text substring(int16_t start, int16_t end = 0);
// Получить символ по индексу
char charAt(uint16_t idx);
// ======== ВЫВОД. СТРОКИ ========
// Получить как String строку
String toString(bool decodeUnicode = false);
// Вывести в String строку. Вернёт false при неудаче
bool toString(String& s, bool decodeUnicode = false);
// Добавить к String строке. Вернёт false при неудаче
bool addString(String& s, bool decodeUnicode = false);
// Вывести в char массив. Вернёт длину строки. terminate - завершить строку нулём
uint16_t toStr(char* buf, int16_t bufsize = -1, bool terminate = true);
// ======== ВЫВОД. B64 ========
// размер данных (байт), если они b64
size_t sizeB64();
// вывести в переменную из b64
bool decodeB64(void* var, size_t size);
// ======== ВЫВОД. ЧИСЛА ========
bool toBool(); // получить значение как bool
int16_t toInt16(); // получить значение как int16
int32_t toInt32(); // получить значение как int32
int64_t toInt64(); // получить значение как int64
uint32_t toInt32HEX(); // получить значение как uint 32 из HEX строки
float toFloat(); // получить значение как float
// также автоматически конвертируется и сравнивается с
bool
char + unsigned
short + unsigned
int + unsigned
long + unsigned
long long + unsigned
float
double
String
// для ручного управления строкой
const char* _str; // указатель на строку
uint16_t _len; // длина
```
#### Пример
```cpp
// конструктор
su::Text v0("-123456");
su::Text v1 = "-123456";
v1 = F("-123456");
String s("abcd");
su::Text v2(s);
v2 = s;
// сравнение
v2 == v1;
v2 == F("text");
v1 == -123456;
// авто конвертация
int v = v0;
String s2 = v2;
// вывод в массив
char buf[20];
v1.toStr(buf);
// парсинг и разделение
su::Text list("abc/123/def");
Serial.println(list.getSub(0, '/')); // abc
Serial.println(list.getSub(2, '/')); // def
Serial.println(list.substring(4, 6)); // 123
su::Text arr[3];
list.split(arr, 3, '/');
Serial.println(arr[0]);
Serial.println(arr[1]);
Serial.println(arr[2]);
// парсить можно в любой тип
int arr2[3]; // float, byte...
list.split(arr2, 3, '/');
Serial.println(arr2[0]);
Serial.println(arr2[1]);
Serial.println(arr2[2]);
// так делать НЕЛЬЗЯ
Text t1(String("123")); // строка будет выгружена из памяти!
// t1.... программа сломается
String s;
Text t1(s);
s += String("123"); // адрес строки изменится!
// t1.... программа сломается
// в то же время вот так - можно
void foo(const Text& text) {
// String существует тут
print(text);
}
foo(String("123"));
```
Встроенный разделитель и хэш-функции позволяют очень просто и эффективно разбирать различные текстовые протоколы. Например пакет вида `key=value`, где `key` может отсылать к переменной в коде. Пакет можно разделить, ключ хешировать и опросить через switch для присвоения н ужной переменной:
```cpp
Text txt("key1=1234");
int val = txt.getSub(1, '='); // значение в int
switch (txt.getSub(0, '=').hash()) { // хэш ключа
case su::SH("key1"):
var1 = val;
break;
case su::SH("key2"):
var2 = val;
break;
case su::SH("key3"):
var2 = val;
break;
}
```
или протокол вида `name/index/value`, где `name` - текстовый ключ, `index` - порядковый номер:
```cpp
Text txt("key/3/1234");
int val = txt.getSub(2, '/');
switch (txt.getSub(0, '/').hash()) {
case su::SH("key"):
switch(txt.getSub(1, '/').toInt16()) {
case 0: break;
case 1: break;
case 2: break;
//.....
}
break;
case su::SH("keykey"):
//...
break;
case su::SH("anotherKey"):
//...
break;
}
```
### su::Value
Добавка к `Text`, поддерживает все остальные стандартные типы данных. Имеет буфер 22 байта, при создании конвертирует число в него:
```cpp
su::Value(bool value);
su::Value(char + unsigned value, uint8_t base = DEC);
su::Value(short + unsigned value, uint8_t base = DEC);
su::Value(int + unsigned value, uint8_t base = DEC);
su::Value(long + unsigned value, uint8_t base = DEC);
su::Value(long long + unsigned value, uint8_t base = DEC);
su::Value(double value, uint8_t dec = 2);
// аналогично с ручным размером буфера
su::ValueT<размер буфера>();
```
#### Пример
```cpp
su::Value v0("-123456"); // все строки также можно
su::Value v1(123);
su::Value v2(3.14);
su::Value v3((uint64_t)12345678987654321);
// конвертируется из числа в текст
v1 = 10;
v1 = 3.14;
v1 = 12345654321ull;
Serial.println(v0); // печатается в Serial
Serial.println(v1 == v2); // сравнивается
// сравнивается с любыми строками
su::Text s("123");
String ss = "123";
Serial.println(s == "123");
Serial.println(s == F("123"));
Serial.println(s == ss);
// конвертируется в любой тип
float f = v2; // f == 3.14
int i = v1; // i = 123
// выводится в String
String S;
v0.toString(s);
// выводится в char[]
char buf[v1.length() + 1]; // +1 для '\0'
v1.toStr(buf);
```
#### Использование в библиотеках
На базе `Text` построены следующие библиотеки:
- [GSON](https://github.com/GyverLibs/GSON)
- [GyverHub](https://github.com/GyverLibs/GyverHub)
- [Pairs](https://github.com/GyverLibs/Pairs)
- [FastBot2](https://github.com/GyverLibs/FastBot2)
##### Передача текста в функцию
- Строки любого типа
- Без аллокаций, что чрезвычайно критично при сборке String
- Без создания десятка перегруженных функций
Например нужна функция, принимающая строку в любом виде. В ванильном фреймворке Arduino можно сделать так:
```cpp
void setText(const String& str) {
// и например прибавить к строке
s += str;
}
```
Такая функция сможет принимать любые строки:
```cpp
setText("const literal");
setText(F("F-string"));
char str[] = "buffer string";
setText(str);
String s = "Arduino String";
setText(s);
```
Но эта строка будет *продублирована* в конструкторе `String`, и самое страшное - в динамической памяти! Таким образом при прибавлении к условно-глобальной String в этой области определения случится переаллокация и фрагментирование памяти. `Text` позволяет полностью этого избежать:
```cpp
void setText(const Text& str) {
// и например прибавить к строке
str.addString(s);
}
```
Теперь эта функция так же умеет принимать строки в любом формате, но **не создаёт их копии**, и например прибавление к строке становится быстрым и безопасным.
##### Вывод текста
Также Text удобен для вывода, например в классе, который хранит буфер и сам наполняет его данными и знает их длину:
```cpp
class MyClass {
public:
su::Text get() {
return su::Text(buffer, len);
}
private:
char buffer[20];
int len;
};
MyClass s;
Serial.println(s.get());
```
Вариант с наследованием:
```cpp
class MyClass : public su::Text {
public:
void foo() {
su::Text::_str = buffer;
su::Text::_len = somelen;
}
private:
char buffer[20];
};
MyClass s;
Serial.println(s);
```
Если вместо `Text` использовать `Value` - функция сможет принимать также любые численные данные.
### su::TextList
Разделитель `Text` списков на `Text` подстроки.
#### Статический
```cpp
TextListT(Text list, char div);
TextListT(Text list, Text div);
// количество построк
uint16_t length();
// размер буфера
uint16_t capacity();
// получить подстроку под индексом
const Text& get(uint16_t idx);
const Text& operator[](int idx);
```
#### Динамический
```cpp
TextList(Text list, char div);
TextList(Text list, Text div);
// количество построк
uint16_t length();
// получить подстроку под индексом
const Text& get(uint16_t idx);
const Text& operator[](int idx);
```
### Устаревшие парсеры
Развернуть
### su::Parser
Разделение строки на подстроки по разделителю в цикле. **Изменяет** исходную строку, но после завершения возвращает разделители на место.
```cpp
su::Parser p(String& str, char div = ';');
su::Parser p(const char* str, char div = ';');
bool next(); // парсить следующую подстроку. Вернёт false, если парсинг закончен
uint8_t index(); // индекс текущей подстроки
const char* str(); // получить подстроку
Text get(); // получить подстроку как Text
```
#### Пример
```cpp
char buf[] = "123;456;abc";
su::Parser p(buf);
while (p.next()) {
Serial.print(p.index());
Serial.print(": ");
Serial.println(p.get());
}
```
### su::Splitter
Разделение строки на подстроки по разделителю в цикле. **Изменяет** исходную строку! После удаления объекта строка восстанавливается, либо вручную вызвать `restore()`
```cpp
su::SplitterT<макс. подстрок> spl(String& str, char div = ';');
su::SplitterT<макс. подстрок> spl(const char* str, char div = ';');
su::Splitter spl(String& str, char div = ';'); // авто-размер (выделяется в heap)
su::Splitter spl(const char* str, char div = ';'); // авто-размер (выделяется в heap)
void setDiv(char div); // установить разделитель
void restore(); // восстановить строку (вернуть разделители)
uint8_t length(); // количество подстрок
const char* str(uint16_t idx); // получить подстроку по индексу
Text get(uint16_t idx); // получить подстроку по индексу как Text
```
#### Пример
```cpp
char buf[] = "123;456;abc";
su::Splitter spl(buf);
for (uint8_t i = 0; i < spl.length(); i++) {
Serial.print(i);
Serial.print(": ");
Serial.println(spl[i]);
}
spl.restore();
```
### su::list функции
```cpp
// Получить количество подстрок в списке
uint16_t su::list::length(Text list, char div = ';');
// Получить индекс подстроки в списке
int16_t su::list::indexOf(Text list, Text str, char div = ';');
// Проверка содержит ли список подстроку
bool su::list::includes(Text list, Text str, char div = ';');
// Получить подстроку из списка по индексу
Text su::list::get(Text list, uint16_t idx, char div = ';');
// распарсить в массив указанного типа и размера. Вернёт количество записанных подстрок
template
uint16_t su::list::parse(Text list, T* buf, uint16_t len, char div = ';');
```
#### Пример
```cpp
Serial.println(su::list::length("123;456;333")); // 3
Serial.println(su::list::includes("123;456;333", "456")); // true
Serial.println(su::list::indexOf("123;456;333", "333")); // 2
Serial.println(su::list::get("123;456;333", 1)); // 456
// распарсить в массив
float arr[3];
su::list::parse(F("3.14;2.54;15.15"), arr, 3);
```
### su::List класс
Получение подстрок по разделителям **без модификации исходной строки**, работает также с PROGMEM строками.
```cpp
List(Text);
// установить разделитель
void setDiv(char div);
// получить размер списка
uint16_t length();
// получить индекс подстроки в списке или -1 если её нет
int16_t indexOf(Text str);
// проверить наличие подстроки в списке
bool includes(Text str);
// получить подстроку под индексом
Text get(uint16_t idx);
// распарсить в массив указанного типа и размера. Вернёт количество записанных подстрок
template
uint16_t parse(T* buf, uint16_t len);
```
#### Пример
```cpp
su::List list(F("123;456;333"));
Serial.print("len: ");
Serial.println(list.length()); // 3
Serial.print("index 2: ");
Serial.println(list[2]); // 333
Serial.print("index of '456':");
Serial.println(list.indexOf(F("456"))); // 1
Serial.print("index of '789':");
Serial.println(list.indexOf("789")); // -1
// переписать в массив
int arr[3];
list.parse(arr, 3);
```
### su::PrintString
```cpp
// строка, в которую можно делать print/println
su::PrintString prs;
prs += "как обычный String";
prs.print(10);
prs.println("hello");
prs.print(F("print!"));
Serial.println(prs);
```
### QWERTY
```cpp
// Изменить раскладку (RU в QWERTY) - String
String su::toQwerty(const String& ru);
// Изменить раскладку (RU в QWERTY) - char* (qw длина как ru + 1, функция добавит '\0')
char* su::toQwerty(const char* ru, char* qw);
```
### Base64
```cpp
// размер закодированных данных по размеру исходных
size_t su::b64::encodedLen(size_t len);
// будущий размер декодированных данных по строке b64 и её длине
size_t su::b64::decodedLen(const char* b64, size_t len);
// закодировать данные в String
void su::b64::encode(String* b64, uint8_t* data, size_t len, bool pgm = false);
// закодировать данные в char[] (библиотека не добавляет '\0' в конец)
void su::b64::encode(char* b64, uint8_t* data, size_t len, bool pgm = false);
// раскодировать данные из строки b64 в буфер data
void su::b64::decode(uint8_t* data, const char* b64, size_t len);
void su::b64::decode(uint8_t* data, const String& b64);
```
### Unicode
Декодер строки, содержащей unicode символы вида `\u0abc`. Также делает unescape символов `\t\r\n`!
```cpp
// декодировать строку.Зарезервировать строку на длину len. Иначе - по длине строки
String su::unicode::decode(const char* str, uint16_t len = 0);
// декодировать строку
String su::unicode::decode(const String& str);
// кодировать unicode символ по его коду. В массиве должно быть 5 ячеек
void su::unicode::encode(char* str, uint32_t c);
// кодировать unicode символ по его коду
String su::unicode::encode(uint32_t code);
```
### URL
```cpp
// символ должен быть urlencoded
bool su::url::needsEncode(char c);
// закодировать в url
void su::url::encode(const char* src, uint16_t len, String& dest);
void su::url::encode(const String& src, String& dest);
String su::url::encode(const String& src);
// раскодировать url
void su::url::decode(const char* src, uint16_t len, String& dest);
void su::url::decode(const String& src, String& dest);
String su::url::decode(const String& src);
```
### Hash
Вместо сравнения строк можно сравнивать хэш этих строк, что делает программу компактнее, легче и в большинстве случаев быстрее. Функции, указанные ниже как "считается компилятором" - считаются компилятором, то есть **строка даже не попадает в код программы** - вместо неё подставляется хэш-число:
```cpp
// считается компилятором
constexpr su::size_t su::SH(const char* str); // (String Hash) размер size_t
constexpr su::size_t SH32(const char* str); // (String Hash) размер 32 бит
// считается в рантайме
size_t su::hash(const char* str, int16_t len = -1); // Размер зависит от платформы и соответствует size_t
uint32_t su::hash32(const char* str, int16_t len = -1); // Размер 32 бит
size_t su::hash_P(PGM_P str, int16_t len = -1); // PROGMEM строка, размер size_t
uint32_t su::hash32_P(PGM_P str, int16_t len = -1); // PROGMEM строка, размер 32 бит
```
> На ESP-платах `SH`, `hash` и `hash_P` по умолчанию являются 32-битными!
По проведённому тесту 32-битная версия хэша имеет 7 коллизий из 234450 английских слов, 16-битная версия - 170723 коллизий (что есть 73% - чисто статистическое количество коллизий из расчёта 16 бит - 65536 значений)
#### Пример
Определить, какая строка была "получена". Классический способ со сравнением строк из PROGMEM:
```cpp
char buf[] = "some_text";
if (!strcmp_P(buf, PSTR("abcdef"))) Serial.println(0);
else if (!strcmp_P(buf, PSTR("12345"))) Serial.println(1);
else if (!strcmp_P(buf, PSTR("wrong text"))) Serial.println(2);
else if (!strcmp_P(buf, PSTR("some text"))) Serial.println(3);
else if (!strcmp_P(buf, PSTR("hello"))) Serial.println(4);
else if (!strcmp_P(buf, PSTR("some_text"))) Serial.println(5);
```
Способ с хэшем строки:
```cpp
using su::SH;
using su::hash;
char buf[] = "some_text";
switch (hash(buf)) {
case su::SH("abcdef"): Serial.println(0); break;
case su::SH("12345"): Serial.println(1); break;
case su::SH("wrong text"): Serial.println(2); break;
case su::SH("some text"): Serial.println(3); break;
case su::SH("hello"): Serial.println(4); break;
case su::SH("some_text"): Serial.println(5); break;
}
```
> Один расчёт хэша занимает чуть большее время, чем сравнение со строкой. Но итоговая конструкция из примера выполняется в 2 раза быстрее (на ESP).
> `SH("строки")` в данном примере вообще не попадают в код программы - вместо них подставляется их хэш
### Прочие утилиты
```cpp
// Длина строки с русскими символами
uint16_t su::strlenRu(const char* str);
// Получить длину целого числа
uint8_t su::intLen(int32_t val);
// Получить длину float числа
uint8_t su::floatLen(double val, uint8_t dec);
// Преобразовать строку в целое число
template
T su::strToInt(const char* str, uint8_t len = 0);
// Преобразовать PROGMEM строку в целое число
template
T su::strToInt_P(const char* str, uint8_t len = 0);
// Преобразовать float в строку с указанием кол-ва знаков после точки
uint8_t su::floatToStr(double val, char* buf, uint8_t dec = 2);
// Преобразовать HEX строку в целое число. Опционально длина
uint32_t su::strToIntHex(const char* str, int8_t len = -1);
// Длина символа в количестве байт
uint8_t su::charSize(char sym);
// Конвертация числа в char* массив. Пишет от начала массива, добавляет '\0', вернёт длину строки
// для int64 макс. длина буфера - 22 символа, для int32 - 12
uint8_t su::uintToStr(uint32_t n, char* buf, uint8_t base = DEC);
uint8_t su::intToStr(int32_t n, char* buf, uint8_t base = DEC);
uint8_t su::uint64ToStr(uint64_t n, char* buf, uint8_t base = DEC);
uint8_t su::int64ToStr(int64_t n, char* buf, uint8_t base = DEC);
// конвертация из строки во float
float su::strToFloat(const char* s);
// конвертация из PROGEMEM строки во float
float su::strToFloat_P(PGM_P s);
// быстрый целочисленный логарифм 10 (длина числа в кол-ве символов)
uint8_t su::getLog10(uint32_t value);
uint8_t su::getLog10(int32_t value);
// быстрое возведение 10 в степень
uint32_t su::getPow10(uint8_t value);
```
## Версии
- v1.0
- v1.1.0 - оптимизация, добавлены фичи, исправлены уязвимости
- v1.2 - добавлены хэш-функции
- v1.2.x - мелкие исправления и улучшения
- v1.3 - оптимизация const, добавлены фичи, сравнения
- v1.3.1 - добавлен substring
- v1.3.2 - поддержка ESP8266 версий 2.x
- v1.3.5 - uintToStr: HEX теперь в нижнем регистре как у си-функций
- v1.3.6 - в Text добавлены toInt32HEX(), count(), split() и getSub(). Добавлен парсер TextList
- v1.3.7 - исправлены варнинги на AVR
- v1.4.0 - AnyText переименован в Text, пространство имён sutil - в su, добавлены функции в Text, результат конвертации и substring приведены к Си и JS функциям
## Установка
- Библиотеку можно найти по названию **StringUtils** и установить через менеджер библиотек в:
- Arduino IDE
- Arduino IDE v2
- PlatformIO
- [Скачать библиотеку](https://github.com/GyverLibs/StringUtils/archive/refs/heads/main.zip) .zip архивом для ручной установки:
- Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64)
- Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32)
- Распаковать и положить в *Документы/Arduino/libraries/*
- (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA)
### Обновление
- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
- Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
## Баги и обратная связь
При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)
Библиотека открыта для доработки и ваших **Pull Request**'ов!
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
- Версия библиотеки
- Какой используется МК
- Версия SDK (для ESP)
- Версия Arduino IDE
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код