{"id":18931394,"url":"https://github.com/gyverlibs/encbutton","last_synced_at":"2025-04-15T03:55:08.769Z","repository":{"id":40665020,"uuid":"370389240","full_name":"GyverLibs/EncButton","owner":"GyverLibs","description":"Лёгкая и очень функциональная библиотека для энкодера с кнопкой, энкодера или кнопки с Arduino","archived":false,"fork":false,"pushed_at":"2025-03-09T06:45:45.000Z","size":1133,"stargazers_count":161,"open_issues_count":0,"forks_count":20,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-15T03:55:02.265Z","etag":null,"topics":["arduino","arduino-library","button","encoder"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GyverLibs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2021-05-24T14:55:45.000Z","updated_at":"2025-04-12T18:05:35.000Z","dependencies_parsed_at":"2022-07-14T05:00:32.534Z","dependency_job_id":"463a2e3a-e11f-4727-b8ba-ebf586c1b0cb","html_url":"https://github.com/GyverLibs/EncButton","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GyverLibs%2FEncButton","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GyverLibs%2FEncButton/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GyverLibs%2FEncButton/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GyverLibs%2FEncButton/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GyverLibs","download_url":"https://codeload.github.com/GyverLibs/EncButton/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249003953,"owners_count":21196794,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","arduino-library","button","encoder"],"created_at":"2024-11-08T11:42:00.221Z","updated_at":"2025-04-15T03:55:08.758Z","avatar_url":"https://github.com/GyverLibs.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![latest](https://img.shields.io/github/v/release/GyverLibs/EncButton.svg?color=brightgreen)](https://github.com/GyverLibs/EncButton/releases/latest/download/EncButton.zip)\n[![PIO](https://badges.registry.platformio.org/packages/gyverlibs/library/EncButton.svg)](https://registry.platformio.org/libraries/gyverlibs/EncButton)\n[![Foo](https://img.shields.io/badge/Website-AlexGyver.ru-blue.svg?style=flat-square)](https://alexgyver.ru/)\n[![Foo](https://img.shields.io/badge/%E2%82%BD%24%E2%82%AC%20%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%82%D1%8C-%D0%B0%D0%B2%D1%82%D0%BE%D1%80%D0%B0-orange.svg?style=flat-square)](https://alexgyver.ru/support_alex/)\n[![Foo](https://img.shields.io/badge/README-ENGLISH-blueviolet.svg?style=flat-square)](https://github-com.translate.goog/GyverLibs/EncButton?_x_tr_sl=ru\u0026_x_tr_tl=en)  \n\n[![Foo](https://img.shields.io/badge/ПОДПИСАТЬСЯ-НА%20ОБНОВЛЕНИЯ-brightgreen.svg?style=social\u0026logo=telegram\u0026color=blue)](https://t.me/GyverLibs)\n\n# EncButton\n\n| ⚠️⚠️⚠️\u003cbr\u003e**Новая версия v3 несовместима с предыдущими, смотри [документацию](#docs), [примеры](#example) и краткий [гайд по миграции](#migrate) с v2 на v3!**\u003cbr\u003e⚠️⚠️⚠️ |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n\nЛёгкая и очень функциональная библиотека для энкодера с кнопкой, энкодера или кнопки с Arduino\n- Кнопка\n    - Обработка событий: нажатие, отпускание, клик, счётчик кликов, удержание, импульсное удержание, время удержания + предварительные клики для всех режимов\n    - Программное подавление дребезга\n    - Поддержка обработки двух одновременно нажимаемых кнопок как третьей кнопки\n- Энкодер\n    - Обработка событий: обычный поворот, нажатый поворот, быстрый поворот\n    - Поддержка четырёх типов инкрементальных энкодеров\n    - Высокоточный алгоритм определения позиции\n    - Буферизация в прерывании\n- Простое и понятное использование\n- Огромное количество возможностей и их комбинаций для разных сценариев использования даже одной кнопки\n- Виртуальный режим (например для работы с расширителем пинов)\n- Оптимизирована для работы в прерывании\n- Максимально быстрое чтение пинов для AVR, esp8266, esp32 (используется GyverIO)\n- Быстрые асинхронные алгоритмы опроса действий с кнопки и энкодера\n- Жёсткая оптимизация и небольшой вес во Flash и SRAM памяти: 5 байт SRAM (на экземпляр) и ~350 байт Flash на обработку кнопки\n\nПримеры сценариев использования:\n- Несколько кликов - включение режима (по кол-ву кликов)\n- Несколько кликов + короткое удержание - ещё вариант включения режима (по кол-ву кликов)\n- Несколько кликов + удержание - постепенное изменение значения выбранной переменной (по кол-ву кликов)\n- Несколько кликов выбирают переменную, энкодер её изменяет\n- Изменение шага изменения переменной при вращении энкодера - например уменьшение при зажатой кнопке и увеличение при быстром вращении\n- Навигация по меню при вращении энкодера, изменение переменной при вращении зажатого энкодера\n- Полноценная навигация по меню при использовании двух кнопок (одновременное удержание для перехода на следующий уровень, одновременное нажатие для возврата на предыдущий)\n- И так далее\n\n### Совместимость\nСовместима со всеми Arduino платформами (используются Arduino-функции)\n\n## Содержание\n- [Установка](#install)\n- [Информация](#info)\n- [Документация](#docs)\n  - [Настройки компиляции](#config)\n  - [Полное описание классов](#class)\n  - [Обработка и опрос](#tick)\n  - [Предварительные клики](#preclicks)\n  - [Прямое чтение кнопки](#btnread)\n  - [Погружение в цикл](#loop)\n  - [Timeout](#timeout)\n  - [Busy](#busy)\n  - [Получение события](#actions)\n  - [Оптимизация](#optimise)\n  - [Коллбэки](#callback)\n  - [Одновременное нажатие](#double)\n  - [Прерывания](#isr)\n  - [Массив кнопок/энкодеров](#array)\n  - [Кастомные функции](#custom)\n  - [Опрос по таймеру](#timer)\n  - [Мини примеры, сценарии](#examples-mini)\n- [Миграция с v2](#migrate)\n- [Примеры](#example)\n- [Версии](#versions)\n- [Баги и обратная связь](#feedback)\n\n\u003ca id=\"install\"\u003e\u003c/a\u003e\n## Установка\n- Для работы требуется библиотека [GyverIO](https://github.com/GyverLibs/GyverIO)\n- Библиотеку можно найти по названию **EncButton** и установить через менеджер библиотек в:\n    - Arduino IDE\n    - Arduino IDE v2\n    - PlatformIO\n- [Скачать библиотеку](https://github.com/GyverLibs/EncButton/archive/refs/heads/main.zip) .zip архивом для ручной установки:\n    - Распаковать и положить в *C:\\Program Files (x86)\\Arduino\\libraries* (Windows x64)\n    - Распаковать и положить в *C:\\Program Files\\Arduino\\libraries* (Windows x32)\n    - Распаковать и положить в *Документы/Arduino/libraries/*\n    - (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив\n- Читай более подробную инструкцию по установке библиотек [здесь](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)\n### Обновление\n- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи\n- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать \"Обновить\"\n- Вручную: **удалить папку со старой версией**, а затем положить на её место новую. \"Замену\" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!\n\n\u003ca id=\"info\"\u003e\u003c/a\u003e\n\n## Информация\n### Энкодер\n#### Тип энкодера\nБиблиотека поддерживает все 4 типа *инкрементальных* энкодеров, тип можно настроить при помощи `setEncType(тип)`:\n- `EB_STEP4_LOW` - активный низкий сигнал (подтяжка к VCC). Полный период (4 фазы) за один щелчок. *Установлен по умолчанию*\n- `EB_STEP4_HIGH` - активный высокий сигнал (подтяжка к GND). Полный период (4 фазы) за один щелчок\n- `EB_STEP2` - половина периода (2 фазы) за один щелчок\n- `EB_STEP1` - четверть периода (1 фаза) за один щелчок, а также энкодеры без фиксации  \n\n![diagram](/doc/enc_type.png)  \n\n#### Рекомендации\nДля работы по сценарию \"энкодер с кнопкой\" рекомендую вот такие ([ссылка](https://ali.ski/cmPI2), [ссылка](https://ali.ski/sZbTK)) круглые китайские модули с распаянными цепями антидребезга (имеют тип `EB_STEP4_LOW` по классификации выше):  \n![scheme](/doc/encAli.png)  \n\nСамостоятельно обвязать энкодер можно по следующей схеме (RC фильтры на каналы энкодера + подтяжка всех пинов к VCC):  \n![scheme](/doc/enc_scheme.png)  \n\n\u003e Примечание: по умолчанию в библиотеке пины энкодера настроены на `INPUT` с расчётом на внешнюю подтяжку. Если у вас энкодер без подтяжки - можно использовать внутреннюю `INPUT_PULLUP`, указав это при инициализации энкодера (см. документацию ниже).\n\n### Кнопка\n#### Уровень кнопки\nКнопка может быть подключена к микроконтроллеру двумя способами и давать при нажатии высокий или низкий сигнал. В библиотеке предусмотрена настройка `setBtnLevel(уровень)`, где уровень - активный сигнал кнопки:\n- `HIGH` - кнопка подключает VCC. Установлен по умолчанию в `Virt`-библиотеках\n- `LOW` - кнопка подключает GND. Установлен по умолчанию в основных библиотеках  \n\n![scheme](/doc/btn_scheme.png)  \n\n#### Подтяжка пина\nВ схемах с микроконтроллерами чаще всего используется подключение кнопки к GND с подтяжкой пина к VCC. Подтяжка может быть внешней (режим пина нужно поставить `INPUT`) или внутренней (режим пина `INPUT_PULLUP`). В \"реальных\" проектах рекомендуется внешняя подтяжка, т.к. она менее подвержена помехам - у внутренней слишком высокое сопротивление.\n\n\u003ca id=\"docs\"\u003e\u003c/a\u003e\n\n## Документация\n\n\u003ca id=\"config\"\u003e\u003c/a\u003e\n\n### Дефайны настроек\nОбъявлять до подключения библиотеки\n\n```cpp\n\n// отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)\n#define EB_NO_FOR\n\n// отключить обработчик событий attach (экономит 2 байта оперативки)\n#define EB_NO_CALLBACK\n\n// отключить счётчик энкодера [VirtEncoder, Encoder, EncButton] (экономит 4 байта оперативки)\n#define EB_NO_COUNTER\n\n// отключить буферизацию энкодера (экономит 2 байта оперативки)\n#define EB_NO_BUFFER\n\n/*\n  Настройка таймаутов для всех классов\n  - Заменяет таймауты константами, изменить их из программы (SetXxxTimeout()) будет нельзя\n  - Настройка влияет на все объявленные в программе кнопки/энкодеры\n  - Экономит 1 байт оперативки на объект за каждый таймаут\n  - Показаны значения по умолчанию в мс\n  - Значения не ограничены 4000мс, как при установке из программы (SetXxxTimeout())\n*/\n#define EB_DEB_TIME 50      // таймаут гашения дребезга кнопки (кнопка)\n#define EB_CLICK_TIME 500   // таймаут ожидания кликов (кнопка)\n#define EB_HOLD_TIME 600    // таймаут удержания (кнопка)\n#define EB_STEP_TIME 200    // таймаут импульсного удержания (кнопка)\n#define EB_FAST_TIME 30     // таймаут быстрого поворота (энкодер)\n#define EB_TOUT_TIME 1000   // таймаут действия (кнопка и энкодер)\n```\n\n\u003ca id=\"class\"\u003e\u003c/a\u003e\n\n### Классы\nКак работать с документацией: EncButton начиная с версии 3.0 представляет собой несколько библиотек (классов) для различных сценариев использования, они друг друга наследуют для расширения функциональности. Таким образом библиотека представляет собой \"луковицу\", каждый слой которой имеет доступ к функциям нижних слоёв:\n- Базовые классы:\n  - `VirtButton` - базовый класс виртуальной кнопки, обеспечивает все возможности кнопки\n  - `VirtEncoder` - базовый класс виртуального энкодера, определяет факт и направление вращения энкодера\n  - `VirtEncButton` - базовый класс виртуального энкодера с кнопкой, обеспечивает опрос энкодера с учётом кнопки, *наследует VirtButton и VirtEncoder*\n- Основные классы:\n  - `Button`, `ButtonT` - класс кнопки, *наследует VirtButton*\n  - `Encoder`, `EncoderT` - класс энкодера, *наследует VirtEncoder*\n  - `EncButton`, `EncButtonT` - класс энкодера с кнопкой, *наследует VirtEncButton, VirtButton, VirtEncoder*\n\nТаким образом для изучения всех доступных функций конкретной библиотеки нужно смотреть не только её, но и то что она наследует. Например для обработки кнопки при помощи `Button` нужно открыть ниже описание `Button` и `VirtButton`.\n\n\u003e *Виртуальный* - без указания пина микроконтроллера, работает напрямую с переданным значением, например для опроса кнопок-энкодеров через расширители пинов и сдвиговые регистры.\n\n\u003e `T`-версии библиотек требуют указания пинов константами (цифрами). Номера пинов будут храниться в памяти программы, это ускоряет работу и делает код легче на 1 байт за каждый пин.\n\n\u003e Примечание: `#include \u003cEncButton.h\u003e` подключает все инструменты библиотеки!\n\n\u003cdetails\u003e\n\u003csummary\u003eТаблица функций кнопки\u003c/summary\u003e\n\n|                   | VirtButton | VirtEncButton | Button | EncButton |\n| ----------------- | :--------: | :-----------: | :----: | :-------: |\n| read              |            |               |   ✔    |           |\n| readBtn           |            |               |        |     ✔     |\n| tickRaw           |     ✔      |       ✔       |   ✔    |     ✔     |\n| setHoldTimeout    |     ✔      |       ✔       |   ✔    |     ✔     |\n| setStepTimeout    |     ✔      |       ✔       |   ✔    |     ✔     |\n| setClickTimeout   |     ✔      |       ✔       |   ✔    |     ✔     |\n| setDebTimeout     |     ✔      |       ✔       |   ✔    |     ✔     |\n| setTimeout        |     ✔      |       ✔       |   ✔    |     ✔     |\n| setBtnLevel       |     ✔      |       ✔       |   ✔    |     ✔     |\n| pressISR          |     ✔      |       ✔       |   ✔    |     ✔     |\n| reset             |     ✔      |       ✔       |   ✔    |     ✔     |\n| clear             |     ✔      |       ✔       |   ✔    |     ✔     |\n| skipEvents        |     ✔      |       ✔       |   ✔    |     ✔     |\n| attach            |     ✔      |       ✔       |   ✔    |     ✔     |\n| detach            |     ✔      |       ✔       |   ✔    |     ✔     |\n| press             |     ✔      |       ✔       |   ✔    |     ✔     |\n| release           |     ✔      |       ✔       |   ✔    |     ✔     |\n| click             |     ✔      |       ✔       |   ✔    |     ✔     |\n| pressing          |     ✔      |       ✔       |   ✔    |     ✔     |\n| hold              |     ✔      |       ✔       |   ✔    |     ✔     |\n| holding           |     ✔      |       ✔       |   ✔    |     ✔     |\n| step              |     ✔      |       ✔       |   ✔    |     ✔     |\n| hasClicks         |     ✔      |       ✔       |   ✔    |     ✔     |\n| getClicks         |     ✔      |       ✔       |   ✔    |     ✔     |\n| getSteps          |     ✔      |       ✔       |   ✔    |     ✔     |\n| releaseHold       |     ✔      |       ✔       |   ✔    |     ✔     |\n| releaseStep       |     ✔      |       ✔       |   ✔    |     ✔     |\n| releaseHoldStep   |     ✔      |       ✔       |   ✔    |     ✔     |\n| waiting           |     ✔      |       ✔       |   ✔    |     ✔     |\n| busy              |     ✔      |       ✔       |   ✔    |     ✔     |\n| action            |     ✔      |       ✔       |   ✔    |     ✔     |\n| getAction         |     ✔      |       ✔       |   ✔    |     ✔     |\n| timeout           |     ✔      |       ✔       |   ✔    |     ✔     |\n| pressFor          |     ✔      |       ✔       |   ✔    |     ✔     |\n| holdFor           |     ✔      |       ✔       |   ✔    |     ✔     |\n| stepFor           |     ✔      |       ✔       |   ✔    |     ✔     |\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eТаблица функций энкодера\u003c/summary\u003e\n\n|                | VirtEncoder | Encoder | VirtEncButton | EncButton |\n| -------------- | :---------: | :-----: | :-----------: | :-------: |\n| readEnc        |             |         |               |     ✔     |\n| initEnc        |      ✔      |    ✔    |       ✔       |     ✔     |\n| setEncReverse  |      ✔      |    ✔    |       ✔       |     ✔     |\n| setEncType     |      ✔      |    ✔    |       ✔       |     ✔     |\n| setEncISR      |      ✔      |    ✔    |       ✔       |     ✔     |\n| clear          |      ✔      |    ✔    |       ✔       |     ✔     |\n| turn           |      ✔      |    ✔    |       ✔       |     ✔     |\n| dir            |      ✔      |    ✔    |       ✔       |     ✔     |\n| tickRaw        |      ✔      |    ✔    |       ✔       |     ✔     |\n| pollEnc        |      ✔      |    ✔    |       ✔       |     ✔     |\n| counter        |      ✔      |    ✔    |       ✔       |     ✔     |\n| setFastTimeout |             |         |       ✔       |     ✔     |\n| turnH          |             |         |       ✔       |     ✔     |\n| fast           |             |         |       ✔       |     ✔     |\n| right          |             |         |       ✔       |     ✔     |\n| left           |             |         |       ✔       |     ✔     |\n| rightH         |             |         |       ✔       |     ✔     |\n| leftH          |             |         |       ✔       |     ✔     |\n| action         |             |         |       ✔       |     ✔     |\n| getAction      |             |         |       ✔       |     ✔     |\n| timeout        |             |         |       ✔       |     ✔     |\n| attach         |             |         |       ✔       |     ✔     |\n| detach         |             |         |       ✔       |     ✔     |\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eVirtButton\u003c/summary\u003e\n\n```cpp\n// ================ НАСТРОЙКИ ================\n// установить таймаут удержания, умолч. 600 (макс. 4000 мс)\nvoid setHoldTimeout(uint16_t tout);\n\n// установить таймаут импульсного удержания, умолч. 200 (макс. 4000 мс)\nvoid setStepTimeout(uint16_t tout);\n\n// установить таймаут ожидания кликов, умолч. 500 (макс. 4000 мс)\nvoid setClickTimeout(uint16_t tout);\n\n// установить таймаут антидребезга, умолч. 50 (макс. 255 мс)\nvoid setDebTimeout(uint8_t tout);\n\n// установить время таймаута, умолч. 1000 (макс. 4000 мс)\nvoid setTimeout(const uint16_t tout);\n\n// установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND)\n// умолч. HIGH, то есть true - кнопка нажата\nvoid setBtnLevel(bool level);\n\n// подключить функцию-обработчик событий\nvoid attach(void (*handler)());\n\n// отключить функцию-обработчик событий\nvoid detach();\n\n// ================== СБРОС ==================\n// сбросить системные флаги (принудительно закончить обработку)\nvoid reset();\n\n// принудительно сбросить флаги событий\nvoid clear(bool resetTout = false);\n\n// игнорировать все события до отпускания кнопки\nvoid skipEvents();\n\n// ================ ОБРАБОТКА ================\n// обработка кнопки значением\nbool tick(bool s);\n\n// обработка виртуальной кнопки как одновременное нажатие двух других кнопок\nbool tick(VirtButton\u0026 b0, VirtButton\u0026 b1);\n\n// кнопка нажата в прерывании кнопки\nvoid pressISR();\n\n// обработка кнопки без сброса событий и вызова коллбэка\nbool tickRaw(bool s);\n\n// ================== ОПРОС ==================\n// кнопка нажата [событие]\nbool press();\nbool press(uint8_t clicks);\n\n// кнопка отпущена (в любом случае) [событие]\nbool release();\nbool release(uint8_t clicks);\n\n// клик по кнопке (отпущена без удержания) [событие]\nbool click();\nbool click(uint8_t clicks);\n\n// кнопка зажата (между press() и release()) [состояние]\nbool pressing();\nbool pressing(uint8_t clicks);\n\n// кнопка была удержана (больше таймаута) [событие]\nbool hold();\nbool hold(uint8_t clicks);\n\n// кнопка удерживается (больше таймаута) [состояние]\nbool holding();\nbool holding(uint8_t clicks);\n\n// импульсное удержание [событие]\nbool step();\nbool step(uint8_t clicks);\n\n// зафиксировано несколько кликов [событие]\nbool hasClicks();\nbool hasClicks(uint8_t clicks);\n\n// кнопка отпущена после удержания [событие]\nbool releaseHold();\nbool releaseHold(uint8_t clicks);\n\n// кнопка отпущена после импульсного удержания [событие]\nbool releaseStep();\nbool releaseStep(uint8_t clicks);\n\n// кнопка отпущена после удержания или импульсного удержания [событие]\nbool releaseHoldStep();\nbool releaseHoldStep(uint8_t clicks);\n\n// получить количество кликов\nuint8_t getClicks();\n\n// получить количество степов\nuint16_t getSteps();\n\n// кнопка ожидает повторных кликов (между click() и hasClicks()) [состояние]\nbool waiting();\n\n// идёт обработка (между первым нажатием и после ожидания кликов) [состояние]\nbool busy();\n\n// было действие с кнопки, вернёт код события [событие]\nuint16_t action();\nEBAction getAction();\n\n// ================== ВРЕМЯ ==================\n// после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [событие]\nbool timeout();\n\n// после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [состояние]\nbool timeoutState();\n\n// время, которое кнопка удерживается (с начала нажатия), мс\nuint16_t pressFor();\n\n// кнопка удерживается дольше чем (с начала нажатия), мс [состояние]\nbool pressFor(uint16_t ms);\n\n// время, которое кнопка удерживается (с начала удержания), мс\nuint16_t holdFor();\n\n// кнопка удерживается дольше чем (с начала удержания), мс [состояние]\nbool holdFor(uint16_t ms);\n\n// время, которое кнопка удерживается (с начала степа), мс\nuint16_t stepFor();\n\n// кнопка удерживается дольше чем (с начала степа), мс [состояние]\nbool stepFor(uint16_t ms);\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eVirtEncoder\u003c/summary\u003e\n\n```cpp\n// ==================== НАСТРОЙКИ ====================\n// инвертировать направление энкодера (умолч. 0)\nvoid setEncReverse(bool rev);\n\n// установить тип энкодера (EB_STEP4_LOW, EB_STEP4_HIGH, EB_STEP2, EB_STEP1)\nvoid setEncType(uint8_t type);\n\n// использовать обработку энкодера в прерывании\nvoid setEncISR(bool use);\n\n// инициализация энкодера\nvoid initEnc(bool e0, bool e1);\n\n// инициализация энкодера совмещённым значением\nvoid initEnc(int8_t v);\n\n// сбросить флаги событий\nvoid clear();\n\n// ====================== ОПРОС ======================\n// был поворот [событие]\nbool turn();\n\n// направление энкодера (1 или -1) [состояние]\nint8_t dir();\n\n// счётчик\nint32_t counter;\n\n// ==================== ОБРАБОТКА ====================\n// опросить энкодер в прерывании. Вернёт 1 или -1 при вращении, 0 при остановке\nint8_t tickISR(bool e0, bool e1);\nint8_t tickISR(int8_t state);\n\n// опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановке\nint8_t tick(bool e0, bool e1);\nint8_t tick(int8_t state);\nint8_t tick();  // сама обработка в прерывании\n\n// опросить энкодер без сброса события поворота. Вернёт 1 или -1 при вращении, 0 при остановке\nint8_t tickRaw(bool e0, bool e1);\nint8_t tickRaw(int8_t state);\nint8_t tickRaw();  // сама обработка в прерывании\n\n// опросить энкодер без установки флагов на поворот (быстрее). Вернёт 1 или -1 при вращении, 0 при остановке\nint8_t pollEnc(bool e0, bool e1);\nint8_t pollEnc(int8_t state);\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eVirtEncButton\u003c/summary\u003e\n\n- Доступны функции из `VirtButton`\n- Доступны функции из `VirtEncoder`\n\n```cpp\n// ================== НАСТРОЙКИ ==================\n// установить таймаут быстрого поворота, мс\nvoid setFastTimeout(uint8_t tout);\n\n// сбросить флаги энкодера и кнопки\nvoid clear(bool resetTout = false);\n\n// ==================== ОПРОС ====================\n// ЛЮБОЙ поворот энкодера [событие]\nbool turn();\n\n// нажатый поворот энкодера [событие]\nbool turnH();\n\n// быстрый поворот энкодера [состояние]\nbool fast();\n\n// ненажатый поворот направо [событие]\nbool right();\n\n// ненажатый поворот налево [событие]\nbool left();\n\n// нажатый поворот направо [событие]\nbool rightH();\n\n// нажатый поворот налево [событие]\nbool leftH();\n\n// было действие с кнопки или энкодера, вернёт код события [событие]\nuint16_t action();\nEBAction getAction();\n\n// ==================== ОБРАБОТКА ====================\n// обработка в прерывании (только энкодер). Вернёт 0 в покое, 1 или -1 при повороте\nint8_t tickISR(bool e0, bool e1);\nint8_t tickISR(int8_t e01);\n\n// обработка энкодера и кнопки\nbool tick(bool e0, bool e1, bool btn);\nbool tick(int8_t e01, bool btn);\nbool tick(bool btn);  // энкодер в прерывании\n\n// обработка энкодера и кнопки без сброса флагов и вызова коллбэка\nbool tickRaw(bool e0, bool e1, bool btn);\nbool tickRaw(int8_t e01, bool btn);\nbool tickRaw(bool btn);  // энкодер в прерывании\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eButton\u003c/summary\u003e\n\n- Доступны функции из `VirtButton`\n- Режим кнопки по умолчанию - `LOW`\n\n```cpp\nButton;\nButton(uint8_t pin);                // с указанием пина\nButton(uint8_t npin, uint8_t mode); // + режим работы (умолч. INPUT_PULLUP)\nButton(uint8_t npin, uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW)\n```\n```cpp\n// указать пин и его режим работы\nvoid init(uint8_t npin, uint8_t mode);\n\n// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel\nbool read();\n\n// функция обработки, вызывать в loop\nbool tick();\n\n// обработка кнопки без сброса событий и вызова коллбэка\nbool tickRaw();\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eButtonT\u003c/summary\u003e\n\n- Доступны функции из `VirtButton`\n- Режим кнопки по умолчанию - `LOW`\n\n```cpp\nButtonT\u003cuint8_t pin\u003e;                 // с указанием пина\nButtonT\u003cuint8_t pin\u003e (uint8_t mode);  // + режим работы (умолч. INPUT_PULLUP)\nButtonT\u003cuint8_t pin\u003e (uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW)\n```\n```cpp\n// указать режим работы\nvoid init(uint8_t mode);\n\n// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel\nbool read();\n\n// функция обработки, вызывать в loop\nbool tick();\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eEncoder\u003c/summary\u003e\n\n- Доступны функции из `VirtEncoder`\n\n```cpp\nEncoder;\nEncoder(uint8_t encA, uint8_t encB);                // с указанием пинов\nEncoder(uint8_t encA, uint8_t encB, uint8_t mode);  // + режим работы (умолч. INPUT)\n```\n```cpp\n// указать пины и их режим работы\nvoid init(uint8_t encA, uint8_t encB, uint8_t mode);\n\n// функция обработки для вызова в прерывании энкодера\nint8_t tickISR();\n\n// функция обработки для вызова в loop\nint8_t tick();\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eEncoderT\u003c/summary\u003e\n\n- Доступны функции из `VirtEncoder`\n\n```cpp\nEncoderT\u003cuint8_t encA, uint8_t encB\u003e;                 // с указанием пинов\nEncoderT\u003cuint8_t encA, uint8_t encB\u003e (uint8_t mode);  // + режим работы (умолч. INPUT)\n```\n```cpp\n// указать режим работы пинов\nvoid init(uint8_t mode);\n\n// функция обработки для вызова в прерывании энкодера\nint8_t tickISR();\n\n// функция обработки для вызова в loop\nint8_t tick();\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eEncButton\u003c/summary\u003e\n\n- Доступны функции из `VirtButton`\n- Доступны функции из `VirtEncoder`\n- Доступны функции из `VirtEncButton`\n\n```cpp\nEncButton;\n\n// настроить пины (энк, энк, кнопка)\nEncButton(uint8_t encA, uint8_t encB, uint8_t btn);\n\n// настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки)\nEncButton(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);\n```\n```cpp\n// настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки)\nvoid init(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);\n\n// функция обработки для вызова в прерывании энкодера\nint8_t tickISR();\n\n// функция обработки, вызывать в loop\nbool tick();\n\n// прочитать значение кнопки с учётом setBtnLevel\nbool readBtn();\n\n// прочитать значение энкодера\nint8_t readEnc();\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eEncButtonT\u003c/summary\u003e\n\n- Доступны функции из `VirtButton`\n- Доступны функции из `VirtEncoder`\n- Доступны функции из `VirtEncButton`\n\n```cpp\n// с указанием пинов\nEncButtonT\u003cuint8_t encA, uint8_t encB, uint8_t btn\u003e;\n\n// + режим работы пинов, уровень кнопки\nEncButtonT\u003cuint8_t encA, uint8_t encB, uint8_t btn\u003e (uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);\n```\n```cpp\n// настроить режим работы пинов, уровень кнопки\nvoid init(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);\n\n// функция обработки для вызова в прерывании энкодера\nint8_t tickISR();\n\n// функция обработки, вызывать в loop\nbool tick();\n\n// прочитать значение кнопки\nbool readBtn();\n\n// прочитать значение энкодера\nint8_t readEnc();\n```\n\u003c/details\u003e\n\n\u003ca id=\"tick\"\u003e\u003c/a\u003e\n\n### Обработка и опрос\nВо всех библиотеках есть общая **функция обработки** (тикер `tick`), которая получает текущий сигнал с кнопки и энкодера\n- Эту функцию нужно однократно вызывать в основном цикле программы (для виртуальных - с передачей значения)\n- Функция возвращает `true` при наступлении события (для энкодера - `1` или `-1` при повороте, `0` при его отсутствии. Таким образом поворот в любую сторону расценивается как `true`)\n- Есть отдельные функции для вызова в прерывании, они имеют суффикс `ISR`, см. документацию ниже\n\nБиблиотека обрабатывает сигнал внутри этой функции, результат можно получить из **функций опроса** событий. Они бывают двух типов:\n- `[событие]` - функция вернёт `true` однократно при наступлении события. Сбросится после следующего вызова функции обработки (например клик, поворот энкодера). За исключением события `timeout`\n- `[состояние]` - функция возвращает `true`, пока активно это состояние (например кнопка удерживается)\n\nДля простоты восприятия функцию обработки нужно размещать в начале цикла, а опросы делать ниже:\n```cpp\nvoid loop() {\n  btn.tick();   // опрос\n\n  if (btn.click()) Serial.println(\"click\"); // однократно выведет при клике\n  if (btn.click()) Serial.println(\"click\"); // тот же клик!\n}\n```\n\u003e В отличие от предыдущих версий библиотеки, функции опроса сбрасываются не внутри себя, а *внутри функции обработки*. Таким образом в примере выше при клике по кнопке в порт дважды выведется сообщение `click()`. Это позволяет использовать функции опроса по несколько раз за текущую итерацию цикла для создания сложной логики работы программы.\n\n#### Несколько функций обработки\nПо очевидным причинам нельзя вызывать функцию обработки больше одного раза за цикл - каждый следующий вызов сбросит события от предыдущего и код будет работать некорректно. Вот так - нельзя:\n```cpp\n// так нельзя\nvoid loop() {\n  btn.tick();\n  if (btn.click()) ...\n\n  // ....\n\n  btn.tick();\n  if (btn.hold()) ...\n}\n```\n\nЕсли очень нужно попасть в глухой цикл и опрашивать там кнопку, то вот так - можно:\n```cpp\n// так можно\nvoid loop() {\n  btn.tick();\n  if (btn.click()) ...\n\n  while (true) {\n    btn.tick();\n    if (btn.hold()) ...\n    if (btn.click()) break;\n  }\n}\n```\n\nЕсли библиотека используется с подключенным обработчиком событий `attach()` (см. ниже), то можно вызывать `tick()` где угодно и сколько угодно раз, события будут обработаны в обработчике:\n```cpp\n// так можно\nvoid cb() {\n  switch (btn.action()) {\n    // ...\n  }\n}\n\nvoid setup() {\n  btn.attach(cb);\n}\n\nvoid loop() {\n  btn.tick();\n  // ...\n  btn.tick();\n  // ...\n  btn.tick();\n}\n```\n\n#### \"Загруженная\" программа\nБиблиотека EncButton - **асинхронная**: она не ждёт, пока закончится обработка кнопки, а позволяет программе выполняться дальше. Это означает, что для корректной работы библиотеки основной цикл программы должен выполняться как можно быстрее и не содержать задержек и других \"глухих\" циклов внутри себя. Для обеспечения правильной обработки кнопки не рекомендуется иметь в основном цикле задержки длительностью более 50-100 мс. Несколько советов:\n- Новичкам: изучить цикл уроков [как написать скетч](https://alexgyver.ru/lessons/how-to-sketch/)\n  - Писать асинхронный код в `loop()`\n  - Любую синхронную конструкцию на `delay()` можно сделать асинхронной при помощи `millis()`\n  - Если в программе *каждая* итерация главного цикла выполняется дольше 50-100мс - в большинстве случаев программа написана неправильно, за исключением каких-то особых случаев\n- Подключить кнопку на аппаратное прерывание (см. ниже)\n- Избегать выполнения \"тяжёлых\" участков кода, пока идёт обработка кнопки, например поместив их в условие `if (!button.busy()) { тяжёлый код }`\n- Если оптимизировать основной цикл невозможно - вызывать тикер в другом \"потоке\" и использовать функцию-обработчик:\n  - В прерывании таймера с периодом ~50мс или чаще\n  - На другом ядре (например ESP32)\n  - В другом таске FreeRTOS\n  - Внутри `yield()` (внутри `delay()`)\n\n#### Раздельная обработка\n\u003e Имеет смысл только при ручном опросе событий! При подключенной функции-обработчике достаточно вызывать обычный `tick()` между тяжёлыми участками программы\n\nТакже в загруженной программе можно разделить обработку и сброс событий: вместо `tick()` использовать `tickRaw()` между тяжёлыми участками кода и ручной сброс `clear()`. Порядок следующий:\n- Опросить действия (click, press, turn...)\n- Вызвать `clear()`\n- Вызывать `tickRaw()` между тяжёлыми участками кода\n\n```cpp\nvoid loop() {\n  if (btn.click()) ...\n  if (btn.press()) ...\n  if (btn.step()) ...\n\n  btn.clear();\n\n  // ...\n  btn.tickRaw();\n  // ...\n  btn.tickRaw();\n  // ...\n  btn.tickRaw();\n  // ...\n}\n```\nЭто позволит опрашивать кнопку/энкодер в не очень хорошо написанной программе, где основной цикл завален тяжёлым кодом. Внутри `tickRaw()` накапливаются события, которые раз в цикл разбираются, а затем вручную сбрасываются.\n\n\u003e В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события `releaseXxx`\n\n#### Обработка внутри delay\nЕсли сложно избавиться от `delay()` внутри главного цикла программы, то на некоторых платформах можно поместить свой код внутри него. Таким образом можно получить даже обработку энкодера в цикле с дилеями без использования прерываний:\n```cpp\n// вставка кода в delay\nvoid yield() {\n  eb.tickRaw();\n}\n\nvoid loop() {\n  if (eb.click()) ...\n  if (btn.turn()) ...\n\n  eb.clear();\n\n  // ...\n  delay(10);\n  // ...\n  delay(50);\n  // ...\n}\n```\n\n\u003e В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события `releaseXxx`\n\n#### Обработка кнопки\nБиблиотека обрабатывает кнопку следующим образом:\n- Нажатие с программным подавлением дребезга (удержание дольше таймаута deb), результат - событие `press`, состояния `pressing` и `busy`\n- Удержание дольше таймаута удержания hold - событие `hold`, состояние `holding`\n- Удержание дольше таймаута удержания hold + таймаута степ - импульсное событие `step`, срабатывает с периодом step пока кнопка удерживается\n- Отпускание кнопки, результат - событие `release`, снятие состояний `pressing` и `holding`\n  - Отпускание до таймаута удержания - событие `click`\n  - Отпускание после удержания - событие `releaseHold`\n  - Отпускание после импульсного удержания - событие `releaseStep`\n  - События `releaseHold` и `releaseStep` взаимоисключающие, если кнопка была удержана до `step` - `releaseHold` уже не сработает\n- Ожидание нового клика в течение таймаута click, состояние `waiting`\n- Если нового клика нет - снятие состоятия `busy`, обработка закончена\n  - Если кнопка снова нажата - обработка нового клика\n  - Счётчик кликов `getClicks()` сбрасывается после событий `releaseHold`/`releaseStep`, которые проверяют предварительные клики. В общем обработчике `action()` это события `EB_REL_HOLD_C` или `EB_REL_STEP_C`\n  - Количество сделанных кликов нужно проверять по событию `hasClicks`, а также можно опросить внутри почти всех событий кнопки, которые идут до `releaseXxx`\n- Если ожидается `timeout` - событие timeout периодом из `setTimeout`\n- Обработка кнопки в прерывании сообщает библиотеке о факте нажатия, вся остальная обработка выполняется штатно в `tick()`\n\n\u003e Отличие `click(n)` от `hasClicks(n)`: `click(n)` вернёт `true` в любом случае при совпадении количества кликов, даже если будет сделано больше кликов. `hasClicks(n)` вернёт `true` только в том случае, если было сделано ровно указанное количество кликов и больше кликов не было!\n\n\u003e Лучше один раз увидеть, чем сто раз прочитать. Запусти пример demo и понажимай на кнопку, или попробуй [онлайн-симуляцию в Wokwi](https://wokwi.com/projects/373591584298469377)\n\n##### Click\n![click](/doc/click.gif)  \n\n##### Hold\n![hold](/doc/hold.gif)  \n\n##### Step\n![step](/doc/step.gif)  \n\nОнлайн-симуляция доступна [здесь](https://wokwi.com/projects/373591584298469377)\n\n#### Обработка энкодера\n- \"Быстрым\" поворотом считается поворот, совершённый менее чем за настроенный таймаут от предыдущего поворота\n- Обработанные в прерывании повороты становятся активными (вызывают события) после вызова `tick()`\n- Доступ к счётчику энкодера `counter` - это публичная переменная класса, можно делать с ней всё что угодно:\n```cpp\nSerial.println(eb.counter); // читать\neb.counter += 1234;         // менять\neb.counter = 0;             // обнулять\n```\n\n#### Обработка энкодера с кнопкой\n- Поворот энкодера при зажатой кнопке снимает и блокирует все последующие события и клики, за исключением события `release`. Состояния нажатой кнопки не изменяются\n- Поворот энкодера также влияет на системный таймаут (функция `timeout()`) - сработает через указанное время после поворота энкодера\n- Счётчик кликов доступен при нажатом повороте: несколько кликов, зажатие кнопки, поворот\n\n\u003ca id=\"preclicks\"\u003e\u003c/a\u003e\n\n### Предварительные клики\nБиблиотека считает количество кликов по кнопке и некоторые функции опроса могут отдельно обрабатываться с *предварительными кликами*. Например 3 клика, затем удержание. Это очень сильно расширяет возможности одной кнопки. Есть два варианта работы с такими событиями:\n```cpp\n  // 1\n  if (btn.hold()) {\n    if (btn.getClicks() == 2) Serial.println(\"hold 2 clicks\");\n  }\n\n  // 2\n  if (btn.hold(2)) Serial.println(\"hold 2 clicks\");\n```\n\nВ первом варианте можно получить количество кликов для дальнейшей обработки вручную, а во втором - библиотека сделает это сама, если количество кликов для действия заранее известно.\n\n\u003ca id=\"btnread\"\u003e\u003c/a\u003e\n\n### Прямое чтение кнопки\nВ некоторых сценариях бывает нужно получить состояние кнопки \"здесь и сейчас\", например определить удерживается ли кнопка сразу после запуска микроконтроллера (старта программы). Функцию `tick()` нужно вызывать постоянно в цикле, чтобы шла обработка кнопки с гашением дребезга контактов и прочими расчётами, поэтому конструкция следующего вида **работать не будет**:\n```cpp\nvoid setup() {\n  btn.tick();\n  if (btn.press()) Serial.println(\"Кнопка нажата при старте\");\n}\n```\n\nДля таких сценариев помогут следующие функции, возвращают `true` если кнопка нажата:\n- `read()` для библиотек Button и ButtonT\n- `readBtn()` для библиотек EncButton и EncButtonT\n\n\u003e Опрос кнопки выполняется с учётом настроенного ранее уровня кнопки (setBtnLevel)! Вручную дополнительно инвертировать логику не нужно:\n\n```cpp\nvoid setup() {\n  // btn.setBtnLevel(LOW); // можно настроить уровень\n\n  if (btn.read()) Serial.println(\"Кнопка нажата при старте\");\n}\n```\n\n\u003ca id=\"loop\"\u003e\u003c/a\u003e\n\n### Погружение в цикл\nДопустим нужно обработать кнопку синхронно и с гашением дребезга. Например если кнопка зажата при старте микроконтроллера - получить её удержание или даже импульсное удержание внутри блока `setup`, то есть до начала выполнения основной программы. Можно воспользоваться состоянием `busy` и опрашивать кнопку из цикла:\n```cpp\nvoid setup() {\n  Serial.begin(115200);\n\n  btn.tick();\n  while (btn.busy()) {\n    btn.tick();\n    if (btn.hold()) Serial.println(\"hold\");\n    if (btn.step()) Serial.println(\"step\");\n  }\n\n  Serial.println(\"program start\");\n}\n```\nКак это работает: первый тик опрашивает кнопку, если кнопка нажата - сразу же активируется состояние busy и система попадает в цикл `while`. Внутри него продолжаем тикать и получать события с кнопки. Когда кнопка будет отпущена и сработают все события - флаг busy опустится и программа автоматически покинет цикл. Можно переписать эту конструкцию на цикл с постусловием, более красиво:\n```cpp\ndo {\n  btn.tick();\n  if (btn.hold()) Serial.println(\"hold\");\n  if (btn.step()) Serial.println(\"step\");\n} while (btn.busy());\n```\n\n\u003ca id=\"timeout\"\u003e\u003c/a\u003e\n\n### Timeout\nВ связанных с кнопкой классах (Button, EncButton) есть функция `timeout()` - она однократно вернёт `true`, если после окончания действий с кнопкой/энкодером прошло указанное в `setTimeout` время. Это можно использовать для сохранения параметров после ввода, например:\n```cpp\nvoid setup() {\n  //eb.setTimeout(1500);  // умолч. 1000\n}\nvoid loop() {\n  eb.tick();\n\n  // ...\n\n  if (eb.timeout()) {\n    // после взаимодействия с энкодером прошло 1000 мс\n    // EEPROM.put(0, settings);\n  }\n}\n```\n\nВ текущей версии обработка таймаута реализована не очень красиво ради экономии места: системный флаг таймаута активен всё время (`action` будет возвращать событие таймаута), сбрасывается в следующих случаях:\n\n- Вызов `timeout()` - данный метод однократно вернёт `true`, последующие вызовы будут возвращать `false` до нового действия\n- Автоматически сбросится после вызова обработчика, если он подключен\n- При вызове `clear(true)` - с флагом очистки таймаута\n- При вызове `reset()`\n\nЕсли нужно пробросить опрос таймаута через несколько вызовов - можно использовать `timeoutState()`, но последний вызов должен быть `timeout()`.\n\n\u003ca id=\"busy\"\u003e\u003c/a\u003e\n\n### Busy\nФункция `busy()` возвращает `true`, пока идёт обработка кнопки, т.е. пока система ожидает действий и выхода таймаутов. Это можно использовать для оптимизации кода, например избегать каких то долгих и тяжёлых частей программы на время обработки кнопки:\n```cpp\nvoid loop() {\n  eb.tick();\n\n  // ...\n\n  if (!eb.busy()) {\n    // потенциально долгий и тяжёлый код\n  }\n}\n```\n\n\u003ca id=\"actions\"\u003e\u003c/a\u003e\n\n### Получение события\nДоступно во всех классах **с кнопкой**:\n- `VirtButton`\n- `Button`\n- `VirtEncButton`\n- `EncButton`\n\nФункция `action()` при наступлении события возвращает код события (отличный от нуля, что само по себе является индикацией наличия события):\n- `EB_PRESS` - нажатие на кнопку\n- `EB_HOLD` - кнопка удержана\n- `EB_STEP` - импульсное удержание\n- `EB_RELEASE` - кнопка отпущена\n- `EB_CLICK` - одиночный клик\n- `EB_CLICKS` - сигнал о нескольких кликах\n- `EB_TURN` - поворот энкодера\n- `EB_REL_HOLD` - кнопка отпущена после удержания\n- `EB_REL_HOLD_C` - кнопка отпущена после удержания с предв. кликами\n- `EB_REL_STEP` - кнопка отпущена после степа\n- `EB_REL_STEP_C` - кнопка отпущена после степа с предв. кликами\n- `EB_TIMEOUT` - прошёл таймаут после нажатия кнопки или поворота энкодера\n\nПолученный код события можно обработать через `switch`:\n```cpp\nswitch (eb.action()) {\n  case EB_PRESS:\n    // ...\n    break;\n  case EB_HOLD:\n    // ...\n    break;\n  // ...\n}\n```\n\nЕсть аналогичная функция `getAction()`, вернёт то же самое, но с более читаемыми константами (удобно с автодополнением):\n\n- `EBAction::Press`\n- `EBAction::Hold`\n- `EBAction::Step`\n- `EBAction::Release`\n- `EBAction::Click`\n- `EBAction::Clicks`\n- `EBAction::Turn`\n- `EBAction::ReleaseHold`\n- `EBAction::ReleaseHoldClicks`\n- `EBAction::ReleaseStep`\n- `EBAction::ReleaseStepClicks`\n- `EBAction::Timeout`\n\nПолученный код события можно обработать через `switch`:\n```cpp\nswitch (eb.getAction()) {\n  case EBAction::Press:\n    // ...\n    break;\n  case EBAction::Hold:\n    // ...\n    break;\n  // ...\n}\n```\n\n\u003e Результат функций `action()`/`getAction()` сбрасывается после следующего вызова `tick()`, то есть доступен на всей текущей итерации основного цикла\n\n\u003ca id=\"optimise\"\u003e\u003c/a\u003e\n\n### Оптимизация\n#### Вес библиотеки\nДля максимального уменьшения веса библиотеки (в частности в оперативной памяти) нужно задавать тайматуы константами через define (экономия 1 байт за таймаут), отключить обработчик событий, счётчики-буферы и использовать T-класс (экономия 1 байт за пин):\n```cpp\n#define EB_NO_FOR\n#define EB_NO_CALLBACK\n#define EB_NO_COUNTER\n#define EB_NO_BUFFER\n#define EB_DEB_TIME 50     // таймаут гашения дребезга кнопки (кнопка)\n#define EB_CLICK_TIME 500  // таймаут ожидания кликов (кнопка)\n#define EB_HOLD_TIME 600   // таймаут удержания (кнопка)\n#define EB_STEP_TIME 200   // таймаут импульсного удержания (кнопка)\n#define EB_FAST_TIME 30    // таймаут быстрого поворота (энкодер)\n#define EB_TOUT_TIME 1000  // таймаут действия (кнопка и энкодер)\n#include \u003cEncButton.h\u003e\nEncButtonT\u003c2, 3, 4\u003e eb;\n```\nВ таком случае энкодер с кнопкой займёт в SRAM всего 8 байт, а просто кнопка - 5.\n\n#### Скорость выполнения\nЧтобы сократить время на проверку системных флагов событий (незначительно, но приятно) можно поместить все опросы в условие по `tick()`, так как `tick()` возвращает `true` только при наступлении **события**:\n```cpp\nvoid loop() {\n  if (eb.tick()) {\n    if (eb.turn()) ...;\n    if (eb.click()) ...;\n  }\n}\n```\n\nТакже опрос событий при помощи функции `action()` выполняется быстрее, чем ручной опрос отдельных функций событий, поэтому максимально эффективно библиотека будет работать вот в таком формате:\n```cpp\nvoid loop() {\n  if (eb.tick()) {\n    switch (eb.action()) {\n      case EB_PRESS:\n        // ...\n        break;\n      case EB_HOLD:\n        // ...\n        break;\n      // ...\n    }\n  }\n}\n```\n\nДля опроса **состояний** кнопки `pressing()`, `holding()`, `waiting()` можно поместить их вовнутрь условия по `busy()`, чтобы не опрашивать состояния пока их гарантированно нет:\n```cpp\nif (btn.busy()) {\n  if (btn.pressing())...\n  if (btn.holding())...\n  if (btn.waiting())...\n}\n```\n\n\u003ca id=\"callback\"\u003e\u003c/a\u003e\n\n### Коллбэки\nМожно подключить внешнюю функцию-обрбаотчик события, она будет вызвана при наступлении любого события. Данная возможность работает во всех классах **с кнопкой**:\n- `VirtButton`\n- `Button`\n- `VirtEncButton`\n- `EncButton`\n\n\u003e Внутри коллбэка можно получить указатель на текущий объект (который вызвал коллбэк) из переменной `void* EB_self`\n\n```cpp\nEncButton eb(2, 3, 4);\n\nvoid callback() {\n  switch (eb.action()) {\n    case EB_PRESS:\n      // ...\n      break;\n    case EB_HOLD:\n      // ...\n      break;\n    // ...\n  }\n\n  // здесь EB_self указатель на eb\n}\n\nvoid setup() {\n  eb.attach(callback);\n}\n\nvoid loop() {\n  eb.tick();\n}\n```\n\n\u003ca id=\"double\"\u003e\u003c/a\u003e\n\n### Одновременное нажатие\nБиблиотека нативно поддерживает работу с двумя одновременно нажатыми кнопками как с третьей кнопкой. Для этого нужно:\n1. Cоздать специальную кнопку `MultiButton`\n2. Передать виртуальной кнопке в обработку свои кнопки (это могут быть объекты классов `VirtButton`, `Button`, `EncButton` + их `T`-версии). **Мульти-кнопка сама опросит обе кнопки!**\n3. Опрашивать события или слушать обработчик\n\n```cpp\nButton b0(4);\nButton b1(5);\nMultiButton b2;  // 1\n\nvoid loop() {\n  b2.tick(b0, b1);  // 2\n\n  // 3\n  if (b0.click()) Serial.println(\"b0 click\");\n  if (b1.click()) Serial.println(\"b1 click\");\n  if (b2.click()) Serial.println(\"b0+b1 click\");\n}\n```\n\nБиблиотека сама \"сбросит\" лишние события с реальных кнопок, если они были нажаты вместе, за исключением события `press`. Таким образом получается полноценная третья кнопка из двух других с удобным опросом.\n\n\u003ca id=\"isr\"\u003e\u003c/a\u003e\n\n### Прерывания\n#### Энкодер\nДля обработки энкодера в загруженной программе нужно:\n- Подключить оба его пина на аппаратные прерывания по `CHANGE`\n- Установить `setEncISR(true)`\n- Вызывать в обработчике специальный тикер для прерывания\n- Основной тикер также нужно вызывать в `loop` для корреткной работы - события генерируются в основном тикере:\n```cpp\n// пример для ATmega328 и EncButton\nEncButton eb(2, 3, 4);\n\n/*\n// esp8266/esp32\nIRAM_ATTR void isr() {\n  eb.tickISR();\n}\n*/\n\nvoid isr() {\n  eb.tickISR();\n}\nvoid setup() {\n  attachInterrupt(0, isr, CHANGE);\n  attachInterrupt(1, isr, CHANGE);\n  eb.setEncISR(true);\n}\nvoid loop() {\n  eb.tick();\n}\n```\n\nПримечание: использование работы в прерывании позволяет корректно обрабатывать позицию энкодера и не пропустить новый поворот. Событие с поворотом, полученное из прерывания, станет доступно *после* вызова `tick` в основном цикле программы, что позволяет не нарушать последовательность работы основного цикла:\n- Буферизация отключена: событие `turn` активируется только один раз, независимо от количества щелчков энкодера, совершённых между двумя вызовами `tick` (щелчки обработаны в прерывании)\n- Буферизация включена: событие `turn` будет вызвано столько раз, сколько реально было щелчков энкодера, это позволяет вообще не пропускать повороты и не нагружать систему в прерывании. **Размер буфера - 5 необработанных щелчков энкодера**\n\nПримечания:\n- Функция `setEncISR` работает только в не виртуальных классах. Если он включен - основной тикер `tick` просто не опрашивает пины энкодера, что экономит процессорное время. Обработка происходит только в прерывании\n- Счётчик энкодера всегда имеет актуальное значение и может опережать буферизированные повороты в программе с большими задержками в основном цикле!\n- На разных платформах прерывания могут работать по разному (например на ESPxx - нужно добавить функции аттрибут `IRAM_ATTR`, см. документацию на свою платформу!)\n- Обработчик, подключенный в `attach()`, будет вызван из `tick()`, то есть *не из прерывания*!\n\n#### Виртуальные классы\nВ виртуальных есть тикер, в который не нужно передавать состояние энкодера, если он обрабатывается в прерывании, это позволяет не опрашивать пины в холостую. Например:\n\n```cpp\nVirtEncoder e;\n\nvoid isr() {\n    e.tickISR(digitalRead(2), digitalRead(3));\n}\nvoid setup() {\n    attachInterrupt(0, isr, CHANGE);\n    attachInterrupt(1, isr, CHANGE);\n\n    e.setEncISR(1);\n}\nvoid loop() {\n    e.tick();   // не передаём состояния пинов\n}\n```\n\n#### Кнопка\nДля обработки кнопки в прерывании нужно:\n- Подключить прерывание на **нажатие** кнопки с учётом её физического подключения и уровня:\n  - Если кнопка замыкает `LOW` - прерывание `FALLING`\n  - Если кнопка замыкает `HIGH` - прерывание `RISING`\n- Вызывать `pressISR()` в обработчике прерывания\n\n```cpp\nButton b(2);\n\n/*\n// esp8266/esp32\nIRAM_ATTR void isr() {\n  b.pressISR();\n}\n*/\n\nvoid isr() {\n  b.pressISR();\n}\nvoid setup() {\n  attachInterrupt(0, isr, FALLING);\n}\nvoid loop() {\n  b.tick();\n}\n```\n\nПримечание: кнопка обрабатывается в основном `tick()`, а функция `pressISR()` всего лишь сообщает библиотеке, что кнопка была нажата вне `tick()`. Это позволяет не пропустить нажатие кнопки, пока программа была занята чем-то другим.\n\n\u003ca id=\"array\"\u003e\u003c/a\u003e\n\n### Массив кнопок/энкодеров\nСоздать массив можно только из нешаблонных классов (без буквы `T`), потому что номера пинов придётся указать уже в рантайме далее в программе. Например:\n```cpp\nButton btns[5];\nEncButton ebs[3];\n\nvoid setup() {\n  btns[0].init(2);  // указать пин\n  btns[1].init(5);\n  btns[2].init(10);\n  // ...\n\n  ebs[0].init(11, 12, 13, INPUT);\n  ebs[1].init(14, 15, 16);\n  // ...\n}\nvoid loop() {\n  for (int i = 0; i \u003c 5; i++) btns[i].tick();\n  for (int i = 0; i \u003c 3; i++) ebs[i].tick();\n\n  if (btns[2].click()) Serial.println(\"btn2 click\");\n  // ...\n}\n```\n\n\u003ca id=\"custom\"\u003e\u003c/a\u003e\n\n### Кастомные функции\nБиблиотека поддерживает задание своих функций для чтения пина и получения времени без редактирования файлов библиотеки. Для этого нужно реализовать соответствующую функцию в своём .cpp или .ino файле:\n- `bool EB_read(uint8_t pin)` - для своей функции чтения пина\n- `void EB_mode(uint8_t pin, uint8_t mode)` - для своего аналога pinMode\n- `uint32_t EB_uptime()` - для своего аналога millis()\n\nПример:\n\n```cpp\n#include \u003cEncButton.h\u003e\n\nbool EB_read(uint8_t pin) {\n    return digitalRead(pin);\n}\n\nvoid EB_mode(uint8_t pin, uint8_t mode) {\n    pinMode(pin, mode);\n}\n\nuint32_t EB_uptime() {\n    return millis();\n}\n```\n\n\u003ca id=\"timer\"\u003e\u003c/a\u003e\n\n### Опрос по таймеру\nИногда может понадобиться вызывать `tick()` не на каждой итерации, а по таймеру. Например для виртуальной кнопки с расширителя пинов, когда чтение расширителя пинов - долгая операция, и вызывать её часто не имеет смысла. Вот так делать нельзя, события будут активны в течение периода таймера!\n```cpp\nvoid loop() {\n  // таймер на 50 мс\n  static uint32_t tmr;\n  if (millis() - tmr \u003e= 50) {\n    tmr = millis();\n    btn.tick(readSomePin());\n  }\n\n  // будет активно в течение 50 мс!!!\n  if (btn.click()) foo();\n}\n```\n\nВ данной ситуации нужно поступить так: тикать по таймеру, там же обрабатывать события и сбрасывать флаги в конце:\n```cpp\nvoid loop() {\n  // таймер на 50 мс\n  static uint32_t tmr;\n  if (millis() - tmr \u003e= 50) {\n    tmr = millis();\n    // тик\n    btn.tick(readSomePin());\n\n    // разбор событий\n    if (btn.click()) foo();\n\n    // сброс флагов\n    btn.clear();\n  }\n}\n```\n\nЛибо можно подключить обработчик и вызывать `clear()` в конце функции:\n```cpp\nvoid callback() {\n  switch (btn.action()) {\n    // ...\n  }\n\n  // сброс флагов\n  btn.clear();\n}\n\nvoid loop() {\n  // таймер на 50 мс\n  static uint32_t tmr;\n  if (millis() - tmr \u003e= 50) {\n    tmr = millis();\n    btn.tick(readSomePin());\n  }\n}\n```\n\nВ случае с вызовом по таймеру антидребезг будет частично обеспечиваться самим таймером и в библиотеке его можно отключить (поставить период 0).\n\nДля корректной работы таймаутов, состояний и счётчика кликов нужен другой подход: буферизировать прочитанные по таймеру состояния и передавать их в тик в основном цикле. Например так:\n```cpp\nbool readbuf = 0;  // буфер пина\n\nvoid loop() {\n  // таймер на 50 мс\n  static uint32_t tmr;\n  if (millis() - tmr \u003e= 50) {\n    tmr = millis();\n    readbuf = readSomePin();  // чтение в буфер\n  }\n\n  // тик из буфера\n  btn.tick(readbuf);\n\n  if (btn.click()) foo();\n}\n```\n\n### Пропуск событий\nEncButton позволяет кнопке работать в паре с энкодером для корректного отслеживания *нажатых поворотов* - при нажатом повороте события с кнопки будут пропущены, т.е. не обработается удержание и клик. Допустим кнопок несколько: они могут выполнять действия как сами по себе, так и в паре с энкодером (кнопка зажата и крутится энкодер, в программе меняется выбранное кнопкой значение). Чтобы при удержании кнопка не генерировала события (удержание, степ, клики...) можно включить пропуск событий. Он будет действовать **до отпускания кнопки**:\n\n```cpp\nif (btn.pressing() \u0026\u0026 enc.turn()) {\n  btn.skipEvents();  // зафиксирован поворот. Пропускаем события\n  // нажатый поворот\n}\n\nif (btn.click()) {\n  // просто клик\n}\n```\n\n\u003ca id=\"examples-mini\"\u003e\u003c/a\u003e\n\n### Мини примеры, сценарии\n```cpp\n// меняем значения переменных\n\n// поворот энкодера\nif (enc.turn()) {\n  // меняем с шагом 5\n  var += 5 * enc.dir();\n\n  // меняем с шагом 1 при обычном повороте, 10 при быстром\n  var += enc.fast() ? 10 : 1;\n\n  // меняем с шагом 1 при обычном повороте, 10 при нажатом\n  var += enc.pressing() ? 10 : 1;\n\n  // меняем одну переменную при повороте, другую - при нажатом повороте\n  if (enc.pressing()) var0++;\n  else var1++;\n\n  // если кнопка нажата - доступны предварительные клики\n  // Выбираем переменную для изменения по предв. кликам\n  if (enc.pressing()) {\n    switch (enc.getClicks()) {\n      case 1: var0 += enc.dir();\n        break;\n      case 2: var1 += enc.dir();\n        break;\n      case 3: var2 += enc.dir();\n        break;\n    }\n  }\n}\n\n// импульсное удержание на каждом шаге инкрементирует переменную\nif (btn.step()) var++;\n\n// смена направления изменения переменной после отпускания из step\nif (btn.step()) var += dir;\nif (btn.releaseStep()) dir = -dir;\n\n// изменение выбранной переменной при помощи step\nif (btn.step(1)) var1++;  // клик-удержание\nif (btn.step(2)) var2++;  // клик-клик-удержание\nif (btn.step(3)) var3++;  // клик-клик-клик-удержание\n\n// если держать step больше 2 секунд - инкремент +5, пока меньше - +1\nif (btn.step()) {\n  if (btn.stepFor(2000)) var += 5;\n  else var += 1;\n}\n\n// включение режима по количеству кликов\nif (btn.hasClicks()) mode = btn.getClicks();\n\n// включение режима по нескольким кликам и удержанию\nif (btn.hold(1)) mode = 1;  // клик-удержание\nif (btn.hold(2)) mode = 2;  // клик-клик-удержание\nif (btn.hold(3)) mode = 3;  // клик-клик-клик-удержание\n\n// или так\nif (btn.hold()) mode = btn.getClicks();\n\n// кнопка отпущена, смотрим сколько её удерживали\nif (btn.release()) {\n  // от 1 до 2 секунд\n  if (btn.pressFor() \u003e 1000 \u0026\u0026 btn.pressFor() \u003c= 2000) mode = 1;\n  // от 2 до 3 секунд\n  else if (btn.pressFor() \u003e 2000 \u0026\u0026 btn.pressFor() \u003c= 3000) mode = 2;\n}\n```\n\n\u003ca id=\"migrate\"\u003e\u003c/a\u003e\n\n## Гайд по миграции с v2 на v3\n### Инициализация\n```cpp\n// ВИРТУАЛЬНЫЕ\nVirtEncButton eb; // энкодер с кнопкой\nVirtButton b;     // кнопка\nVirtEncoder e;    // энкодер\n\n// РЕАЛЬНЫЕ\n// энкодер с кнопкой\nEncButton eb(enc0, enc1, btn);                    // пины энкодера и кнопки\nEncButton eb(enc0, enc1, btn, modeEnc);           // + режим пинов энкодера (умолч. INPUT)\nEncButton eb(enc0, enc1, btn, modeEnc, modeBtn);  // + режим пина кнопки (умолч. INPUT_PULLUP)\nEncButton eb(enc0, enc1, btn, modeEnc, modeBtn, btnLevel);  // + уровень кнопки (умолч. LOW)\n// шаблонный\nEncButton\u003cenc0, enc1, btn\u003e eb;                    // пины энкодера и кнопки\nEncButton\u003cenc0, enc1, btn\u003e eb(modeEnc);           // + режим пинов энкодера (умолч. INPUT)\nEncButton\u003cenc0, enc1, btn\u003e eb(modeEnc, modeBtn);  // + режим пина кнопки (умолч. INPUT_PULLUP)\nEncButton\u003cenc0, enc1, btn\u003e eb(modeEnc, modeBtn, btnLevel);  // + уровень кнопки (умолч. LOW)\n\n// кнопка\nButton b(pin);                  // пин\nButton b(pin, mode);            // + режим пина кнопки (умолч. INPUT_PULLUP)\nButton b(pin, mode, btnLevel);  // + уровень кнопки (умолч. LOW)\n// шаблонный\nButtonT\u003cpin\u003e b;                 // пин\nButtonT\u003cpin\u003e b(mode);           // + режим пина кнопки (умолч. INPUT_PULLUP)\nButtonT\u003cpin\u003e b(mode, btnLevel); // + уровень кнопки (умолч. LOW)\n\n// энкодер\nEncoder e(enc0, enc1);          // пины энкодера\nEncoder e(enc0, enc1, mode);    // + режим пинов энкодера (умолч. INPUT)\n// шаблонный\nEncoderT\u003cenc0, enc1\u003e e;         // пины энкодера\nEncoderT\u003cenc0, enc1\u003e e(mode);   // + режим пинов энкодера (умолч. INPUT)\n```\n\n### Функции\n| v2          | v3           |\n| ----------- | ------------ |\n| `held()`    | `hold()`     |\n| `hold()`    | `holding()`  |\n| `state()`   | `pressing()` |\n| `setPins()` | `init()`     |\n\n- Изменился порядок указания пинов (см. доку выше)\n- `clearFlags()` заменена на `clear()` (сбросить флаги событий) и `reset()` (сбросить системные флаги обработки, закончить обработку)\n\n### Логика работы\nВ v3 функции опроса событий (click, turn...) не сбрасываются сразу после своего вызова - они сбрасываются при следующем вызове `tick()`, таким образом сохраняют своё значение во всех последующих вызовах на текущей итерации главного цикла программы. **Поэтому `tick()` нужно вызывать только 1 раз за цикл, иначе будут пропуски действий!** Читай об этом выше.\n\n\u003ca id=\"example\"\u003e\u003c/a\u003e\n## Примеры\nОстальные примеры смотри в **examples**!\n\u003cdetails\u003e\n\u003csummary\u003eПолное демо EncButton\u003c/summary\u003e\n\n```cpp\n// #define EB_NO_FOR           // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)\n// #define EB_NO_CALLBACK      // отключить обработчик событий attach (экономит 2 байта оперативки)\n// #define EB_NO_COUNTER       // отключить счётчик энкодера (экономит 4 байта оперативки)\n// #define EB_NO_BUFFER        // отключить буферизацию энкодера (экономит 1 байт оперативки)\n\n// #define EB_DEB_TIME 50      // таймаут гашения дребезга кнопки (кнопка)\n// #define EB_CLICK_TIME 500   // таймаут ожидания кликов (кнопка)\n// #define EB_HOLD_TIME 600    // таймаут удержания (кнопка)\n// #define EB_STEP_TIME 200    // таймаут импульсного удержания (кнопка)\n// #define EB_FAST_TIME 30     // таймаут быстрого поворота (энкодер)\n// #define EB_TOUT_TIME 1000   // таймаут действия (кнопка и энкодер)\n\n#include \u003cEncButton.h\u003e\nEncButton eb(2, 3, 4);\n//EncButton eb(2, 3, 4, INPUT); // + режим пинов энкодера\n//EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP); // + режим пинов кнопки\n//EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP, LOW);  // + уровень кнопки\n\nvoid setup() {\n    Serial.begin(115200);\n\n    // показаны значения по умолчанию\n    eb.setBtnLevel(LOW);\n    eb.setClickTimeout(500);\n    eb.setDebTimeout(50);\n    eb.setHoldTimeout(600);\n    eb.setStepTimeout(200);\n\n    eb.setEncReverse(0);\n    eb.setEncType(EB_STEP4_LOW);\n    eb.setFastTimeout(30);\n\n    // сбросить счётчик энкодера\n    eb.counter = 0;\n}\n\nvoid loop() {\n    eb.tick();\n\n    // обработка поворота общая\n    if (eb.turn()) {\n        Serial.print(\"turn: dir \");\n        Serial.print(eb.dir());\n        Serial.print(\", fast \");\n        Serial.print(eb.fast());\n        Serial.print(\", hold \");\n        Serial.print(eb.pressing());\n        Serial.print(\", counter \");\n        Serial.print(eb.counter);\n        Serial.print(\", clicks \");\n        Serial.println(eb.getClicks());\n    }\n\n    // обработка поворота раздельная\n    if (eb.left()) Serial.println(\"left\");\n    if (eb.right()) Serial.println(\"right\");\n    if (eb.leftH()) Serial.println(\"leftH\");\n    if (eb.rightH()) Serial.println(\"rightH\");\n\n    // кнопка\n    if (eb.press()) Serial.println(\"press\");\n    if (eb.click()) Serial.println(\"click\");\n\n    if (eb.release()) {\n      Serial.println(\"release\");\n\n      Serial.print(\"clicks: \");\n      Serial.print(eb.getClicks());\n      Serial.print(\", steps: \");\n      Serial.print(eb.getSteps());\n      Serial.print(\", press for: \");\n      Serial.print(eb.pressFor());\n      Serial.print(\", hold for: \");\n      Serial.print(eb.holdFor());\n      Serial.print(\", step for: \");\n      Serial.println(eb.stepFor());\n    }\n\n    // состояния\n    // Serial.println(eb.pressing());\n    // Serial.println(eb.holding());\n    // Serial.println(eb.busy());\n    // Serial.println(eb.waiting());\n\n    // таймаут\n    if (eb.timeout()) Serial.println(\"timeout!\");\n\n    // удержание\n    if (eb.hold()) Serial.println(\"hold\");\n    if (eb.hold(3)) Serial.println(\"hold 3\");\n\n    // импульсное удержание\n    if (eb.step()) Serial.println(\"step\");\n    if (eb.step(3)) Serial.println(\"step 3\");\n\n    // отпущена после импульсного удержания\n    if (eb.releaseStep()) Serial.println(\"release step\");\n    if (eb.releaseStep(3)) Serial.println(\"release step 3\");\n\n    // отпущена после удержания\n    if (eb.releaseHold()) Serial.println(\"release hold\");\n    if (eb.releaseHold(2)) Serial.println(\"release hold 2\");\n\n    // проверка на количество кликов\n    if (eb.hasClicks(3)) Serial.println(\"has 3 clicks\");\n\n    // вывести количество кликов\n    if (eb.hasClicks()) {\n        Serial.print(\"has clicks: \");\n        Serial.println(eb.getClicks());\n    }\n}\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eПодключение обработчика\u003c/summary\u003e\n\n```cpp\n#include \u003cEncButton.h\u003e\nEncButton eb(2, 3, 4);\n\nvoid callback() {\n    Serial.print(\"callback: \");\n    switch (eb.action()) {\n        case EB_PRESS:\n            Serial.println(\"press\");\n            break;\n        case EB_HOLD:\n            Serial.println(\"hold\");\n            break;\n        case EB_STEP:\n            Serial.println(\"step\");\n            break;\n        case EB_RELEASE:\n            Serial.println(\"release\");\n            break;\n        case EB_CLICK:\n            Serial.println(\"click\");\n            break;\n        case EB_CLICKS:\n            Serial.print(\"clicks \");\n            Serial.println(eb.getClicks());\n            break;\n        case EB_TURN:\n            Serial.print(\"turn \");\n            Serial.print(eb.dir());\n            Serial.print(\" \");\n            Serial.print(eb.fast());\n            Serial.print(\" \");\n            Serial.println(eb.pressing());\n            break;\n        case EB_REL_HOLD:\n            Serial.println(\"release hold\");\n            break;\n        case EB_REL_HOLD_C:\n            Serial.print(\"release hold clicks \");\n            Serial.println(eb.getClicks());\n            break;\n        case EB_REL_STEP:\n            Serial.println(\"release step\");\n            break;\n        case EB_REL_STEP_C:\n            Serial.print(\"release step clicks \");\n            Serial.println(eb.getClicks());\n            break;\n    }\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    eb.attach(callback);\n}\n\nvoid loop() {\n    eb.tick();\n}\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eВсе типы кнопок\u003c/summary\u003e\n\n```cpp\n#include \u003cEncButton.h\u003e\n\nButton btn(4);\nButtonT\u003c5\u003e btnt;\nVirtButton btnv;\n\nvoid setup() {\n    Serial.begin(115200);\n}\n\nvoid loop() {\n    // Button\n    btn.tick();\n    if (btn.click()) Serial.println(\"btn click\");\n\n    // ButtonT\n    btnt.tick();\n    if (btnt.click()) Serial.println(\"btnt click\");\n\n    // VirtButton\n    btnv.tick(!digitalRead(4));  // передать логическое значение\n    if (btnv.click()) Serial.println(\"btnv click\");\n}\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eВсе типы энкодеров\u003c/summary\u003e\n\n```cpp\n#include \u003cEncButton.h\u003e\n\nEncoder enc(2, 3);\nEncoderT\u003c5, 6\u003e enct;\nVirtEncoder encv;\n\nvoid setup() {\n    Serial.begin(115200);\n}\n\nvoid loop() {\n    // опрос одинаковый для всех, 3 способа:\n\n    // 1\n    // tick вернёт 1 или -1, значит это шаг\n    if (enc.tick()) Serial.println(enc.counter);\n\n    // 2\n    // можно опросить через turn()\n    enct.tick();\n    if (enct.turn()) Serial.println(enct.dir());\n\n    // 3\n    // можно не использовать опросные функции, а получить направление напрямую\n    int8_t v = encv.tick(digitalRead(2), digitalRead(3));\n    if (v) Serial.println(v);  // выведет 1 или -1\n}\n```\n\u003c/details\u003e\n\n\u003ca id=\"versions\"\u003e\u003c/a\u003e\n## Версии\n\u003cdetails\u003e\n\u003csummary\u003eСтарые\u003c/summary\u003e\n\n- v1.1 - пуллап отдельныи методом\n- v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч)\n- v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения\n- v1.4 - обработка нажатия и отпускания кнопки\n- v1.5 - добавлен виртуальный режим\n- v1.6 - оптимизация работы в прерывании\n- v1.6.1 - подтяжка по умолчанию INPUT_PULLUP\n- v1.7 - большая оптимизация памяти, переделан FastIO\n- v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех)\n- v1.8.1 - убран FastIO\n- v1.9 - добавлена отдельная отработка нажатого поворота и запрос направления\n- v1.10 - улучшил обработку released, облегчил вес в режиме callback и исправил баги\n- v1.11 - ещё больше всякой оптимизации + настройка уровня кнопки\n- v1.11.1 - совместимость Digispark\n- v1.12 - добавил более точный алгоритм энкодера EB_BETTER_ENC\n- v1.13 - добавлен экспериментальный EncButton2\n- v1.14 - добавлена releaseStep(). Отпускание кнопки внесено в дебаунс\n- v1.15 - добавлен setPins() для EncButton2\n- v1.16 - добавлен режим EB_HALFSTEP_ENC для полушаговых энкодеров\n- v1.17 - добавлен step с предварительными кликами\n- v1.18 - не считаем клики после активации step. hold() и held() тоже могут принимать предварительные клики. Переделан и улучшен дебаунс\n- v1.18.1 - исправлена ошибка в releaseStep() (не возвращала результат)\n- v1.18.2 - fix compiler warnings\n- v1.19 - оптимизация скорости, уменьшен вес в sram\n- v1.19.1 - ещё чутка увеличена производительность\n- v1.19.2 - ещё немного увеличена производительность, спасибо XRay3D\n- v1.19.3 - сделал высокий уровень кнопки по умолчанию в виртуальном режиме\n- v1.19.4 - фикс EncButton2\n- v1.20 - исправлена критическая ошибка в EncButton2\n- v1.21 - EB_HALFSTEP_ENC теперь работает для обычного режима\n- v1.22 - улучшен EB_HALFSTEP_ENC для обычного режима\n- v1.23 - getDir() заменил на dir()\n- v2.0 \n    - Алгоритм EB_BETTER_ENC оптимизирован и установлен по умолчанию, дефайн EB_BETTER_ENC упразднён\n    - Добавлен setEncType() для настройки типа энкодера из программы, дефайн EB_HALFSTEP_ENC упразднён\n    - Добавлен setEncReverse() для смены направления энкодера из программы\n    - Добавлен setStepTimeout() для установки периода импульсного удержания, дефайн EB_STEP упразднён\n    - Мелкие улучшения и оптимизация\n\u003c/details\u003e\n\n- v3.0\n  - Библиотека переписана с нуля, с предыдущими версиями несовместима!\n    - Полностью другая инициализация объекта\n    - Переименованы: hold()-\u003eholding(), held()-\u003ehold()\n  - Оптимизация Flash памяти: библиотека весит меньше, в некоторых сценариях - на несколько килобайт\n  - Оптимизация скорости выполнения кода, в том числе в прерывании\n  - На несколько байт меньше оперативной памяти, несколько уровней оптимизации на выбор\n  - Более простое, понятное и удобное использование\n  - Более читаемый исходный код\n  - Разбитие на классы для использования в разных сценариях\n  - Новые функции, возможности и обработчики для кнопки и энкодера\n  - Буферизация энкодера в прерывании\n  - Нативная обработка двух одновременно нажимаемых кнопок как третьей кнопки\n  - Поддержка 4-х типов энкодеров\n  - Переписана документация\n  - EncButton теперь заменяет GyverLibs/VirtualButton (архивирована)\n- v3.1\n  - Расширена инициализация кнопки\n  - Убраны holdEncButton() и toggleEncButton()\n  - Добавлен turnH()\n  - Оптимизированы прерывания энкодера, добавлена setEncISR()\n  - Буферизация направления и быстрого поворота\n  - Сильно оптимизирована скорость работы action() (общий обработчик)\n  - Добавлено подключение внешней функции-обработчика событий\n  - Добавлена обработка кнопки в прерывании - pressISR()\n- v3.2\n  - Добавлены функции tickRaw() и clear() для всех классов. Позволяет проводить раздельную обработку (см. доку)\n  - Улучшена обработка кнопки с использованием прерываний\n- v3.3\n  - Добавлены функции получения времени удержания pressFor(), holdFor(), stepFor() (отключаемые)\n  - Добавлен счётчик степов getSteps() (отключаемый)\n- v3.4\n  - Доступ к счётчику кликов во время нажатого поворота\n  - Добавлена функция detach()\n- v3.5 \n  - Добавлена зависимость GyverIO (ускорен опрос пинов)\n  - Добавлена возможность задать свои функции аптайма и чтения пина\n- v3.5.2 \n  - Оптимизация\n  - Упрощена замена кастомных функций\n  - Исправлена ошибка компиляции при использовании библиотеки в нескольких .cpp файлах\n- v3.5.3\n  - Добавлено количество кликов в опрос press/release/click/pressing\n- v3.5.5 - коллбэк на базе std::function для ESP\n- v3.5.8 - добавлен метод releaseHoldStep()\n- v3.5.11 - добавлен метод skipEvents() для игнорирования событий кнопки в сложных сценариях использования\n- v3.6.0 \n  - Добавлен класс MultiButton для корректного опроса нескольких кнопок с вызовом обработчика\n  - Добавлено подключение обработчика с передачей указателя на объект\n\n\u003ca id=\"feedback\"\u003e\u003c/a\u003e\n## Баги и обратная связь\nПри нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)  \nБиблиотека открыта для доработки и ваших **Pull Request**'ов!\n\nПри сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:\n- Версия библиотеки\n- Какой используется МК\n- Версия SDK (для ESP)\n- Версия Arduino IDE\n- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде\n- Какой код загружался, какая работа от него ожидалась и как он работает в реальности\n- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgyverlibs%2Fencbutton","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgyverlibs%2Fencbutton","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgyverlibs%2Fencbutton/lists"}