Набор инструментов для работы со строками
- Быстрые функции конвертации
- Парсинг, разбивание по разделителям
- Несколько классов-конвертеров данных в строку и обратно для использования в других библиотеках
- Кодирование и раскодирование base64, url, unicode, йцукен/qwerty-раскладки
Совместима со всеми Arduino платформами (используются Arduino-функции)
Класс-обёртка для всех типов строк. Может быть создана в конструкторе из:
"const char"
- строкиchar[]
- строкиF("f-строки")
PROGMEM
- строкиString
- строки
Особенности:
- Хранит тип и длину строки
- Позволяет печататься, конвертироваться в любой целочисленный формат и сравниваться с переменными всех стандартных типов, а также сравниваться с любыми другими строками
- Вывод в подстроки разными способами, поиск и разделение
- Не может изменять исходную строку, все операции только "для чтения"
- Не создаёт копию строки и работает с оригинальной строкой, т.е. оригинальная строка должна быть в памяти на время существования Text
- Если создана из String строки, то оригинальная String строка не должна меняться в процессе работы экземпляра Text
// ====== КОНСТРУКТОР ======
su::Text(String& str);
su::Text(const String& str);
su::Text(const uint8_t* str, uint16_t len);
su::Text(const char* str, int16_t len = 0, bool pgm = 0);
su::Text(const __FlashStringHelper* str, int16_t len = 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);
uint16_t split(T** arr, uint16_t len, char div);
// Разделить по строке-разделителю в массив любого типа
uint16_t split(T* arr, uint16_t len, Text div);
uint16_t split(T** arr, uint16_t len, Text 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; // длина
// конструктор
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 для присвоения н ужной переменной:
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
- порядковый номер:
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;
}
Добавка к Text
, поддерживает все остальные стандартные типы данных. Имеет буфер 22 байта, при создании конвертирует число в него:
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<размер буфера>();
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
построены следующие библиотеки:
- Строки любого типа
- Без аллокаций, что чрезвычайно критично при сборке String
- Без создания десятка перегруженных функций
Например нужна функция, принимающая строку в любом виде. В ванильном фреймворке Arduino можно сделать так:
void setText(const String& str) {
// и например прибавить к строке
s += str;
}
Такая функция сможет принимать любые строки:
setText("const literal");
setText(F("F-string"));
char str[] = "buffer string";
setText(str);
String s = "Arduino String";
setText(s);
Но эта строка будет продублирована в конструкторе String
, и самое страшное - в динамической памяти! Таким образом при прибавлении к условно-глобальной String в этой области определения случится переаллокация и фрагментирование памяти. Text
позволяет полностью этого избежать:
void setText(const Text& str) {
// и например прибавить к строке
str.addString(s);
}
Теперь эта функция так же умеет принимать строки в любом формате, но не создаёт их копии, и например прибавление к строке становится быстрым и безопасным.
Также Text удобен для вывода, например в классе, который хранит буфер и сам наполняет его данными и знает их длину:
class MyClass {
public:
su::Text get() {
return su::Text(buffer, len);
}
private:
char buffer[20];
int len;
};
MyClass s;
Serial.println(s.get());
Вариант с наследованием:
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
- функция сможет принимать также любые численные данные.
Разделитель Text
списков на Text
подстроки.
TextListT<int16_t cap>(Text list, char div);
TextListT<int16_t cap>(Text list, Text div);
// количество построк
uint16_t length();
// размер буфера
uint16_t capacity();
// получить подстроку под индексом
const Text& get(uint16_t idx);
const Text& operator[](int idx);
TextList(Text list, char div);
TextList(Text list, Text div);
// количество построк
uint16_t length();
// получить подстроку под индексом
const Text& get(uint16_t idx);
const Text& operator[](int idx);
"Потоковый" разделитель Text
строки на подстроки для работы в цикле
TextParser(const Text& txt, char div);
TextParser(const Text& txt, const Text& div);
// парсить. Вернёт true, если найдена подстрока
bool parse();
// индекс подстроки (всегда > 0)
int index();
// получить текущую подстроку
const Text& get();
Пример:
// for
for (su::TextParser p("123;456", ';'); p.parse();) {
Serial.println(p);
}
// while
su::TextParser p("123;456", ';');
while (p.parse()) {
Serial.println(p);
}
Пример с вложенными подстроками с разными разделителями:
su::Text t("123;456\nabc;def;ghk\n333;444");
for (su::TextParser row(t, '\n'); row.parse();) {
for (su::TextParser col(row, ';'); col.parse();) {
Serial.print(col);
Serial.print(',');
}
Serial.println();
}
// вывод:
// 123,456,
// abc,def,ghk,
// 333,444,
Статический стринг билдер на базе Text, замена mString
template <uint16_t cap> StringStatic;
StringExt(char* buf, uint16_t capacity, uint16_t len = 0);
// очистить
void clear();
// завершить нулём
void terminate();
// автоматически завершать нулём (умолч. false)
void terminateAuto(bool ter);
// обрезать с from до конца
void trunc(uint16_t from);
// прибавить
bool concat(const Value& txt);
bool concat(const char* str, uint16_t len);
bool concat(double val, uint8_t dec);
// присвоить
bool assign(const Value& txt);
bool assign(const char* str, uint16_t len);
bool assign(double val, uint8_t dec);
// можно приравнивать и прибавлять любой тип через =, +=, +
Пример:
su::StringStatic<50> s;
// char str[20];
// su::StringExt s(str, 20);
s = F("abc");
s += "def";
s += 12345;
s += 'a';
Serial.println(s);
Serial.println(s.length());
s.clear();
s = s + 123 + "abc" + F("FSTR") + 3.14;
Serial.println(s);
Можно дописать существующую строку:
char str[20] = "hello"; // len 5
su::StringExt s(str, 20, 5);
s += F(" world!");
Serial.println(s);
Развернуть
Разделение строки на подстроки по разделителю в цикле. Изменяет исходную строку, но после завершения возвращает разделители на место.
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
char buf[] = "123;456;abc";
su::Parser p(buf);
while (p.next()) {
Serial.print(p.index());
Serial.print(": ");
Serial.println(p.get());
}
Разделение строки на подстроки по разделителю в цикле. Изменяет исходную строку! После удаления объекта строка восстанавливается, либо вручную вызвать restore()
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
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();
// Получить количество подстрок в списке
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 <typename T>
uint16_t su::list::parse(Text list, T* buf, uint16_t len, char div = ';');
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);
Получение подстрок по разделителям без модификации исходной строки, работает также с PROGMEM строками.
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 <typename T>
uint16_t parse(T* buf, uint16_t len);
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);
// строка, в которую можно делать print/println
su::PrintString prs;
prs += "как обычный String";
prs.print(10);
prs.println("hello");
prs.print(F("print!"));
Serial.println(prs);
// Изменить раскладку (RU в QWERTY) - String
String su::toQwerty(const String& ru);
// Изменить раскладку (RU в QWERTY) - char* (qw длина как ru + 1, функция добавит '\0')
char* su::toQwerty(const char* ru, char* qw);
// размер закодированных данных по размеру исходных
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 символы вида \u0abc
. Также делает unescape символов \t\r\n
!
// декодировать строку.Зарезервировать строку на длину 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);
// символ должен быть 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);
// StringLength длина строки, выполняется на этапе компиляции
constexpr size_t su::SL(const char* str);
constexpr size_t operator"" _SL; // C++ 11
Примеры
int L1 = su:SL("text");
int L2 = "text"_SL;
Вместо сравнения строк можно сравнивать хэш этих строк, что делает программу компактнее, легче и в большинстве случаев быстрее. Функции, указанные ниже как "считается компилятором" - считаются компилятором, то есть строка даже не попадает в код программы - вместо неё подставляется хэш-число:
// считается компилятором
constexpr size_t su::SH(const char* str); // (String Hash) размер size_t
constexpr size_t SH32(const char* str); // (String Hash) размер 32 бит
constexpr size_t operator"" _SH; // C++ 11
constexpr size_t operator"" _SH32; // C++ 11
// считается в рантайме
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
,SH
,hash
иhash_P
по умолчанию являются 32-битными!
По проведённому тесту 32-битная версия хэша имеет 7 коллизий из 234450 английских слов, 16-битная версия - 170723 коллизий (что есть 73% - чисто статистическое количество коллизий из расчёта 16 бит - 65536 значений)
Определить, какая строка была "получена". Классический способ со сравнением строк из PROGMEM:
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);
Способ с хэшем строки:
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 "some text"_SH: Serial.println(3); break;
case "hello"_SH: Serial.println(4); break;
case "some_text"_SH: Serial.println(5); break;
}
Один расчёт хэша занимает чуть большее время, чем сравнение со строкой. Но итоговая конструкция из примера выполняется в 2 раза быстрее (на ESP).
SH("строки")
в данном примере вообще не попадают в код программы - вместо них подставляется их хэш
// Длина строки с русскими символами
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 <typename T>
T su::strToInt(const char* str, uint8_t len = 0);
// Преобразовать PROGMEM строку в целое число
template <typename T>
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 функциям
- 1.4.1
- оптимизирован split
- можно split-разделять в массив указателей на переменные
- добавлен TextParser
- добавлены стринг билдеры StringExt и StringStatic
- 1.4.3 - Оптимизация сравнения, добавлено constexpr измерение длины строки
- 1.4.7 - исправлен баг split для esp32
- 1.4.9 - оптимизация, добавлены короткие функции хеширования
- Библиотеку можно найти по названию StringUtils и установить через менеджер библиотек в:
- Arduino IDE
- Arduino IDE v2
- PlatformIO
- Скачать библиотеку .zip архивом для ручной установки:
- Распаковать и положить в C:\Program Files (x86)\Arduino\libraries (Windows x64)
- Распаковать и положить в C:\Program Files\Arduino\libraries (Windows x32)
- Распаковать и положить в Документы/Arduino/libraries/
- (Arduino IDE) автоматическая установка из .zip: Скетч/Подключить библиотеку/Добавить .ZIP библиотеку… и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек здесь
- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
- Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
При нахождении багов создавайте Issue, а лучше сразу пишите на почту alex@alexgyver.ru
Библиотека открыта для доработки и ваших Pull Request'ов!
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
- Версия библиотеки
- Какой используется МК
- Версия SDK (для ESP)
- Версия Arduino IDE
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код