[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/workflows/tg-send.yml",
    "content": "\nname: Telegram Message\non:\n  release:\n    types: [published]\njobs:\n  build:\n    name: Send Message\n    runs-on: ubuntu-latest\n    steps:\n      - name: send telegram message on push\n        uses: appleboy/telegram-action@master\n        with:\n          to: ${{ secrets.TELEGRAM_TO }}\n          token: ${{ secrets.TELEGRAM_TOKEN }}\n          disable_web_page_preview: true\n          message: |\n            ${{ github.event.repository.name }} v${{ github.event.release.tag_name }}\n            ${{ github.event.release.body }}\n            https://github.com/${{ github.repository }}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 AlexGyver\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![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&_x_tr_tl=en)  \n\n[![Foo](https://img.shields.io/badge/ПОДПИСАТЬСЯ-НА%20ОБНОВЛЕНИЯ-brightgreen.svg?style=social&logo=telegram&color=blue)](https://t.me/GyverLibs)\n\n# EncButton\n\n| ⚠️⚠️⚠️<br>**Новая версия v3 несовместима с предыдущими, смотри [документацию](#docs), [примеры](#example) и краткий [гайд по миграции](#migrate) с v2 на v3!**<br>⚠️⚠️⚠️ |\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\n|              | [uButton](https://github.com/GyverLibs/uButton) | [EncButton](https://github.com/GyverLibs/EncButton) | [EncButton opti](https://github.com/GyverLibs/EncButton) | [GyverButton](https://github.com/GyverLibs/GyverButton) | [OneButton](https://github.com/mathertel/OneButton) | [OneButtonTiny](https://github.com/mathertel/OneButton) | [JC_Button](https://github.com/JChristensen/JC_Button) | [AceButton](https://github.com/bxparks/AceButton) |\n|--------------|-------------------------------------------------|-----------------------------------------------------|----------------------------------------------------------|---------------------------------------------------------|-----------------------------------------------------|---------------------------------------------------------|--------------------------------------------------------|---------------------------------------------------|\n| Flash        | 776                                             | 1006                                                | 610                                                      | 1054                                                    | 1838                                                | 1152                                                    | 650                                                    | 1482                                              |\n| SRAM         | 6                                               | 15                                                  | 6                                                        | 23                                                      | 83                                                  | 36                                                      | 24                                                     | 45                                                |\n| Фич          | Очень много (20+)                               | Очень много (20+)                                   | Очень много (20+)                                        | Много (13)                                              | Средне (8)                                          | Мало (4)                                                | Крайне мало (6)                                        | Средне (7)                                        |\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<a id=\"install\"></a>\n\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<a id=\"info\"></a>\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> Примечание: по умолчанию в библиотеке пины энкодера настроены на `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<a id=\"docs\"></a>\n\n## Документация\n\n<a id=\"config\"></a>\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<a id=\"class\"></a>\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> *Виртуальный* - без указания пина микроконтроллера, работает напрямую с переданным значением, например для опроса кнопок-энкодеров через расширители пинов и сдвиговые регистры.\n\n> `T`-версии библиотек требуют указания пинов константами (цифрами). Номера пинов будут храниться в памяти программы, это ускоряет работу и делает код легче на 1 байт за каждый пин.\n\n> Примечание: `#include <EncButton.h>` подключает все инструменты библиотеки!\n\n<details>\n<summary>Таблица функций кнопки</summary>\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</details>\n\n<details>\n<summary>Таблица функций энкодера</summary>\n\n|                | VirtEncoder | Encoder | VirtEncButton | EncButton |\n| -------------- | :---------: | :-----: | :-----------: | :-------: |\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</details>\n\n<details>\n<summary>VirtButton</summary>\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// установить время таймаута для timeout(), умолч. 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& b0, VirtButton& 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</details>\n<details>\n<summary>VirtEncoder</summary>\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);\n\n// опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановке\nint8_t tick(bool e0, bool e1);\nint8_t tick();  // сама обработка в прерывании\n\n// опросить энкодер без сброса события поворота. Вернёт 1 или -1 при вращении, 0 при остановке\nint8_t tickRaw(bool e0, bool e1);\nint8_t tickRaw();  // сама обработка в прерывании\n\n// опросить энкодер без установки флагов на поворот (быстрее). Вернёт 1 или -1 при вращении, 0 при остановке\nint8_t pollEnc(bool e0, bool e1);\n```\n</details>\n<details>\n<summary>VirtEncButton</summary>\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);\n\n// обработка энкодера и кнопки\nbool tick(bool e0, bool e1, bool btn);\nbool tick(bool btn);  // энкодер в прерывании\n\n// обработка энкодера и кнопки без сброса флагов и вызова коллбэка\nbool tickRaw(bool e0, bool e1, bool btn);\nbool tickRaw(bool btn);  // энкодер в прерывании\n```\n</details>\n<details>\n<summary>Button</summary>\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// получить пин кнопки\nuint8_t getPin();\n```\n</details>\n<details>\n<summary>ButtonT</summary>\n\n- Доступны функции из `VirtButton`\n- Режим кнопки по умолчанию - `LOW`\n\n```cpp\nButtonT<uint8_t pin>;                 // с указанием пина\nButtonT<uint8_t pin> (uint8_t mode);  // + режим работы (умолч. INPUT_PULLUP)\nButtonT<uint8_t pin> (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</details>\n<details>\n<summary>Encoder</summary>\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// получить пин энкодера\nuint8_t getPinA();\n\n// получить пин энкодера\nuint8_t getPinB();\n```\n</details>\n<details>\n<summary>EncoderT</summary>\n\n- Доступны функции из `VirtEncoder`\n\n```cpp\nEncoderT<uint8_t encA, uint8_t encB>;                 // с указанием пинов\nEncoderT<uint8_t encA, uint8_t encB> (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</details>\n<details>\n<summary>EncButton</summary>\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// получить пин кнопки\nuint8_t getPin();\n\n// получить пин энкодера\nuint8_t getPinA();\n\n// получить пин энкодера\nuint8_t getPinB();\n```\n</details>\n<details>\n<summary>EncButtonT</summary>\n\n- Доступны функции из `VirtButton`\n- Доступны функции из `VirtEncoder`\n- Доступны функции из `VirtEncButton`\n\n```cpp\n// с указанием пинов\nEncButtonT<uint8_t encA, uint8_t encB, uint8_t btn>;\n\n// + режим работы пинов, уровень кнопки\nEncButtonT<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// настроить режим работы пинов, уровень кнопки\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</details>\n\n<a id=\"tick\"></a>\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> В отличие от предыдущих версий библиотеки, функции обработки сбрасываются не внутри себя, а *внутри тикера*. Таким образом в примере выше при клике по кнопке в порт дважды выведется сообщение `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> Имеет смысл только при ручном опросе событий! При подключенной функции-обработчике достаточно вызывать обычный `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> В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события `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> В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события `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> Отличие `click(n)` от `hasClicks(n)`: `click(n)` вернёт `true` в любом случае при совпадении количества кликов, даже если будет сделано больше кликов. `hasClicks(n)` вернёт `true` только в том случае, если было сделано ровно указанное количество кликов и больше кликов не было!\n\n> Лучше один раз увидеть, чем сто раз прочитать. Запусти пример 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<a id=\"preclicks\"></a>\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<a id=\"btnread\"></a>\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> Опрос кнопки выполняется с учётом настроенного ранее уровня кнопки (setBtnLevel)! Вручную дополнительно инвертировать логику не нужно:\n\n```cpp\nvoid setup() {\n  // btn.setBtnLevel(LOW); // можно настроить уровень\n\n  if (btn.read()) Serial.println(\"Кнопка нажата при старте\");\n}\n```\n\n<a id=\"loop\"></a>\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<a id=\"timeout\"></a>\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<a id=\"busy\"></a>\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<a id=\"actions\"></a>\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> Результат функций `action()`/`getAction()` сбрасывается после следующего вызова `tick()`, то есть доступен на всей текущей итерации основного цикла\n\n<a id=\"optimise\"></a>\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 <EncButton.h>\nEncButtonT<2, 3, 4> 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<a id=\"callback\"></a>\n\n### Коллбэки\nМожно подключить внешнюю функцию-обрбаотчик события, она будет вызвана при наступлении любого события. Данная возможность работает во всех классах **с кнопкой**:\n- `VirtButton`\n- `Button`\n- `VirtEncButton`\n- `EncButton`\n\n> Внутри коллбэка можно получить указатель на текущий объект (который вызвал коллбэк) из переменной `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<a id=\"double\"></a>\n\n### Одновременное нажатие\nБиблиотека нативно поддерживает работу с двумя одновременно нажатыми кнопками как с третьей кнопкой. Для этого нужно:\n\n#### Новый способ (v3)\n1. Cоздать специальную кнопку `MultiButton`\n2. Передать виртуальной кнопке в обработку свои кнопки (это могут быть объекты классов `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#### Старый способ\n1. Создать дополнительную виртуальную кнопку\n2. Вызвать тикеры у отдельных кнопок\n3. Вызвать тикер у виртуальной кнопки, передав в него отдельные кнопки\n\nДанный способ не работает с обработчиком - он будет вызван у отдельных кнопок в любом случае\n\n```cpp\nButton b0(4);\nButton b1(5);\nVirtButton b2;  // виртуальная - 1\n\nvoid loop() {\n    // тикеры - 2\n    b0.tick();\n    b1.tick();\n\n    // обработка одновременного нажатия двух кнопок - 3\n    b2.tick(b0, b1);\n\n    if (b0.click()) Serial.println(\"b0 click\");\n    if (b1.click()) Serial.println(\"b1 click\");\n\n    if (b2.click()) Serial.println(\"b0+b1 click\");\n    if (b2.step()) Serial.println(\"b0+b1 step\");\n}\n```\n\n<a id=\"isr\"></a>\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#### Энкодер с кнопкой\nНужно подключить все три пина на прерывания и действовать как выше - и для кнопки, и для энкодера:\n\n```cpp\nvoid eisr() {\n    eb.tickISR();\n}\nvoid bisr() {\n    eb.pressISR();\n}\n\nvoid setup() {\n    // номера прерываний \"для примера\"\n    attachInterrupt(0, eisr, CHANGE);\n    attachInterrupt(1, eisr, CHANGE);\n    eb.setEncISR(true);\n\n    attachInterrupt(2, bisr, FALLING);\n}\n\nvoid loop() {\n    eb.tick();\n}\n```\n\n<a id=\"array\"></a>\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 < 5; i++) btns[i].tick();\n  for (int i = 0; i < 3; i++) ebs[i].tick();\n\n  if (btns[2].click()) Serial.println(\"btn2 click\");\n  // ...\n}\n```\n\n<a id=\"custom\"></a>\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 <EncButton.h>\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<a id=\"timer\"></a>\n\n### Опрос по таймеру\nИногда может понадобиться вызывать `tick()` не на каждой итерации, а по таймеру. Например для виртуальной кнопки с расширителя пинов, когда чтение расширителя пинов - долгая операция, и вызывать её часто не имеет смысла. Вот так делать нельзя, события будут активны в течение периода таймера!\n```cpp\nvoid loop() {\n  // таймер на 50 мс\n  static uint32_t tmr;\n  if (millis() - tmr >= 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 >= 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 >= 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 >= 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() && enc.turn()) {\n  btn.skipEvents();  // зафиксирован поворот. Пропускаем события\n  // нажатый поворот\n}\n\nif (btn.click()) {\n  // просто клик\n}\n```\n\n<a id=\"examples-mini\"></a>\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() > 1000 && btn.pressFor() <= 2000) mode = 1;\n  // от 2 до 3 секунд\n  else if (btn.pressFor() > 2000 && btn.pressFor() <= 3000) mode = 2;\n}\n```\n\n<a id=\"migrate\"></a>\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<enc0, enc1, btn> eb;                    // пины энкодера и кнопки\nEncButton<enc0, enc1, btn> eb(modeEnc);           // + режим пинов энкодера (умолч. INPUT)\nEncButton<enc0, enc1, btn> eb(modeEnc, modeBtn);  // + режим пина кнопки (умолч. INPUT_PULLUP)\nEncButton<enc0, enc1, btn> eb(modeEnc, modeBtn, btnLevel);  // + уровень кнопки (умолч. LOW)\n\n// кнопка\nButton b(pin);                  // пин\nButton b(pin, mode);            // + режим пина кнопки (умолч. INPUT_PULLUP)\nButton b(pin, mode, btnLevel);  // + уровень кнопки (умолч. LOW)\n// шаблонный\nButtonT<pin> b;                 // пин\nButtonT<pin> b(mode);           // + режим пина кнопки (умолч. INPUT_PULLUP)\nButtonT<pin> b(mode, btnLevel); // + уровень кнопки (умолч. LOW)\n\n// энкодер\nEncoder e(enc0, enc1);          // пины энкодера\nEncoder e(enc0, enc1, mode);    // + режим пинов энкодера (умолч. INPUT)\n// шаблонный\nEncoderT<enc0, enc1> e;         // пины энкодера\nEncoderT<enc0, enc1> 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<a id=\"example\"></a>\n## Примеры\nОстальные примеры смотри в **examples**!\n<details>\n<summary>Полное демо EncButton</summary>\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 <EncButton.h>\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</details>\n<details>\n<summary>Подключение обработчика</summary>\n\n```cpp\n#include <EncButton.h>\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</details>\n<details>\n<summary>Все типы кнопок</summary>\n\n```cpp\n#include <EncButton.h>\n\nButton btn(4);\nButtonT<5> 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</details>\n<details>\n<summary>Все типы энкодеров</summary>\n\n```cpp\n#include <EncButton.h>\n\nEncoder enc(2, 3);\nEncoderT<5, 6> 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</details>\n\n<a id=\"versions\"></a>\n## Версии\n<details>\n<summary>Старые</summary>\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</details>\n\n- v3.0\n  - Библиотека переписана с нуля, с предыдущими версиями несовместима!\n    - Полностью другая инициализация объекта\n    - Переименованы: hold()->holding(), held()->hold()\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<a id=\"feedback\"></a>\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"
  },
  {
    "path": "README_EN.md",
    "content": "This is an automatic translation, may be incorrect in some places. See sources and examples!\n\n# ENCBUTTON\n\n|⚠️⚠️⚠️ <br> ** The new version of V3 is incompatible with the previous ones, see [documentation] (#docs), [examples] (# Example) and brief [migration guide] (#migrate) from v2 to v3! ** <*** <BR> ⚠️⚠️⚠️ |\n| ----------------------------------------------------------------------------------------------------------------------------------------------------- |\n\nA light and very functional library for an encoder with a button, encoder or buttons with Arduino\n- Button\n    - processing of events: pressing, releasing, click, click counter, retention, impulse retention, deduction time + preliminary clicks for all modes\n    - Program suppression of rubbish\n    - support for processing two simultaneously pressed buttons as a third button\n- Encoder\n    - Processing of events: a normal turn, pressed turn, fast turn\n    - Support of four types of incidental encoders\n    - high -precision algorithm for determining the position\n    - buffer in interruption\n- simple and understandable use\n- a huge number of opportunities and their combinations for different scenarios for using even one button\n- virtual regime (for example, for working with a pain expander)\n- optimized to work in interruption\n- The fastest reading of pins for AVR, ESP8266, ESP32 (used by gyverio)\n- Fast asynchronous algorithms of survey of actions from the button and encoder\n- rigid optimization and low weight in Flash and SRAM memory: 5 bytes SRAM (on an instance) and ~ 350 bytes Flash to process the button\n\nExamples of use scenarios:\n- Several clicks - inclusion of the regime (according to the number of clicks)\n- Several clicks + short retention - another option for turning on the mode (according to the number of clicks)\n- several clicks + holding - a gradual change in the value of the selected variable (on the number of clicks)\n- Several clicks choose a variable, encoder changes it\n- changing the step of changes in the variable during the rotation of the encoder - for example, a decrease with a closed button and an increase with rapid rotation\n- navigation by menu during the rotation of the encoder, a change in the variable during the rotation of a clamped encoder\n- full -fledged navigation by the menu when using two buttons (simultaneous retention to go to the next level, simultaneous pressing for return to the previous one)\n- And so on\n\n## compatibility\nCompatible with all arduino platforms (used arduino functions)\n\n## Content\n- [installation] (# Install)\n- [Information] (# Info)\n- [documentation] (#docs)\n  - [compilation settings] (#config)\n  - [full description of classes] (#class)\n  - [processing and survey] (#tick)\n  - [preliminary clicks] (# PRECLICS)\n  - [direct reading of the button] (#btnread)\n  - [immersion in the cycle] (#loop)\n  - [Timeout] (# Timeout)\n  - [Busy] (# Busy)\n  - [receipt of an event]\n  - [Optimization] (# Optimise)\n  - [Collback] (#callback)\n  - [Simultaneous pressing] (# Double)\n  - [interruption] (# ISR)\n  - [array of buttons/encoders] (# Array)\n  - [custom functions] (# Custom)\n  - [Timer survey] (# Timer)\n  - [Mini examples, scenarios] (# Examples-Mini)\n- [migration with v2] (#migrate)\n- [Examples] (# Example)\n- [versions] (#varsions)\n- [bugs and feedback] (#fedback)\n\n<a id=\"install\"> </a>\n## Installation\n- For work, a library is required [gyverio] (https://github.com/gyverlibs/gyverio)\n-Library can be found by the name ** encbutton ** and installed through the library manager in:\n    - Arduino ide\n    - Arduino ide v2\n    - Platformio\n- [download the library] (https://github.com/gyverlibs/encbuton/archive/refs/heads/main.zip) .Zip archive for manual installation:\n    - unpack and put in * C: \\ Program Files (X86) \\ Arduino \\ Libraries * (Windows X64)\n    - unpack and put in * C: \\ Program Files \\ Arduino \\ Libraries * (Windows X32)\n    - unpack and put in *documents/arduino/libraries/ *\n    - (Arduino id) Automatic installation from. Zip: * sketch/connect the library/add .Zip library ... * and specify downloaded archive\n- Read more detailed instructions for installing libraries [here] (https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%BD%D0%BE%BE%BE%BED0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA)\n### Update\n- I recommend always updating the library: errors and bugs are corrected in the new versions, as well as optimization and new features are added\n- through the IDE library manager: find the library how to install and click \"update\"\n- Manually: ** remove the folder with the old version **, and then put a new one in its place.“Replacement” cannot be done: sometimes in new versions, files that remain when replacing are deleted and can lead to errors!\n\n<a id=\"info\"> </a>\n\n## Information\n## encoder\n#### type of encoder\nThe library supports all 4 types of * incidental * encoders, the type can be configured using `setenctype (type)`:\n- `eb_step4_Low` - active low signal (tightening to VCC).Full period (4 phases) per click.*Set by default*\n- `eb_step4_high` - an active high signal (tightening to GND).Full period (4 phases) for one click\n- `eb_step2` - half of the period (2 phases) per click\n- `eb_step1` - a quarter of the period (1 phase) per click, as well as encoders without fixation\n\n! [Diagram] (/doc/enc_type.png)\n\n#### Recommendations\nTo work according to the Encoder with the button, I recommend these ([link] (https://ali.ski/cmpi2), [link] (https://ali.ski/szbtk)) Round Chinese modules with broken anti -ship chains(have the type `eb_step4_low` according to the classification above):\n! [Scheme] (/doc/encali.png)\n\nYou can tie an encoder yourself according to the following scheme (RC filters to the encoder channels + tightening of all pens to VCC):\n! [Scheme] (/doc/enc_scheme.png)\n\n> Note: by default in the library of Pino Encoder, you are configured by `Input` with the calculation of an external luster.If you have an encoder without lifting, you can use the internal `Input_pullup`, indicating this in the initialization of the encoder (see the documentation below).\n\n### Button\n### The level of the button\nThe button can be connected to the microcontroller in two ways and give a high or low signal when pressed.The library provides for setting up `setbtnlevel (level)`, where the level is an active button signal:\n- `High` - the button connects the VCC.Installed by default in `virt`-bibliotexes\n- `Low` - the button connects GND.Set by default in the main libraries\n\n! [Scheme] (/doc/btn_scheme.png)\n\n#### Pin\nIn diagrams with microcontrollers, the connection of the GND button with a PIN suspension to VCC is most often used.The tightening can be external (Pin mode must be put `Input`) or internal (PIN mode` Input_pullup`).In \"real\" projects, an external lifelong is recommended, becauseIt is less susceptible to interference - the internal has too high resistance.\n\n<a id=\"docs\"> </a>\n\n## Documentation\n\n<a id=\"config\"> </a>\n\n### Defaine settings\nBe up to the library\n\n`` `CPP\n\n// Disable PressFor/Holdfor/StepFor support and Stepov counter (saves 2 bytes of RAM)\n#define eb_no_for\n\n// Disable the event processor attach (saves 2 bytes of RAM)\n#define eb_no_callback\n\n// Disable the encoder counter [Virtencoder, Encoder, Encbutton] (saves 4 bytes of RAM)\n#define eb_no_counter\n\n// Disconnect the buffer of the encoder (saves 2 bytes of RAM)\n#define eb_no_buffer\n\n/*\n  Setting up timeouts for all classes\n  - replaces the timauts constants, changeCranberries from the program (setxxxtimeout ()) will not be\n  - Setting affects all buttons announced in the program/Encoders\n  - saves 1 bytes of RAM for an object for each timeout\n  - shows the default values in MS\n  - values are not limited to 4000MS, as when installing from the program (SetXXXTimeout ())\n*/\n#define eb_deb_time 50 // Timesout to extinguish the trim button (button)\n#define eb_click_time 500 // Click Stayout (button)\n#define eb_hold_time 600 // Maintenance Times (button)\n#define eb_step_time 200 // Impulse retention Timesout (button)\n#define EB_FAST_TIME 30 // TIMAUT RAM (ENCODER)\n`` `\n\n<a id=\"class\"> </a>\n\n### classes\nHow to work with the documentation: Encbutton starting with version 3.0 is several libraries (classes) for various use scenarios, they inherit each other to expand functionality.Thus, the library is a “onion”, each layer of which has access to the functions of the lower layers:\n- Basic classes:\n  - `virtbutton` - the base class of the virtual button, provides all the possibilities of the buttons\n  - `virtencoder` - the base class of the virtual encoder, determines the fact and direction of the rotation of the enkoder\n  - `virtencbutton` - the base class of a virtual encoder with a button, provides an encoder poll taking into account the button, *inherits Virtbututton and Virtencoder *\n- Main classes:\n  - `Button`,` buttont` - button class, *inherits virtbutton *\n  - `Encoder`,` Encodert` - Encoder class, *inherits virtencoder *\n  - `ENCBUTTON`,` ENCBUTTONT` - ENCODER class with a button, *inherits VirtenCbutton, Virtbutton, Virtencoder *\n\nThus, to study all the available functions of a particular library, you need to watch not only it, but also what it inherits.For example, to process the button using `Button`, you need to open below the description of` button` and `virtbutton`.\n\n> * Virtual * - without specifying a PIN of the microcontroller, works directly with the transmitted value, for example, for a survey of the enemies buttons through pain extensors and shift registers.\n\n> `T'- version of libraries require indicating Pino constants (numbers).Pino numbers will be stored in the memory of the program, this accelerates the work and makes the code easier by 1 byte for each pin.\n\n> Note: `# Include <encbutton.h>` connects all the library tools!\n\n<details>\n<summary> Table of Functions of the button </ Summary>\n\n||Virtbutton |Virtencbutton |Button |Encbutton |\n| ---------------- |: -------------------------------- |: ----------------: |:--------: |: ---------: |\n|Read |||✔ ||\n|Readbtn ||||✔ |\n|TickRaW |✔ |✔ |✔ |✔ |\n|SetHoldtimeout |✔ |✔ |✔ |✔ |\n|Setsteptimeout |✔ |✔ |✔ |✔ |\n|SetClicktimeout |✔ |✔ |✔ |✔ |\n|Setdebtimeout |✔ |✔ |✔ |✔ |\n|Setbtnlevel |✔ |✔ |✔ |✔ |\n|Pressisr |✔ |✔ |✔ |✔ |\n|Reset |✔ |✔ |✔ |✔ |\n|Clear |✔ |✔ |✔ |✔ |\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|Waiting |✔ |✔ |✔ |✔ |\n|Busy |✔ |✔ |✔ |✔ |\n|Action |✔ |✔ |✔ |✔ |\n|Timeout |✔ |✔ |✔ |✔ |\n|Pressfor |✔ |✔ |✔ |✔ |\n|Holdfor |✔ |✔ |✔ |✔ |\n|STEPFOR |✔ |✔ |✔ |✔ |\n</details>\n\n<details>\n<summary> Encoder functions table </ Summary>\n\n||Virtencoder |Encoder |Virtencbutton |Encbutton |\n| -------------- |: ----------------------------- |: -------: |: -----------------------------: |: ---------: |\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|Timeout |||✔ |✔ |\n|Attach |||✔ |✔ |\n|Detach |||✔ |✔ |\n</details>\n\n<details>\n<summary> virtbutton </summary>\n\n`` `CPP\n// ==ward\n// Set the deduction timeout, silence.600 (max. 4000 ms)\nVOID SetHoldtimeout (Uint16_T Tout);\n\n// Install the timout of impulse retention, silence.200 (max. 4000 ms)\nVOID Setsteptimeout (Uint16_T Tout);\n\n// Install the expectations of clicks, silence.500 (max. 4000 ms)\nVOID setClicktimeout (Uint16_T Tout);\n\n// Install the Timout of the Anti -Direct, silence.50 (Max. 255 ms)\nVOID Setdebtimeout (Uint8_t Tout);\n\n// set the level of the button (HIGH - button closes VCC, Low - closes GND)\n// silent.High, that is, True - the button is pressed\nVOID setbtnlevel (Bool LEVEL);\n\n// Connect the function-processor of events (type VOID F ())\nVOID attach (VOID (*handler) ());\n\n// Disconnect the event-handle function\nVOID Detach ();\n\n// ==============ward\n// throw off system flags (forcibly finish processing)\nVOID Reset ();\n\n// forcibly drop the flags of events\nVoid Clear ();\n\n// ====ward\n// button processing with value\nBool Tick (Bool S);\n\n// Processing of the virtual button as simultaneous pressing two other buttons\nBool Tick (Virtbutton & B0, Virtbutton & B1);\n\n// button pressed in the interruption of the button\nVOID Pressisr ();\n\n// Processing the button without reseting events and calling collobes\nBool Tickrad (Bool S);\n\n// ================== Poll ================================ward\n// Pressure button [event]\nBool Press ();\nBool Press (Uint8_T Clicks);\n\n// button released (in any case) [evente]\nBool Release ();\nBool Release (Uint8_T Clicks);\n\n// click on the button (released without holding) [event]\nBool click ();\nBool Click (Uint8_T Clicks);\n\n// Squeezed button (between Press () and Release ()) [condition]\nBool Pressing ();\nBool Pressing (Uint8_T Clicks);\n\n// The button was withheld (more timeout) [event]\nBool Hold ();\nBool Hold (Uint8_T Clicks);\n\n// The button is held (more timeout) [condition]\nBool Holding ();\nBool Holding (Uint8_T Clicks);\n\n// Impulse retention [event]\nBool Step ();\nBool Step (Uint8_T Clicks);\n\n// Several clicks were recorded [event]\nBool HasClicks ();\nBool HasClicks (Uint8_T Clicks);\n\n// button released after holding [Event]\nBool ReleaseHold ();\nBool ReleaseHold (Uint8_T Clicks);\n\n// Button is released after impulse retention [Event]\nBool ReleaseStep ();\nBool ReleaseStep (Uint8_T Clicks);\n\n// get the number of clicks\nuint8_t getclicks ();\n\n// Get the number of steps\nuint16_t getsteps ();\n\n// button awaits repeated clicks (between Click () and HasClicks ()) [condition]\nBool Waiting ();\n\n// processing (between the first press and after waiting for clicks) [condition]\nBool Busy ();\n\n// there was an action from the button, the event code [event] will return\nUint16_T Action ();\n\n// ==========================================================================\n// After interacting with the button (or enkoder Encbutton), the specified time has passed, ms [event]\nBool Timeout (Uint16_T MS);\n\n// The time that the button is held (from the beginning of the press), ms\nuint16_t Pressfor ();\n\n// The button is held longer than (from the beginning of pressing), ms [condition]\nBool Pressfor (Uint16_T MS);\n\n// The time that the button is held (from the beginning of retention), ms\nuint16_t holdfor ();\n\n// The button is held longer than (from the beginning of retention), ms [condition]\nBool Holdfor (Uint16_T MS);\n\n// The time that the button is held (from the beginning of the step), ms\nuint16_t stepfor ();\n\n// The button is held longer than (from the beginning of the step), ms [condition]\nBool StepFor (Uint16_T MS);\n`` `\n</details>\n<details>\n<summary> virtencoder </summary>\n\n`` `CPP\n// ===============================================\n// Invert the direction of the encoder (silence 0)\nVOID Setencreverse (Bool Rev);\n\n// Install the type of encoder (eb_step4_low, eb_step4_high, eb_step2, eb_step1)\nVOID Setenctype (Uint8_T Type);\n\n// use encoder processing in interruption\nVOID Setencisr (Bool Use);\n\n// Initialization of the encoder\nVOID Initenc (Bool E0, Bool E1);\n\n// Encoder initialization with a combined value\nVOID Initenc (int8_t v);\n\n// throw off the flags of events\nVoid Clear ();\n\n// ===================== Support ==========================\n// there was a turn [event]\nBool Turn ();\n\n// Direction of the encoder (1 or -1) [condition]\nint8_t die ();\n\n// counter\nInt32_T Counter;\n\n// ====ward\n// Interrogate the encoder in interruption.Will return 1 or -1 during rotation, 0 when stopping\nInt8_t Tickisr (Bool E0, Bool E1);\nInt8_T Tickisr (int8_t state);\n\n// Introduce the Encoder.Will return 1 or -1 during rotation, 0 when stopping\nInt8_T Tick (Bool E0, Bool E1);\nint8_t tick (int8_t state);\nint8_t tick ();// The processing itself in interruption\n\n// Introduce the Encoder without resetting the turning event.Will return 1 or -1 during rotation, 0 when stopping\nInt8_T TickRaW (Bool E0, Bool E1);\nInt8_T TickRaW (int8_t state);\nInt8_T TickRaW ();// The processing itself in interruption\n\n// Introduce the encoder without installing flags on a turn (faster).Will return 1 or -1 during rotation, 0 when stopping\nInt8_t Pollenc (Bool E0, Bool E1);\nInt8_t Pollenc (Int8_T State);\n`` `\n</details>\n<details>\n<summary> virtencbutton </summary>\n\n- Available functions from `virtbutton`\n- Available functions from `virtencoder`\n\n`` `CPP\n// ============ward\n// Install a timeout of fast turning, ms\nVOID setfasttimeout (Uint8_t Tout);\n\n// throw the flags of encoder and buttons\nVoid Clear ();\n\n// =================== Poll =================================\n// Any turn of the encoder [event]\nBool Turn ();\n\n// pressed turn of enkoder [event]\nBool Turnh ();\n\n// Fast turning of the encoder [condition]\nBool Fast ();\n\n// Open Turn to the right [event]\nBool Right ();\n\n// Non -pressed turnfrom left [event]\nBool Left ();\n\n// pressed turn to the right [event]\nBool Righth ();\n\n// pressed turn to the left [event]\nBool Lefth ();\n\n// there was an action from a button or encoder, will return the event code [event]\nUint16_T Action ();\n\n// ====ward\n// processing in interruption (only encoder).Will return 0 at rest, 1 or -1 when turning\nInt8_t Tickisr (Bool E0, Bool E1);\nInt8_T Tickisr (int8_t e01);\n\n// processing of encoder and buttons\nBool Tick (Bool E0, Bool E1, Bool BTN);\nBool Tick (Int8_t E01, Bool BTN);\nBool Tick (Bool BTN);// Encoder in interruption\n\n// Processing the encoder and buttons without discharge flags and calling collobes\nBool Tickrad (Bool E0, Bool E1, Bool BTN);\nBool Tickrade (Int8_t E01, Bool BTN);\nBool Tickrade (Bool BTN);// Encoder in interruption\n`` `\n</details>\n<details>\n<summary> Button </summary>\n\n- Available functions from `virtbutton`\n- default buttons mode - `Low`\n\n`` `CPP\nButton;\nButton (uint8_t pin);// indicating Pin\nButton (uint8_t npin, uint8_t mode);// + mode of operation (silence input_pullup)\nButton (uint8_t npin, uint8_t mode, uint8_t btnlevel);// + button level (silence)\n`` `\n`` `CPP\n// indicate the pin and its operating mode\nVOID Init (uint8_t npin, uint8_t mode);\n\n// Read the current value of the button (without debate) taking into account Setbtnlevel\nBool Read ();\n\n// processing function, call in loop\nBool Tick ();\n\n// Processing the button without reseting events and calling collobes\nBool Tickrade ();\n`` `\n</details>\n<details>\n<summary> buttont </summary>\n\n- Available functions from `virtbutton`\n- default buttons mode - `Low`\n\n`` `CPP\nButtont <uint8_t pin>;// indicating Pin\nButtont <uint8_t pin> (uint8_t mode);// + mode of operation (silence input_pullup)\nButtont <uint8_t pin> (uint8_t mode, uint8_t btnlevel);// + button level (silence)\n`` `\n`` `CPP\n// specify the operating mode\nVOID Init (Uint8_t Mode);\n\n// Read the current value of the button (without debate) taking into account Setbtnlevel\nBool Read ();\n\n// processing function, call in loop\nBool Tick ();\n`` `\n</details>\n<details>\n<summary> encoder </summary>\n\n- Available functions from `virtencoder`\n\n`` `CPP\nEncoder;\nEncoder (Uint8_t Enca, Uint8_T ENCB);// indicating Pinov\nEncoder (Uint8_t Enca, Uint8_t Encb, Uint8_t Mode);// + mode of operation (silence. Input)\n`` `\n`` `CPP\n// Indicate pins and their operating mode\nVOID Init (Uint8_t Enca, Uint8_t Encb, Uint8_t Mode);\n\n// Function of processing for calling in an interruption of encoder\nint8_t tickisr ();\n\n// Function of processing for calling in LOOP\nint8_t tick ();\n`` `\n</details>\n<details>\n<summary> encodert </summary>\n\n- Available functions from `virtencoder`\n\n`` `CPP\nEncodert <uint8_t enca, uint8_t encb>;// indicating Pinov\nEncodert <uint8_t enca, uint8_t encb> (Uint8_t Mode);// + mode of operation (silence. Input)\n`` `\n`` `CPP\n// specify the mode of operation of Pinov\nVOID Init (Uint8_t Mode);\n\n// Function of processing for calling in an interruption of encoder\nint8_t tickisr ();\n\n// Function of processing for calling in LOOP\nint8_t tick ();\n`` `\n</details>\n<details>\n<summary> encbutton </summary>\n\n- Available functions from `virtbutton`\n- Available functions from `virtencoder`\n- Available functions from `virtencbutton`\n\n`` `CPP\nENCBUTTON;\n\n// Set the Pins (ENK, ENK, button)\nENCBUTTON (UINT8_T ENCA, UINT8_T ENCB, UINT8_T BTN);\n\n// Reference Pins (ENK, ENK, button, Pinmode ENK, Pinmode button, button level)\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// Reference Pins (ENK, ENK, button, Pinmode ENK, Pinmode button, button level)\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// Function of processing for calling in an interruption of encoder\nint8_t tickisr ();\n\n// processing function, call in loop\nBool Tick ();\n\n// Read the value of the button taking into account Setbtnlevel\nBool Readbtn ();\n`` `\n</details>\n<details>\n<summary> encbuttont </summary>\n\n- Available functions from `virtbutton`\n- Available functions from `viRencoder`\n- Available functions from `virtencbutton`\n\n`` `CPP\n// indicating Pinov\nENCBUTTONT <uint8_T ENCA, UINT8_T ENCB, UINT8_T BTN>;\n\n// + Pino operation mode, button level\nENCBUTTONT <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// Configure Pino operation mode, button level\nVOID Init (Uint8_t Modeenc = Input, Uint8_t Modebtn = Input_pullup, Uint8_t Btnlevel = Low);\n\n// Function of processing for calling in an interruption of encoder\nint8_t tickisr ();\n\n// processing function, call in loop\nBool Tick ();\n\n// Read the button value\nBool Readbtn ();\n`` `\n</details>\n\n<a id=\"tick\"> </a>\n\n### Processing and Poll\nAll libraries have a general ** function of processing ** (ticker `tick`), which receives the current signal from the button and encoder\n- this function must be caused once in the main cycle of the program (for virtual - with the transmission of the meaning)\n- the function returns `true` when the event occurs (for encoder -` 1` or `-1` when turning,` 0` in its absence. Thus, the turn in any direction is regarded as `true`)\n- There are separate functions for calling in interruption, they have a suffix `isr`, see documentation below\n\nThe library processes the signal inside this function, the result can be obtained from ** functions of the survey ** events.They are of two types:\n- `[event]` - the function will return `true` once upon the event of an event.It will be reset after the next call call (for example, click, turning enncoder)\n- `[condition]` - the function returns `true`, while this condition is actively (for example, the button is held)\n\nFor simplicity of perception, the processing function must be placed at the beginning of the cycle, and polls do below:\n`` `CPP\nVOID loop () {\n  btn.tick ();// survey\n\n  if (btn.click ()) serial.println (\"click\");// will display once when clicking\n  if (btn.click ()) serial.println (\"click\");// The same click!\n}\n`` `\n> Unlike previous versions of the library, the survey functions are not reset inside themselves, but *inside the processing function *.Thus, in the example above, when clicking on the button in the port, the message `click ()` is displayed twice.This allows you to use the survey functions several times for the current iteration of the cycle to create a complex logic of the program.\n\n#### several functions of processing\nFor obvious reasons, it is impossible to cause the processing function more than once per cycle - each next call will drop events from the previous one and the code will work incorrectly.So - you can’t:\n`` `CPP\n// you can not do it this way\nVOID loop () {\n  btn.tick ();\n  if (btn.click ()) ...\n\n  // ....\n\n  btn.tick ();\n  if (btn.hold ()) ...\n}\n`` `\n\nIf you really need to get into a deaf cycle and interrogate the button there, then it can: you can:\n`` `CPP\n// so it is possible\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\nIf the library is used with an connected event handler `Attach ()` (see below), then you can call `tick ()` anywhere and as many times as you like, the events will be processed in the handler:\n`` `CPP\n// so it is possible\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### \"loaded\" program\nThe Encbutton - ** asynchronous ** library: it does not wait until the button is completed, and allows the program to be executed further.This means that for the correct operation of the library, the main cycle of the program should be performed as quickly as possible and not contain delays and other \"deaf\" cycles within itself.To ensure proper processing of the button, it is not recommended to have a main delay cycle lasting more than 50-100 ms.A few tips:\n-beginners: to study the lesson cycle [how to write a sketch] (https://alexgyver.ru/lessns/how-to-sketch/)\n  - write asynchronous code in `loop ()`\n  - Any synchronous structure on `delay ()` can be made asynchronous using `millis ()` `\n  - if in the program * each * Iteration gThe cranberries of the bay cycle are performed longer than 50-100ms-in most cases the program is written incorrectly, with the exception of some special cases\n- connect the button to the hardware interruption (see below)\n- avoid the execution of \"heavy\" sections of the code while the button is processing, for example, by placing them in the condition `If (! Button.busy ()) {heavy code}`} `\n- If it is impossible to optimize the main cycle - call the ticker in another \"stream\" and use the processor:\n  - in interruption of a timer with a period of ~ 50ms or more often\n  - on another core (for example, ESP32)\n  - In another Task Freertos\n  - inside `yield ()` (inside `delay ()`)\n\n#### Separate processing\n> It makes sense only with a manual survey of events!With a connected processing function, it is enough to call the usual `tick ()` between the heavy sections of the program\n\nAlso, in a loaded program, you can divide the processing and reset of events: instead of `tick ()` use `tickRAW ()` between heavy sections of the code and manual reset `Clear ()`.The order is as follows:\n- Surrender actions (Click, Press, Turn ...)\n- Call `Clear ()`\n- call `tickRaW ()` between heavy sections of code\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`` `\nThis will allow to interview the button/encoder in a not very well written program, where the main cycle is littered with heavy code.Inside the `Tickrade ()` Events accumulate that are dismantled once in the cycle, and then manually reset.\n\n> In this scenario, the Encoder's buffering in the interruption does not work and all events are not processed `Releasexxx`\n\n#### Processing inside Delay\nIf it is difficult to get rid of the `delay ()` inside the main cycle of the program, then on some platforms you can place your code inside it.Thus, you can even get encoder processing in a cycle with deals without using interruptions:\n`` `CPP\n// Code insertion in Delay\nVOID yield () {\n  EB.TickRAW ();\n}\n\nVOID loop () {\n  if.click ()) ...\n  if (btn.turn ()) ...\n\n  eb.clear ();\n\n  // ...\n  Delay (10);\n  // ...\n  DELAY (50);\n  // ...\n}\n`` `\n\n> In this scenario, the Encoder's buffering in the interruption does not work and all events are not processed `Releasexxx`\n\n#### button processing\nThe library processes the button as follows:\n- Pressing with software suppression of rubbish (holding longer than the Deb time), the result is the event `Press`, the state of` Pressing` and `Busy`\n- retention longer than the Hold Hold time - the event `hold`, the state` holding`\n- holding longer than the Hold + Timeshot Timeshu Taimout - a pulse event `step`, triggers with the STEP period while the button holds\n- release of the button, the result - the event `Release`, the removal of the states` Pressing` and `Holding`\n  - release to the deduction time - event `click`\n  - release after holding - event `Releasehold`\n  - release after impulse deduction - event `ReleaseStep`\n  - Events `Releasehold` and` ReleaseStep` mutually exclusive, if the button was withheld `step` -` Releasehold` will no longer work\n- Waiting for a new click during the Click timeout, the state `Waiting`\n- If there is no new click - the removal of the state of `Busy`, the processing is completed\n  - If the button is pressed again - processing a new click\n  - The Clicks Clicks `getClicks ()` is discarded after the events `Releasehold`/` Releastep`, which check the preliminary clicks.In the general processor `Action ()` Events `EB_REL_Hold_C` or` EB_REL_STEP_C`\n  - The number of clicks made must be checked by the `Hasclicks` event, and you can also interview almost all the events of the buttons that go to` Releasexxx`\n- If `Timeout` is expected - Timeout event with the specified period from the current moment\n- processing the button in the interruption informs the library about the fact of pressing, the rest of the processing is performed regularly in `Tick ()` `\n\n> The difference is `Click (n)` `Hasclicks (n)`: `Click (n)` will return `true` in any case when the number of clicks coincides, even if more clicks are made.`HasClicks (n)` will return `true` only inCranberry, if the exactly indicated number of clicks was made and there were no more clicks!\n\n> It is better to see once than read a hundred times.Launch an example of Demo and go on the button, or try [online symulation in 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\nOnline symulation is available [here] (https://wokwi.com/projects/373591584298469377)\n\n#### Encoder Processing\n- \"Fast\" turn is considered a turn committed less than tuned timaut from the previous turn\n- the turns processed in interruption become active (cause events) after calling `tick ()`\n- Access to the encoder’s counter `Counter` is a public variable of the class, you can do anything with it:\n`` `CPP\nSerial.println (eb.counter);// read\nEb.counter += 1234;// change\neb.counter = 0;// Knock\n`` `\n\n#### encoder processing with a button\n- The turning of the encoder with a clamped button removes and blocks all subsequent events and clicks, with the exception of the event `redase`.The states of the pressed button do not change\n- The turning of the encoder also affects the system timout (the `timeout ()` function) will work after the indicated time after turning the enkoder\n- the Klikov counter is available when pressed: several clicks, click of a button, turn\n\n<a id=\"preclicks\"> </a>\n\n### Preliminary clicks\nThe library considers the number of clicks by the button and some survey functions can separately be processed with *preliminary clicks *.For example, 3 clicks, then retention.This greatly expands the capabilities of one button.There are two options for working with such events:\n`` `CPP\n  // 1\n  if (btn.hold ()) {\n    if (btn.getclics () == 2) serial.println (\"Hold 2 Clicks\");\n  }\n\n  // 2\n  if (btn.hold (2)) serial.println (\"Hold 2 Clicks\");\n`` `\n\nIn the first version, you can get the number of clicks for further processing manually, and in the second - the library will do this itself if the number of clicks for action is known in advance.\n\n<a id=\"btnread\"> </a>\n\n## direct reading button\nIn some scenarios, you need to get the state of the \"here and now\" button, for example, determine whether the button is held immediately after starting the microcontroller (program start).The function `tick ()` must be called constantly in the cycle so that the button is processing with the extinguishing of the ratio of contacts and other calculations, so the design of the next type ** will not work **:\n`` `CPP\nVOID setup () {\n  btn.tick ();\n  if (btn.press ()) serial.println (\"button pressed at start\");\n}\n`` `\n\nThe following functions will help for such scenarios, return `true` if the button is pressed:\n- `read ()` for libraries button and buttont\n- `Readbtn ()` for library libraries and encbuttont\n\n> The button survey is performed taking into account the previously configured button level (Setbtnlevel)!It is not necessary to manually invert the logic:\n\n`` `CPP\nVOID setup () {\n  // btn.setbtnlevel (LOW);// you can configure the level\n\n  if (btn.read ()) serial.println (\"button pressed at start\");\n}\n`` `\n\n<a id=\"loop\"> </a>\n\n### immersion in the cycle\nSuppose you need to process the button synchronously and with the extinguishing of the rattles.For example, if the button is clamped at the start of the microcontroller, get its retention or even the pulse retention inside the `setup` unit, that is, before the start of the main program.You can use the state of `Busy` and interview the button from the cycle:\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`` `\nHow it works: the first tick interrogates the button, if the button is pressed - the state of the Busy is immediately activated and the system enters the `While` cycle.Inside it, we continue to tick and get events from the button.When the button is released and all events will work - the Busy flag will drop and the program will automatically leave the cycle.You can rewrite this design to the cycle with a postcryption, more beautiful:\n`` `CPP\ndo {\n  B.tn.tick ();\n  if (btn.hold ()) serial.println (\"Hold\");\n  if (btn.step ()) serial.println (\"step\");\n} While (btn.busy ());\n`` `\n\n<a id=\"timeout\"> </a>\n\n## Timeout\nIn the classes associated with the button (Button, Encbutton) there is a function `Timeout (Time)` - it will once return `true` if the indicated time has passed after the action with the button/encoder.This can be used to preserve parameters after entering, for example:\n`` `CPP\nVOID loop () {\n  eb.tick ();\n\n  // ...\n\n  if.timeout ()) {\n    // after interaction with encoder 1 second passed\n    // eeprom.put (0, settings);\n  }\n}\n`` `\n\n<a id=\"busy\"> </a>\n\n### Busy\nThe `Busy () function` Returns `True` while the button processing is underway, i.e.So far, the system awaits actions and the release of timeouts.This can be used to optimize the code, for example, avoid some long and heavy parts of the program during the button processing:\n`` `CPP\nVOID loop () {\n  eb.tick ();\n\n  // ...\n\n  if (! eb.busy ()) {\n    // Potentially long and difficult code\n  }\n}\n`` `\n\n<a id=\"Actions\"> </a>\n\n### Obtaining an event\nAvailable in all classes ** with the ** button:\n- `virtbutton`\n- `Button`\n- `virtencbutton`\n- `encbutton`\n\nThe function `Action ()` when the event occurs, the event is returned (different from scratch, which in itself is an indication of the existence of an event):\n- `eb_press` - click on the button\n- `eb_hold` - the button is kept\n- `eb_step` - impulse retention\n- `eb_release` - the button is released\n- `eb_click` - Single click\n- `eb_clicks` - A few click signal\n- `eb_turn` - turn of the encoder\n- `eb_rel_hold` - the button is released after holding\n- `EB_REL_HOLD_C` - the button is released after holding off the premises.clicks\n- `EB_REL_STEP` - the button is released after the step\n- `EB_REL_STEP_C` - the button is released after the step with a border.clicks\n\n> The result of the function `Action ()` is reset after the next call `tick ()`, that is, is available on the entire current iteration of the main cycle\n\nThe obtained event code can be processed through `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<a id=\"OPTIMISE\"> </a>\n\n## Optimization\n#### Library weight\nTo maximally reduce the weight of the library (in particular in RAM), you need to set the Timatui constants through Define (saving 1 bytes per timaut), turn off the events processor, counters-buffers and use the T-class (saving 1 byte per pin):\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 // Timesout to extinguish the trim button (button)\n#define eb_click_time 500 // Click Stayout (button)\n#define eb_hold_time 600 // Maintenance Times (button)\n#define eb_step_time 200 // Impulse retention Timesout (button)\n#define EB_FAST_TIME 30 // TIMAUT RAM (ENCODER)\n#include <encbutton.h>\nENCBUTTONT <2, 3, 4> EB;\n`` `\nIn this case, an encoder with a button will take only 8 bytes in SRAM, and just a button - 5.\n\n#### Fulfillment speed\nTo reduce the time for checking the system flags of events (insignificantly, but pleasant), you can place all the polls in the condition by `tick ()`, since `tick ()` returns `true` only when ** events **: the events **:\n`` `CPP\nVOID loop () {\n  if (eb.tick ()) {\n    if.turn ()) ...;\n    if.click ()) ...;\n  }\n}\n`` `\n\nAlso, a survey of events using the `Action () function is performed faster than a manual survey of individual functions of events, so the library will work in this format as efficiently as possible:\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\nFor polling ** states ** buttons `Pressing ()`, `Holding ()`, `WATING ()` You can place them inside the conditions of `BUSY ()` so as not to interview them until they are guaranteed:\n`` `CPP\nif (btn.busy ()) {\n  if (btn.pressing ()) ...\n  if (btn.holding ()) ...\n  if (btn.waiting ()) ...\n}\n`` `\n\n<a id=\"callback\"> </a>\n\n### Collback\nYou can connect the external function-shacklerCranberry, it will be caused when any event occurs.This opportunity works in all classes ** with the ** button:\n- `virtbutton`\n- `Button`\n- `virtencbutton`\n- `encbutton`\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\nVOID setup () {\n  eb.attach (callback);\n}\n\nVOID loop () {\n  eb.tick ();\n}\n`` `\n\n<a id=\"duble\"> </a>\n\n### Simultaneous pressing\nThe library supports work with two simultaneously pressed buttons as with the third button.For this you need:\n1. To make a virtual button `virtbutton`\n2. Call the processing of real buttons\n3. Pass these buttons to the virtual button to process (these can be objects of classes `virtbutton`,` button`, `encbutton` + their` t`- version)\n4. Next to interrogate events\n\n`` `CPP\nButton b0 (4);\nButton b1 (5);\nVirtbutton B2;// 1\n\nVOID loop () {\n  b0.tick ();// 2\n  b1.tick ();// 2\n  B2.Tick (B0, B1);// 3\n\n  // 4\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\nThe library itself will “drop” unnecessary events from real buttons if they were pressed together, with the exception of the event `Press`.Thus, a full -fledged third button of two others with a convenient survey is obtained.\n\n<a id=\"isr\"> </a>\n\n## interrupt\n### encoder\nFor processing an encoder in a loaded program you need:\n- Connect both of his pins to hardware interruptions by `Change`\n- install `setencisr (true)`\n- call a special ticker for interruption in the handler\n- The main ticker also needs to be called in `loop` for corrething work - events are generated in the main ticker:\n`` `CPP\n// Example for Atmega328 and 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\nNote: The use of work in the interruption allows you to correctly process the encoder position and not miss a new turn.An event with a turn obtained from an interruption will become available * after * call `Tick` in the main cycle of the program, which allows not to violate the sequence of the main cycle:\n- Buferization is disabled: the `turn` event is activated only once, regardless of the number of clicks of the encoder made between the two challenges of` tick` (clicks are processed in interruption)\n- The buffering is included: the `turn` event will be caused as many times as there were really clicks of the encoder, this allows you to not miss the turns and not load the system in the interruption.** Boofer size - 5 unprocessed clicks of encoder **\n\nNotes:\n- The `setencisr` function works only in non - virtual classes.If it is turned on, the main ticker `tick` simply does not interview Encoder's pins, which saves processor time.Processing occurs only in interruption\n- The encoder counter is always relevant and can be ahead of buffering turns in the program with large delays in the main cycle!\n- on different interrupt platforms, they can work differently (for example, on ESPXX - you need to add the functions of the atrica `` IRAM_ATTR`, see documentation on your platform!)\n- a processor connected to `Attach ()` will be called from `Tick ()`, that is, *not from interruption *!\n\n### virtual classes\nIn the virtual ones there is a ticker in which it is not necessary to transmit the state of the encoder, if it is processed in an interruption, this allows you to not interview pins in idle.For example:\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 ();// Do not transmit the states of Pinov\n}\n`` `\n\n#### Button\nTo process the button in the interruption, you need:\n- Connect an interruption on ** press ** buttons taking into account its physical connection and level:\n  - If the button is deputy`Low` - Interruption` Falling`\n  - if the button closes `high` - interruption` rising`\n- call `Pressisr ()` in the interruption processor\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\nNote: the button is processed mainly `tick ()`, and the function `Pressisr ()` just informs the library that the button was pressed outside `Tick ()`.This allows you not to miss the pressing of the button until the program was busy with something else.\n\n<a id=\"array\"> </a>\n\n### Array of buttons/Encoder\nYou can create an array only from non -step classes (without the letter `t`), because Pinov numbers will have to be indicated already in the radio further in the program.For example:\n`` `CPP\nButton btns [5];\nENCBUTTON EBS [3];\n\nVOID setup () {\n  btns [0] .init (2);// Indicate the pin\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 <5; i ++) btns [i] .Tick ();\n  for (int i = 0; i <3; i ++) EBS [i] .Tick ();\n\n  if (btns [2] .Click ()) serial.println (\"BTN2 click\");\n  // ...\n}\n`` `\n\n<a id=\"cubom\"> </a>\n\n### Caste functions\nThe library supports the task of its functions for reading PIN and getting time without editing library files.To do this, you need to implement the corresponding function in your .cpp or.\n- `bool eb_read (uint8_t pin)` - for its pine reading function\n- `void eb_mode (uint8_t pin, uint8_t mode)` - for your analogue Pinmode\n- `uint32_t eb_uptime ()` - for your analogue millis ()\n\nExample:\n\n`` `CPP\n#include <encbutton.h>\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<a id=\"timer\"> </a>\n\n### Survey by timer\nSometimes it may be necessary to call `tick ()` not on every iteration, but by the timer.For example, for a virtual button from the Pino Expand, when reading the Pino Expand is a long operation, and it often does not make sense to call it.You can’t do this, events will be active during the timer!\n`` `CPP\nVOID loop () {\n  // Timer for 50 ms\n  Static uint32_t tmr;\n  if (millis () - tmr> = 50) {\n    TMR = Millis ();\n    btn.tick (Readsomepin ());\n  }\n\n  // will be actively within 50 ms !!!\n  if (btn.click ()) foo ();\n}\n`` `\n\nIn this situation, you need to do this: tick along the timer, process events there and drop flags at the end:\n`` `CPP\nVOID loop () {\n  // Timer for 50 ms\n  Static uint32_t tmr;\n  if (millis () - tmr> = 50) {\n    TMR = Millis ();\n    // TIK\n    btn.tick (Readsomepin ());\n\n    // analysis of events\n    if (btn.click ()) foo ();\n\n    // Reset of the flags\n    btn.clear ();\n  }\n}\n`` `\n\nOr you can connect the handler and call `clear ()` at the end of the function:\n`` `CPP\nVoid callback () {\n  switch (btn.action ()) {\n    // ...\n  }\n\n  // Reset of the flags\n  btn.clear ();\n}\n\nVOID loop () {\n  // Timer for 50 ms\n  Static uint32_t tmr;\n  if (millis () - tmr> = 50) {\n    TMR = Millis ();\n    btn.tick (Readsomepin ());\n  }\n}\n`` `\n\nIn the case of calling the timer, the anti -departments will be partially provided by the timer itself and in the library it can be turned off (set the period 0).\n\nFor the correct operation of timeouts, conditions and a click counter, you need another approach: buffering the states read according to the timer and transfer them to the TIC in the main cycle.For example:\n`` `CPP\nBool Readbuf = 0;// buffer Pina\n\nVOID loop () {\n  // Timer for 50 ms\n  Static uint32_t tmr;\n  if (millis () - tmr> = 50) {\n    TMR = Millis ();\n    Readbuf = Readsomepin ();// Reading in the buffer\n  }\n\n  // tick from the buffer\n  BTN.Tick (Readbuf);\n\n  if (btn.click ()) foo ();\n}\n`` `\n\n<a id=\"EXAMPles-mini\"> </a>\n\n### Mini examples, scripts\n`` `CPP\n// Change the values of the variables\n\n// Turn of the encoder\nif (enc.turn ()) {\n  // Change in step 5\n  var += 5 * enc.dir ();\n\n  // Change in step 1 with a normal turn, 10 with fast\n  Var += ENC.FAST ()?10: 1;\n\n  // Change in step 1 with a normal turn, 10 with pressed\n  vAR += ENC.Pressing ()?10: 1;\n\n  // Change one variable when turning, the other - with a pressed turn\n  if (enc.pressing ()) Var0 ++;\n  Else Var1 ++;\n\n  // If the button is pressed - preliminary clicks are available\n  // Choose a variable for changes in the premises.Clicks\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// Impulse retention at every step is increasing the variable\nif (btn.step ()) var ++;\n\n// Change the direction of change in the variable after letting go from STEP\nif (btn.step ()) var += dir;\nif (btn.releastep ()) die = -dir;\n\n// Change the selected variable using STEP\nif (btn.step (1)) Var1 ++;// Click-holding\nif (btn.step (2)) Var2 ++;// Click-Click-holding\nif (btn.step (3)) var3 ++;// Click-Click-Click-hold\n\n// if you keep the STEP for more than 2 seconds - an incremental +5, so far less - +1\nif (btn.step ()) {\n  if (btn.stepfor (2000)) var += 5;\n  Else Var += 1;\n}\n\n// inclusion of the mode by the number of clicks\nif (btn.hasclicks ()) mode = btn.getclicks ();\n\n// inclusion of the mode by several clicks and retention\nif (btn.hold (1)) mode = 1;// Click-holding\nif (btn.hold (2)) mode = 2;// Click-Click-holding\nif (btn.hold (3)) mode = 3;// Click-Click-Click-hold\n\n// or so\nif (btn.hold ()) mode = btn.getclicks ();\n\n// Button is released, look how much it was held\nif (btn.release ()) {\n  // from 1 to 2 seconds\n  if (btn.pressfor ()> 1000 && btn.pressfor () <= 2000) mod = 1;\n  // from 2 to 3 seconds\n  Else if (BTN.PressFor ()> 2000 && BTN.PressFor () <= 3000) Mode = 2;\n}\n`` `\n\n<a id=\"migrate\"> </a>\n\n## guide for migration from v2 to v3\n## H initialization\n`` `CPP\n// virtual\nVirtencbutton eb;// Encoder with button\nVirtbutton b;// button\nVirtencoder e;// Encoder\n\n// Real\n// Encoder with button\nENCBUTTON EB (ENC0, ENC1, BTN);// Encoder Pins and buttons\nENCBUTTON EB (ENC0, ENC1, BTN, MODEENC);// + Pino Pino Encoder Pin (silence. Input)\nENCBUTTON EB (ENC0, ENC1, BTN, MODEENC, MODEBTN);// + Pin mode buttons (silent. Input_pullup)\nENCBUTTON EB (ENC0, ENC1, BTN, MODEENC, MODEBTN, BTNlevel);// + button level (silence)\n// template\nENCBUTTON <ENC0, ENC1, BTN> EB;// Encoder Pins and buttons\nENCBUTTON <ENC0, ENC1, BTN> EB (Modeenc);// + Pino Pino Encoder Pin (silence. Input)\nENCBUTTON <ENC0, ENC1, BTN> EB (Modeenc, Modebtn);// + Pin mode buttons (silent. Input_pullup)\nENCBUTTON <ENC0, ENC1, BTN> EB (Modeenc, Modebtn, Btnlevel);// + button level (silence)\n\n// button\nButton b (pin);// PIN\nButton b (PIN, Mode);// + Pin mode buttons (silent. Input_pullup)\nButton B (PIN, Mode, Btnlevel);// + button level (silence)\n// template\nButtont <pin> b;// PIN\nButtont <pin> b (mode);// + Pin mode buttons (silent. Input_pullup)\nButtont <pin> b (mode, btnlevel);// + button level (silence)\n\n// Encoder\nENCODER E (ENC0, ENC1);// Pines of Encoder\nENCODER E (ENC0, ENC1, Mode);// + Pino Pino Encoder Pin (silence. Input)\n// template\nEncodert <enc0, enc1> e;// Pines of Encoder\nEncodert <Enc0, Enc1> E (Mode);// + Pino Pino Encoder Pin (silence. Input)\n`` `\n\n### functions\n|v2 |v3 |\n| ------------- | -------------------------------------\n|`HELD ()` |`Hold ()` |\n|`Hold ()` |`Holding ()` |\n|`state ()` |`Pressing ()` |\n|`setpins ()` |`Init ()` |\n\n- The procedure for indicating Pinov has changed (see DEMPLE above)\n- `Clearflags ()` replaced by `Clear ()` (drop the flags of events) and `reset ()` (drop systemic flags of processing, finish processing)\n\n### Logic of Work\nIn the V3, the functions of an event survey (Click, Turn ...) are not discarded immediately after their call - they are discarded at the next call `Tick ()`, thus retain their meaning in all subsequent challenges on the current iteration of the main cycle of the program.** Therefore, `tick ()` needs to be called only 1 time per cycle, otherwise there will be missions of actions! ** Read about thisabove.\n\n<a id=\"EXAMPLE\"> </a>\n## Examples\nThe rest of the examples look at ** Examples **!\n<details>\n<summary> Full demo encbutton </summary>\n\n`` `CPP\n// #define eb_no_for // Disable Pressfor/Holdfor/StepFor support and Stepov counter (saves 2 bytes of RAM)\n// #define eb_no_callback // Disable the event processor Attach (saves 2 bytes of RAM)\n// #define eb_no_counter // Disable the enkoder counter (saves 4 bytes of RAM)\n// #define EB_NO_BUFFER // Disable the buffer of the encoder (saves 1 byte of RAM)\n\n// #define eb_deb_time 50 // Timesout to darebells button (button)\n// #define eb_click_time 500 // Clicks standstatics (button)\n// #define eb_hold_time 600 // Maintenance Times (button)\n// #define eb_step_time 200 // pulse retention rate (button)\n// #define EB_FAST_TIME 30 // Quick turn Timesout (Encoder)\n\n#include <encbutton.h>\nENCBUTTON EB (2, 3, 4);\n// ENCBUTTON EB (2, 3, 4, Input);// + Pino Pino mode\n// ENCBUTTON EB (2, 3, 4, Input, Input_pullup);// + button pins mode\n// ENCBUTTON EB (2, 3, 4, Input, Input_pullup, Low);// + button level\n\nVOID setup () {\n    Serial.Begin (115200);\n\n    // shows the default values\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    // throw the Encoder counter\n    eb.counter = 0;\n}\n\nVOID loop () {\n    eb.tick ();\n\n    // General rotation processing\n    if.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    // Turning rotation processing\n    if.left ()) serial.println (\"Left\");\n    if.right ()) serial.println (\"right\");\n    if.left ()) serial.println (\"Lefth\");\n    if.righth ()) serial.println (\"righth\");\n\n    // button\n    if.press ()) serial.println (\"Press\");\n    if.click ()) serial.println (\"click\");\n\n    if.release ()) {\n      Serial.println (\"Release\");\n\n      Serial.print (\"Clicks:\");\n      Serial.print (eb.getClicks ());\n      Serial.print (\", stps:\");\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    // States\n    // serial.println (eb.pressing ());\n    // serial.println (eb.holding ());\n    // serial.println (eb.busy ());\n    // serial.println (eb.waiting ());\n\n    // Timesout\n    if (eb.timeout ()) serial.println (\"Timeout!\");\n\n    // Holding\n    if.hold ()) serial.println (\"Hold\");\n    if.hold (3)) serial.println (\"Hold 3\");\n\n    // Impulse retention\n    if.step ()) serial.println (\"step\");\n    if.step (3)) serial.println (\"STEP 3\");\n\n    // released after impulse deduction\n    if (eb.releastep ()) serial.println (\"Release Step\");\n    if (eb.releastep (3)) serial.println (\"Release Step 3\");\n\n    // released after holding\n    if.releasehold ()) serial.println (\"Release Hold\");\n    if (eb.releasehold (2)) serial.println (\"Release Hold 2\");\n\n    // Check for the number of clicks\n    if.hasclicks (3)) Serial.println (\"Has 3 Clicks\");\n\n    // Bring the number of clicks\n    if.hasclicks ()) {\n        Serial.print (\"Has Clicks:\");\n        Serial.println (eb.getClicks ());\n    }\n}\n`` `\n</details>\n<details>\n<summary> connection of the handler </summary>\n\n`` `CPP\n#include <encbutton.h>\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: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</details>\n<details>\n<summary> All types of buttons </summary>\n\n`` `CPP\n#include <encbutton.h>\n\nButton BTN (4);\nButtont <5> 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));// transmit logical value\n    if (btnv.click ()) serial.println (\"btnv click\");\n}\n`` `\n</details>\n<details>\n<summary> All types of encoder </summary>\n\n`` `CPP\n#include <encbutton.h>\n\nENCODER ENC (2, 3);\nENCODERT <5, 6> ENCT;\nVirtencoder encv;\n\nVOID setup () {\n    Serial.Begin (115200);\n}\n\nVOID loop () {\n    // The survey is the same for everyone, 3 ways:\n\n    // 1\n    // Tick will return 1 or -1, then this is a step\n    if (enc.tick ()) serial.println (enc.counter);\n\n    // 2\n    // can be interviewed through turn ()\n    enct.tick ();\n    if (enct.turn ()) serial.println (enct.dir ());\n\n    // 3\n    // you can not use survey functions, but get the direction directly\n    int8_t v = encv.tick (DigitalRead (2), DigitalRead (3));\n    if (v) serial.println (v);// Derive 1 or -1\n}\n`` `\n</details>\n\n<a id=\"versions\"> </a>\n## versions\n<details>\n<summary> Old </ Summary>\n\n- V1.1 - Pullap separately by the method\n- V1.2 - You can transfer the parameter input_pullup / input (silent) to the designer\n- V1.3 - Virtual clamping of the encoder button is made into a separate function + minor improvements\n- V1.4 - Processing of pressing and releasing the button\n- v1.5 - added virtual mode\n- V1.6 - Optimization of work in interruption\n- V1.6.1 - Saching by default Input_pullup\n- V1.7 - a large memory optimization, remade Fastio\n- V1.8 - Individual tuning of the TIMUUT Maintenance of the button (was common at all)\n- v1.8.1 - removed Fastio\n- v1.9 - added a separate development of a pressed turn and a request for direction\n- V1.10 - improved ReleASDE processing, eased the weight in callback and corrected the bugs\n- V1.11 - even more than any optimization + setting button level\n- V1.11.1 - Digispark compatibility\n- V1.12 - added a more accurate algorithm of enkoder Eb_better_enc\n- V1.13 - Added experimental ENCBUTTON2\n- V1.14 - added ReleaseStep ().The release of the button is included in the debate\n- v1.15 - added Setpins () for Encbutton2\n- V1.16 - added EB_HALFSTEP_Enc mode for hemisphere encoders\n- v1.17 - added STEP with preliminary clicks\n- V1.18 - We do not consider clicks after the activation of STEP.Hold () and Held () can also take preliminary clicks.Redistributed and improved debate\n- V1.18.1 - Fixed error in ReleaseStep () (did not return the result)\n- V1.18.2 - Fix Compiler Warnings\n- V1.19 - speed optimization, reduced weight in SRAM\n- v1.19.1 - still a bit increased performance\n- v1.19.2 - not yetCranberries increased a lot of performance, thanks xray3d\n- v1.19.3 - made a high level of the default button in virtual mode\n- V1.19.4 - Fix Encbutton2\n- V1.20 - Critical error is fixed in Encbutton2\n- V1.21 - EB_HALFSTEP_ENC now works for a normal mode\n- V1.22 - Improved EB_HALFSTEP_Enc for a normal mode\n- V1.23 - Getdir () replaced with DIR ()\n- V2.0\n    - The eb_better_enc algorithm is optimized and set by default, the define eb_better_enc is abolished\n    - added setenctype () to configure the type of encoder from the program, the define EB_HALFSTEP_ENC is abolished\n    - added Setencreverse () to change the direction of the encoder from the program\n    - added setteptimeout () to set the period of impulse deduction, the define EB_STEP is abolished\n    - Small improvements and optimization\n</details>\n\n- V3.0\n  - The library is rewritten from scratch, with previous versions is incompatible!\n    - completely different initialization of the object\n    -renamed: Hold ()-> Holding (), HELD ()-> HOLD ()\n  - Optimization of Flash memory: the library weighs less, in some scenarios - by several kilobytes\n  - optimization of the speed of code execution, including in interruption\n  - several bytes less than RAM, several optimization levels to choose from\n  - a simpler, understandable and convenient use\n  - more readable source code\n  - Breaking into classes for use in different scenarios\n  - new functions, capabilities and handlers for the button and encoder\n  - Encoder's buffer in interruption\n  - native processing of two simultaneously pressed buttons as a third button\n  - support of 4 types of encoder\n  - The documentation is rewritten\n  - Encbutton is now replacing Gyverlibs/Virtualbutton (archived)\n- V3.1\n  - The initialization of the button is expanded\n  - removed Holdencbutton () and Toggleencbutton ()\n  - added Turnh ()\n  - Optimized the interruptions of encoder, added Setencisr ()\n  - Buerization of the direction and quick turn\n  - strongly optimized the speed of Action () (general processor)\n  - Added connection of the external function-processor of events\n  - Added button processing in interruption - Pressisr ()\n- V3.2\n  - Added the functions of TickRaW () and Clear () for all classes.Allows for separate processing (see document)\n  - improved processing button using interruptions\n- V3.3\n  - Added functions of receiving PressFor (), HoldFor (), StepFor () (disconnected)\n  - Added meter of the steps Getsteps () (disconnected)\n- V3.4\n  - access to the click counter during a pressed turn\n  - Added function Detach ()\n- V3.5\n  - added dependence of Gyverio (accelerated Pino survey)\n  - added the opportunity to set your pharmacy and pine reading functions\n- V3.5.2\n  - Optimization\n  - Simplified replacement of custom functions\n  - Fixed a compilation error when using a library in several .cpp files\n- V3.5.3\n  - Added the number of clicks to the Press/Release/Click/Pressing poll\n- V3.5.5 - Collback based on the STD :: Function for ESP\n    \n<a id=\"feedback\"> </a>\n## bugs and feedback\nCreate ** Issue ** when you find the bugs, and better immediately write to the mail [alex@alexgyver.ru] (mailto: alex@alexgyver.ru)\nThe library is open for refinement and your ** pull Request ** 'ow!\n\nWhen reporting about bugs or incorrect work of the library, it is necessary to indicate:\n- The version of the library\n- What is MK used\n- SDK version (for ESP)\n- version of Arduino ide\n- whether the built -in examples work correctly, in which the functions and designs are used, leading to a bug in your code\n- what code has been loaded, what work was expected from it and how it works in reality\n- Ideally, attach the minimum code in which the bug is observed.Not a canvas of a thousand lines, but a minimum code"
  },
  {
    "path": "examples/callback/callback.ino",
    "content": "// опрос событий через функцию-обработчик\n\n#include <Arduino.h>\n#include <EncButton.h>\n\nEncButton eb(2, 3, 4);\n\nvoid cb() {\n    // здесь EB_self - указатель на сам объект\n\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.print(\"release. 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            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        default:\n            Serial.println();\n    }\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    eb.attach(cb);\n}\n\nvoid loop() {\n    eb.tick();\n}"
  },
  {
    "path": "examples/callback2/callback2.ino",
    "content": "// опрос событий через функцию-обработчик\n\n#include <Arduino.h>\n#include <EncButton.h>\n\nEncButton eb(2, 3, 4);\n\nvoid cb() {\n    // здесь EB_self - указатель на сам объект\n\n    Serial.print(\"callback: \");\n    switch (eb.getAction()) {\n        case EBAction::Press:\n            Serial.println(\"press\");\n            break;\n        case EBAction::Hold:\n            Serial.println(\"hold\");\n            break;\n        case EBAction::Step:\n            Serial.println(\"step\");\n            break;\n        case EBAction::Release:\n            Serial.print(\"release. 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            break;\n        case EBAction::Click:\n            Serial.println(\"click\");\n            break;\n        case EBAction::Clicks:\n            Serial.print(\"clicks \");\n            Serial.println(eb.getClicks());\n            break;\n        case EBAction::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 EBAction::ReleaseHold:\n            Serial.println(\"release hold\");\n            break;\n        case EBAction::ReleaseHoldClicks:\n            Serial.print(\"release hold clicks \");\n            Serial.println(eb.getClicks());\n            break;\n        case EBAction::ReleaseStep:\n            Serial.println(\"release step\");\n            break;\n        case EBAction::ReleaseStepClicks:\n            Serial.print(\"release step clicks \");\n            Serial.println(eb.getClicks());\n            break;\n        case EBAction::Timeout:\n            Serial.println(\"timeout\");\n            break;\n        default: break;\n    }\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    eb.attach(cb);\n}\n\nvoid loop() {\n    eb.tick();\n}"
  },
  {
    "path": "examples/demo/demo.ino",
    "content": "// полное демо\n#include <Arduino.h>\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 <EncButton.h>\nEncButton eb(2, 3, 4);\n// EncButton eb(2, 3, 4, INPUT); // + режим пинов энкодера\n// EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP); // + режим пинов кнопки\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    eb.setTimeout(1000);\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}"
  },
  {
    "path": "examples/double/double.ino",
    "content": "// опрос одновременного нажатия двух кнопок как нажатия третьей кнопки (виртуальной)\n// библиотека сама сбросит события с первых двух кнопок, если они нажаты вместе\n\n#include <Arduino.h>\n#include <EncButton.h>\n\nButton b0(4);\nButton b1(5);\nVirtButton b2;  // виртуальная\n\nvoid setup() {\n    Serial.begin(115200);\n}\n\nvoid loop() {\n    b0.tick();\n    b1.tick();\n\n    // обработка одновременного нажатия двух кнопок\n    b2.tick(b0, b1);\n\n    if (b0.click()) Serial.println(\"b0 click\");\n    if (b1.click()) Serial.println(\"b1 click\");\n\n    if (b2.click()) Serial.println(\"b0+b1 click\");\n    if (b2.step()) Serial.println(\"b0+b1 step\");\n}"
  },
  {
    "path": "examples/doubleCallback/doubleCallback.ino",
    "content": "// опрос одновременного нажатия двух кнопок как нажатия третьей кнопки\n// с корректным вызовом обработчиков\n\n#include <Arduino.h>\n#include <EncButton.h>\n\nButton b0(4);\nButton b1(5);\nMultiButton b12;  // виртуальная\n\nvoid decode(uint16_t action) {\n    switch (action) {\n        case EB_PRESS:\n            Serial.println(\"press\");\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.println(\"clicks\");\n            break;\n        case EB_REL_HOLD:\n            Serial.println(\"release hold\");\n            break;\n        case EB_REL_HOLD_C:\n            Serial.println(\"release hold clicks \");\n            break;\n        case EB_REL_STEP:\n            Serial.println(\"release step\");\n            break;\n        case EB_REL_STEP_C:\n            Serial.println(\"release step clicks \");\n            break;\n        case EB_TIMEOUT:\n            Serial.println(\"timeout\");\n            break;\n    }\n}\n\nvoid setup() {\n    Serial.begin(115200);\n\n    // обработчики\n    b0.attach([]() {\n        uint16_t action = static_cast<VirtButton*>(EB_self)->action();\n        if (action != EB_HOLD) Serial.print(\"b0: \");\n        decode(action);\n    });\n\n    b1.attach([]() {\n        uint16_t action = static_cast<VirtButton*>(EB_self)->action();\n        if (action != EB_HOLD) Serial.print(\"b1: \");\n        decode(action);\n    });\n\n    b12.attach([]() {\n        uint16_t action = static_cast<VirtButton*>(EB_self)->action();\n        if (action != EB_HOLD) Serial.print(\"b0+b1: \");\n        decode(action);\n    });\n}\n\nvoid loop() {\n    // обработка одновременного нажатия двух кнопок\n    // обрабатываются все три кнопки\n    b12.tick(b0, b1);\n\n    // или вручную\n    if (b0.click()) Serial.println(\"b0 click\");\n    if (b1.click()) Serial.println(\"b1 click\");\n    if (b12.click()) Serial.println(\"b0+b1 click\");\n}"
  },
  {
    "path": "examples/isr/isr.ino",
    "content": "// энкодер и прерывания\n#include <Arduino.h>\n#include <EncButton.h>\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}\n\nvoid setup() {\n    Serial.begin(115200);\n    attachInterrupt(0, isr, CHANGE);\n    attachInterrupt(1, isr, CHANGE);\n    eb.setEncISR(true);\n}\n\nvoid loop() {\n    eb.tick();\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.println(eb.counter);\n    }\n\n    delay(100);  // имитация загруженной программы\n}"
  },
  {
    "path": "examples/one_button_3_var/one_button_3_var.ino",
    "content": "#include <Arduino.h>\n// используем одну КНОПКУ для удобного изменения трёх переменных\n// первая - один клик, затем удержание (нажал-отпустил-нажал-держим)\n// вторая - два клика, затем удержание\n// третья - три клика, затем удержание\n// смотри монитор порта\n\n#include <EncButton.h>\nButton btn(4);\n\n// переменные для изменения\nint val_a, val_b, val_c;\n\n// шаги изменения (signed)\nint8_t step_a = 1;\nint8_t step_b = 5;\nint8_t step_c = 10;\n\nvoid setup() {\n    Serial.begin(115200);\n}\n\nvoid loop() {\n    btn.tick();\n\n    // передаём количество предварительных кликов\n    if (btn.step(1)) {\n        val_a += step_a;\n        Serial.print(\"val_a: \");\n        Serial.println(val_a);\n    }\n    if (btn.step(2)) {\n        val_b += step_b;\n        Serial.print(\"val_b: \");\n        Serial.println(val_b);\n    }\n    if (btn.step(3)) {\n        val_c += step_c;\n        Serial.print(\"val_c: \");\n        Serial.println(val_c);\n    }\n\n    // разворачиваем шаг для изменения в обратную сторону\n    // передаём количество предварительных кликов\n    if (btn.releaseStep(1)) step_a = -step_a;\n    if (btn.releaseStep(2)) step_b = -step_b;\n    if (btn.releaseStep(3)) step_c = -step_c;\n}"
  },
  {
    "path": "examples/one_enc_3_var/one_enc_3_var.ino",
    "content": "// управление тремя переменными при помощи энкодера:\n// - нащёлкай кнопкой нужную переменную (1, 2 или 3 клика)\n// - 1 переменная просто изменяется с постоянным шагом\n// - 2 переменная: шаг 1, при зажатой кнопке - шаг 5\n// - 3 переменная: шаг 1, при быстром вращении - шаг 5\n\n#include <Arduino.h>\n#include <EncButton.h>\nEncButton eb(2, 3, 4);\n\nint var1 = 0;\nint var2 = 0;\nint var3 = 0;\nuint8_t select = 1;  // выбранная переменная\n\nvoid setup() {\n    Serial.begin(115200);\n}\n\nvoid loop() {\n    eb.tick();\n\n    // выбор переменной для изменения\n    if (eb.hasClicks()) {\n        select = eb.getClicks();\n        Serial.println(String(\"Select: \") + select);\n    }\n\n    if (eb.turn()) {\n        // меняем переменную\n        switch (select) {\n            case 1:\n                // изменение с шагом 5\n                var1 += 5 * eb.dir();\n                break;\n            case 2:\n                // изменение с шагом 1, при зажатой кнопке шаг 5\n                var2 += (eb.pressing() ? 5 : 1) * eb.dir();\n                break;\n            case 3:\n                // изменение с шагом 1, при быстром вращении шаг 5\n                var3 += (eb.fast() ? 5 : 1) * eb.dir();\n                break;\n        }\n        Serial.println(String(\"vars \") + var1 + ',' + var2 + ',' + var3);\n    }\n}"
  },
  {
    "path": "examples/virtual_buttons/virtual_AnalogKey/virtual_AnalogKey.ino",
    "content": "// пример работы в виртуальном режиме совместно с библиотекой AnalogKey\n// https://github.com/GyverLibs/AnalogKey\n\n#include <EncButton.h>\nVirtButton btn0;\nVirtButton btn1;\n\n#include <AnalogKey.h>\n// создаём массив значений сигналов с кнопок\nint16_t sigs[16] = {\n  1023, 927, 856, 783,\n  671,  632,  590,  560,\n  504,  480,  455,  440,\n  399,  319,  255,  230\n};\n\n// указываем пин, количество кнопок и массив значений\nAnalogKey<A0, 16, sigs> keys;\n\nvoid setup() {\n  Serial.begin(9600);\n}\n\nvoid loop() {\n  btn0.tick(keys.status(0));\n  btn1.tick(keys.status(1));\n\n  // забираем действия с кнопок\n  if (btn0.click()) Serial.println(\"click 0\");\n  if (btn0.hold()) Serial.println(\"hold 0\");\n\n  if (btn1.press()) Serial.println(\"press 1\");\n  if (btn1.step()) Serial.println(\"step 1\");\n}\n"
  },
  {
    "path": "examples/virtual_buttons/virtual_SimpleKeypad/virtual_SimpleKeypad.ino",
    "content": "// пример работы в виртуальном режиме совместно с библиотекой SimpleKeypad\n// https://github.com/maximebohrer/SimpleKeypad\n\n#include <EncButton.h>\nVirtButton btn0;\nVirtButton btn1;\n\n// пины подключения (по порядку штекера)\nbyte colPins[] = {7, 6, 5, 4};\nbyte rowPins[] = {11, 10, 9, 8};\n\n// массив имён кнопок\nchar keys[4][4] = {\n  {'1', '2', '3', 'A'},\n  {'4', '5', '6', 'B'},\n  {'7', '8', '9', 'C'},\n  {'*', '0', '#', 'D'}\n};\n\n#include <SimpleKeypad.h>\nSimpleKeypad pad((char*)keys, rowPins, colPins, 4, 4);\n\nvoid setup() {\n  Serial.begin(9600);\n  btn0.setDebTimeout(0);\n  btn1.setDebTimeout(0);\n}\n\nvoid loop() {\n  btn0.tick(0);\n  btn1.tick(0);\n  \n  // тикаем все кнопки, передавая сравнение с кодом кнопки в цикле\n  // делаем это по таймеру, чтобы не опрашивать клавиатуру постоянно\n  static uint32_t tmr;\n  if (millis() - tmr >= 10) {\n    tmr = millis();\n    char key = pad.scan();\n    btn0.tick(key == '1');\n    btn1.tick(key == '2');\n  }\n\n  // забираем действия с кнопок\n  if (btn0.click()) Serial.println(\"click 0\");\n  if (btn0.hold()) Serial.println(\"hold 0\");\n\n  if (btn1.press()) Serial.println(\"press 1\");\n  if (btn1.step()) Serial.println(\"step 1\");\n}\n"
  },
  {
    "path": "examples/virtual_buttons/virtual_SimpleKeypad_array/virtual_SimpleKeypad_array.ino",
    "content": "// пример работы в виртуальном режиме совместно с библиотекой SimpleKeypad\n// https://github.com/maximebohrer/SimpleKeypad\n// передаём EncButton сразу всю клавиатуру через массивы и циклы\n\n#include <EncButton.h>\nVirtButton btn[16];\n\n// пины подключения (по порядку штекера)\nbyte colPins[] = {7, 6, 5, 4};\nbyte rowPins[] = {11, 10, 9, 8};\n\n// массив имён кнопок\nchar keys[4][4] = {\n  {'1', '2', '3', 'A'},\n  {'4', '5', '6', 'B'},\n  {'7', '8', '9', 'C'},\n  {'*', '0', '#', 'D'}\n};\n\n#include <SimpleKeypad.h>\nSimpleKeypad pad((char*)keys, rowPins, colPins, 4, 4);\n\nvoid setup() {\n  Serial.begin(9600);\n  for (int i = 0; i < 16; i++) btn[i].setDebTimeout(0);\n}\n\nvoid loop() {\n  for (int i = 0; i < 16; i++) btn[i].tick(0);\n  \n  // массово тикаем все кнопки, передавая сравнение с кодом кнопки в цикле\n  // делаем это по таймеру, чтобы не опрашивать клавиатуру постоянно\n  static uint32_t tmr;\n  if (millis() - tmr >= 10) {\n    tmr = millis();\n    char key = pad.scan();\n    char* keysPtr = (char*)keys;  // указатель для удобства опроса\n    for (int i = 0; i < 16; i++) btn[i].tick(key == keysPtr[i]);\n  }\n\n  // забираем действия с кнопок\n  if (btn[0].click()) Serial.println(\"click 0\");\n  if (btn[0].hold()) Serial.println(\"hold 0\");\n\n  if (btn[1].press()) Serial.println(\"press 1\");\n  if (btn[1].step()) Serial.println(\"step 1\");\n}\n"
  },
  {
    "path": "keywords.txt",
    "content": "#######################################\n# Syntax Coloring Map For EncButton\n#######################################\n\n#######################################\n# Datatypes (KEYWORD1)\n#######################################\nEncButton\tKEYWORD1\nEncButtonT\tKEYWORD1\nVirtEncButton\tKEYWORD1\nButton\tKEYWORD1\nButtonT\tKEYWORD1\nVirtEncoder\tKEYWORD1\nVirtButton\tKEYWORD1\nMultiButton\tKEYWORD1\n\nEB_NO_COUNTER\tKEYWORD1\nEB_NO_BUFFER\tKEYWORD1\nEB_NO_CALLBACK\tKEYWORD1\nEB_NO_FOR\tKEYWORD1\n\nEB_DEB_TIME\tKEYWORD1\nEB_CLICK_TIME\tKEYWORD1\nEB_HOLD_TIME\tKEYWORD1\nEB_STEP_TIME\tKEYWORD1\nEB_FAST_TIME\tKEYWORD1\n\n#######################################\n# Methods and Functions (KEYWORD2)\n#######################################\n\nEB_read\tKEYWORD2\nEB_uptime\tKEYWORD2\nEB_mode\tKEYWORD2\n\nsetHoldTimeout\tKEYWORD2\nsetStepTimeout\tKEYWORD2\nsetClickTimeout\tKEYWORD2\nsetDebTimeout\tKEYWORD2\nsetTimeout\tKEYWORD2\nsetBtnLevel\tKEYWORD2\nreset\tKEYWORD2\nclear\tKEYWORD2\npress\tKEYWORD2\nrelease\tKEYWORD2\nclick\tKEYWORD2\npressing\tKEYWORD2\nhold\tKEYWORD2\nholding\tKEYWORD2\nstep\tKEYWORD2\nhasClicks\tKEYWORD2\ngetClicks\tKEYWORD2\ngetSteps\tKEYWORD2\nreleaseHold\tKEYWORD2\nreleaseStep\tKEYWORD2\nreleaseHoldStep\tKEYWORD2\ntimeout\tKEYWORD2\ntimeoutState\tKEYWORD2\nwaiting\tKEYWORD2\nbusy\tKEYWORD2\naction\tKEYWORD2\ngetAction\tKEYWORD2\nattach\tKEYWORD2\ndetach\tKEYWORD2\npressISR\tKEYWORD2\npressFor\tKEYWORD2\nholdFor\tKEYWORD2\nstepFor\tKEYWORD2\n\nsetEncReverse\tKEYWORD2\nsetEncType\tKEYWORD2\ninitEnc\tKEYWORD2\n\nsetFastTimeout\tKEYWORD2\nsetEncISR\tKEYWORD2\nturn\tKEYWORD2\nturnH\tKEYWORD2\nright\tKEYWORD2\nleft\tKEYWORD2\nrightH\tKEYWORD2\nleftH\tKEYWORD2\nencHolding\tKEYWORD2\ndir\tKEYWORD2\nfast\tKEYWORD2\n\npollEnc\tKEYWORD2\ninit\tKEYWORD2\ntickRaw\tKEYWORD2\ntickISR\tKEYWORD2\ntick\tKEYWORD2\nreadBtn\tKEYWORD2\n\n#######################################\n# Constants (LITERAL1)\n#######################################\t\nEB_PRESS\tLITERAL1\nEB_HOLD\tLITERAL1\nEB_STEP\tLITERAL1\nEB_RELEASE\tLITERAL1\nEB_CLICK\tLITERAL1\nEB_CLICKS\tLITERAL1\nEB_TURN\tLITERAL1\nEB_REL_HOLD\tLITERAL1\nEB_REL_HOLD_C\tLITERAL1\nEB_REL_STEP\tLITERAL1\nEB_REL_STEP_C\tLITERAL1\nEB_TIMEOUT\tLITERAL1\n\nEB_STEP4_LOW\tLITERAL1\nEB_STEP4_HIGH\tLITERAL1\nEB_STEP2\tLITERAL1\nEB_STEP1\tLITERAL1\n\nNone\tLITERAL1\nPress\tLITERAL1\nHold\tLITERAL1\nStep\tLITERAL1\nRelease\tLITERAL1\nClick\tLITERAL1\nClicks\tLITERAL1\nTurn\tLITERAL1\nReleaseHold\tLITERAL1\nReleaseHoldClicks\tLITERAL1\nReleaseStep\tLITERAL1\nReleaseStepClicks\tLITERAL1\nTimeout\tLITERAL1"
  },
  {
    "path": "library.properties",
    "content": "name=EncButton\nversion=3.7.4\nauthor=AlexGyver <alex@alexgyver.ru>\nmaintainer=AlexGyver <alex@alexgyver.ru>\nsentence=Light and powerful library for button and encoder operation for Arduino\nparagraph=Debounce, click count, hold, step hold mode and many more. Maximum possibilities for button and encoder\ncategory=Sensors\nurl=https://github.com/GyverLibs/EncButton\narchitectures=*\ndepends=GyverIO"
  },
  {
    "path": "src/EncButton.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"core/Button.h\"\n#include \"core/EncButton.h\"\n#include \"core/Encoder.h\"\n#include \"core/MultiButton.h\"\n#include \"core/VirtButton.h\"\n#include \"core/VirtEncButton.h\"\n#include \"core/VirtEncoder.h\""
  },
  {
    "path": "src/core/Button.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"VirtButton.h\"\n#include \"io.h\"\n\n// ============= VAR PIN =============\nclass Button : public VirtButton {\n   public:\n    Button(uint8_t npin = 0, uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        init(npin, mode, btnLevel);\n    }\n\n    // указать пин и его режим работы\n    void init(uint8_t npin = 0, uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        pin = npin;\n        EB_mode(pin, mode);\n        setBtnLevel(btnLevel);\n    }\n\n    // прочитать текущее значение кнопки (без дебаунса)\n    bool read() {\n        return EB_read(pin) ^ bf.read(EB_INV);\n    }\n\n    // функция обработки, вызывать в loop\n    bool tick() {\n        return VirtButton::tick(EB_read(pin));\n    }\n\n    // обработка кнопки без сброса событий и вызова коллбэка\n    bool tickRaw() {\n        return VirtButton::tickRaw(EB_read(pin));\n    }\n\n    // получить пин кнопки\n    uint8_t getPin() {\n        return pin;\n    }\n\n   private:\n    uint8_t pin;\n};\n\n// ============= TEMPLATE PIN =============\ntemplate <uint8_t PIN>\nclass ButtonT : public VirtButton {\n   public:\n    ButtonT(uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        init(mode, btnLevel);\n    }\n\n    // указать режим работы пина\n    void init(uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        EB_mode(PIN, mode);\n        setBtnLevel(btnLevel);\n    }\n\n    // прочитать текущее значение кнопки (без дебаунса)\n    bool read() {\n        return EB_read(PIN) ^ bf.read(EB_INV);\n    }\n\n    // функция обработки, вызывать в loop\n    bool tick() {\n        return VirtButton::tick(EB_read(PIN));\n    }\n\n    // обработка кнопки без сброса событий и вызова коллбэка\n    bool tickRaw() {\n        return VirtButton::tickRaw(EB_read(PIN));\n    }\n};"
  },
  {
    "path": "src/core/EncButton.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"VirtEncButton.h\"\n#include \"io.h\"\n\n// ===================== CLASS =====================\nclass EncButton : public VirtEncButton {\n   public:\n    // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка)\n    EncButton(uint8_t encA = 0, uint8_t encB = 0, uint8_t btn = 0, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        init(encA, encB, btn, modeEnc, modeBtn, btnLevel);\n    }\n\n    // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка)\n    void init(uint8_t encA = 0, uint8_t encB = 0, uint8_t btn = 0, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        e0 = encA;\n        e1 = encB;\n        b = btn;\n        EB_mode(e0, modeEnc);\n        EB_mode(e1, modeEnc);\n        EB_mode(b, modeBtn);\n        setBtnLevel(btnLevel);\n        initEnc(EB_read(e0), EB_read(e1));\n    }\n\n    // ====================== TICK ======================\n    // функция обработки для вызова в прерывании энкодера\n    int8_t tickISR() {\n        return VirtEncButton::tickISR(EB_read(e0), EB_read(e1));\n    }\n\n    // функция обработки, вызывать в loop\n    bool tick() {\n        return ef.read(EB_EISR) ? VirtEncButton::tick(EB_read(b)) : VirtEncButton::tick(EB_read(e0), EB_read(e1), EB_read(b));\n    }\n\n    // функция обработки без сброса событий\n    bool tickRaw() {\n        return ef.read(EB_EISR) ? VirtEncButton::tickRaw(EB_read(b)) : VirtEncButton::tickRaw(EB_read(e0), EB_read(e1), EB_read(b));\n    }\n\n    // ====================== READ ======================\n    // прочитать значение кнопки\n    bool readBtn() {\n        return EB_read(b) ^ bf.read(EB_INV);\n    }\n\n    // получить пин кнопки\n    uint8_t getPin() {\n        return b;\n    }\n\n    // получить пин энкодера\n    uint8_t getPinA() {\n        return e0;\n    }\n\n    // получить пин энкодера\n    uint8_t getPinB() {\n        return e1;\n    }\n\n    // ===================== PRIVATE =====================\n   private:\n    uint8_t e0, e1, b;\n};\n\n// ===================== T CLASS =====================\ntemplate <uint8_t ENCA, uint8_t ENCB, uint8_t BTN>\nclass EncButtonT : public VirtEncButton {\n   public:\n    // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка)\n    EncButtonT(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        init(modeEnc, modeBtn, btnLevel);\n    }\n\n    // настроить пины (pinmode энк, pinmode кнопка)\n    void init(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) {\n        EB_mode(ENCA, modeEnc);\n        EB_mode(ENCB, modeEnc);\n        EB_mode(BTN, modeBtn);\n        setBtnLevel(btnLevel);\n        initEnc(EB_read(ENCA), EB_read(ENCB));\n    }\n\n    // ====================== TICK ======================\n    // функция обработки для вызова в прерывании энкодера\n    int8_t tickISR() {\n        return VirtEncButton::tickISR(EB_read(ENCA), EB_read(ENCB));\n    }\n\n    // функция обработки, вызывать в loop\n    bool tick() {\n        return ef.read(EB_EISR) ? VirtEncButton::tick(EB_read(BTN)) : VirtEncButton::tick(EB_read(ENCA), EB_read(ENCB), EB_read(BTN));\n    }\n\n    // функция обработки без сброса событий\n    bool tickRaw() {\n        return ef.read(EB_EISR) ? VirtEncButton::tickRaw(EB_read(BTN)) : VirtEncButton::tickRaw(EB_read(ENCA), EB_read(ENCB), EB_read(BTN));\n    }\n\n    // ====================== READ ======================\n    // прочитать значение кнопки\n    bool readBtn() {\n        return EB_read(BTN) ^ bf.read(EB_INV);\n    }\n};"
  },
  {
    "path": "src/core/Encoder.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"VirtEncoder.h\"\n#include \"io.h\"\n\n// ============= VAR PIN =============\nclass Encoder : public VirtEncoder {\n   public:\n    // указать пины и их режим работы\n    Encoder(uint8_t encA = 0, uint8_t encB = 0, uint8_t mode = INPUT) {\n        init(encA, encB, mode);\n    }\n\n    // указать пины и их режим работы\n    void init(uint8_t encA = 0, uint8_t encB = 0, uint8_t mode = INPUT) {\n        e0 = encA;\n        e1 = encB;\n        EB_mode(e0, mode);\n        EB_mode(e1, mode);\n        initEnc(EB_read(e0), EB_read(e1));\n    }\n\n    // функция обработки для вызова в прерывании энкодера\n    int8_t tickISR() {\n        return VirtEncoder::tickISR(EB_read(e0), EB_read(e1));\n    }\n\n    // функция обработки, вызывать в loop\n    int8_t tick() {\n        return ef.read(EB_EISR) ? VirtEncoder::tick() : VirtEncoder::tick(EB_read(e0), EB_read(e1));\n    }\n\n    // обработка без сброса события поворота\n    int8_t tickRaw() {\n        return ef.read(EB_EISR) ? VirtEncoder::tickRaw() : VirtEncoder::tickRaw(EB_read(e0), EB_read(e1));\n    }\n\n    // получить пин энкодера\n    uint8_t getPinA() {\n        return e0;\n    }\n\n    // получить пин энкодера\n    uint8_t getPinB() {\n        return e1;\n    }\n\n   private:\n    uint8_t e0, e1;\n};\n\n// ============= TEMPLATE PIN =============\ntemplate <uint8_t ENCA, uint8_t ENCB>\nclass EncoderT : public VirtEncoder {\n   public:\n    // указать режим работы пинов\n    EncoderT(uint8_t mode = INPUT) {\n        init(mode);\n    }\n\n    // указать режим работы пинов\n    void init(uint8_t mode = INPUT) {\n        EB_mode(ENCA, mode);\n        EB_mode(ENCB, mode);\n        initEnc(EB_read(ENCA), EB_read(ENCB));\n    }\n\n    // функция обработки для вызова в прерывании энкодера\n    int8_t tickISR() {\n        return VirtEncoder::tickISR(EB_read(ENCA), EB_read(ENCB));\n    }\n\n    // функция обработки, вызывать в loop\n    int8_t tick() {\n        return ef.read(EB_EISR) ? VirtEncoder::tick() : VirtEncoder::tick(EB_read(ENCA), EB_read(ENCB));\n    }\n\n    // обработка без сброса события поворота\n    int8_t tickRaw() {\n        return ef.read(EB_EISR) ? VirtEncoder::tickRaw() : VirtEncoder::tickRaw(EB_read(ENCA), EB_read(ENCB));\n    }\n};"
  },
  {
    "path": "src/core/MultiButton.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"VirtButton.h\"\n\nclass MultiButton : public VirtButton {\n   public:\n    template <typename T0, typename T1>\n    bool tick(T0& b0, T1& b1) {\n        b0.clear();\n        b1.clear();\n        b0.tickRaw();\n        b1.tickRaw();\n\n        // if (bf.read(EB_BOTH)) {\n        //     if (!b0.pressing() && !b1.pressing()) bf.clear(EB_BOTH);\n        //     if (!b0.pressing()) b0.reset();\n        //     if (!b1.pressing()) b1.reset();\n        //     b0.clear();\n        //     b1.clear();\n        //     return VirtButton::tick(true);\n        // } else {\n        //     if (b0.pressing() && b1.pressing()) bf.set(EB_BOTH);\n        //     b0.call();\n        //     b1.call();\n        //     return VirtButton::tick(false);\n        // }\n        if (!bf.read(EB_BOTH)) {\n            b0.call();\n            b1.call();\n        }\n        return VirtButton::tick(b0, b1);\n    }\n};"
  },
  {
    "path": "src/core/VirtButton.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"flags.h\"\n#include \"io.h\"\n#include \"self.h\"\n\n#ifndef __AVR__\n#include <functional>\n#endif\n\n// ===================== FLAGS ======================\n#define EB_PRESS (1 << 0)        // нажатие на кнопку\n#define EB_HOLD (1 << 1)         // кнопка удержана\n#define EB_STEP (1 << 2)         // импульсное удержание\n#define EB_RELEASE (1 << 3)      // кнопка отпущена\n#define EB_CLICK (1 << 4)        // одиночный клик\n#define EB_CLICKS (1 << 5)       // сигнал о нескольких кликах\n#define EB_TURN (1 << 6)         // поворот энкодера\n#define EB_REL_HOLD (1 << 7)     // кнопка отпущена после удержания\n#define EB_REL_HOLD_C (1 << 8)   // кнопка отпущена после удержания с предв. кликами\n#define EB_REL_STEP (1 << 9)     // кнопка отпущена после степа\n#define EB_REL_STEP_C (1 << 10)  // кнопка отпущена после степа с предв. кликами\n#define EB_TIMEOUT (1 << 11)     // прошёл таймаут после нажатия кнопки или поворота энкодера\n\nenum class EBAction {\n    None = 0,\n    Press = EB_PRESS,\n    Hold = EB_HOLD,\n    Step = EB_STEP,\n    Release = EB_RELEASE,\n    Click = EB_CLICK,\n    Clicks = EB_CLICKS,\n    Turn = EB_TURN,\n    ReleaseHold = EB_REL_HOLD,\n    ReleaseHoldClicks = EB_REL_HOLD_C,\n    ReleaseStep = EB_REL_STEP,\n    ReleaseStepClicks = EB_REL_STEP_C,\n    Timeout = EB_TIMEOUT,\n};\n\n// =================== TOUT BUILD ===================\n#define EB_SHIFT 4\n\n// таймаут антидребезга, мс\n#ifdef EB_DEB_TIME\n#define EB_DEB_T (EB_DEB_TIME)\n#else\n#endif\n\n// таймаут между клтками, мс\n#ifdef EB_CLICK_TIME\n#define EB_CLICK_T (EB_CLICK_TIME)\n#define EB_GET_CLICK_TIME() ((uint16_t)EB_CLICK_T)\n#else\n#define EB_GET_CLICK_TIME() (uint16_t)(EB_CLICK_T << EB_SHIFT)\n#endif\n\n// таймаут удержания, мс\n#ifdef EB_HOLD_TIME\n#define EB_HOLD_T (EB_HOLD_TIME)\n#define EB_GET_HOLD_TIME() ((uint16_t)EB_HOLD_T)\n#else\n#define EB_GET_HOLD_TIME() (uint16_t)(EB_HOLD_T << EB_SHIFT)\n#endif\n\n// период степа, мс\n#ifdef EB_STEP_TIME\n#define EB_STEP_T (EB_STEP_TIME)\n#define EB_GET_STEP_TIME() ((uint16_t)EB_STEP_T)\n#else\n#define EB_GET_STEP_TIME() (uint16_t)(EB_STEP_T << EB_SHIFT)\n#endif\n\n// время таймаута, мс\n#ifdef EB_TOUT_TIME\n#define EB_TOUT_T (EB_TOUT_TIME)\n#define EB_GET_TOUT_TIME() ((uint16_t)EB_TOUT_T)\n#else\n#define EB_GET_TOUT_TIME() (uint16_t)(EB_TOUT_T << EB_SHIFT)\n#endif\n\n// =================== PACK FLAGS ===================\n#define EB_CLKS_R (1 << 0)\n#define EB_PRS_R (1 << 1)\n#define EB_HLD_R (1 << 2)\n#define EB_STP_R (1 << 3)\n#define EB_REL_R (1 << 4)\n#define EB_ALL_R (EB_CLKS_R | EB_PRS_R | EB_HLD_R | EB_STP_R | EB_REL_R)\n\n#define EB_PRS (1 << 5)\n#define EB_HLD (1 << 6)\n#define EB_STP (1 << 7)\n#define EB_REL (1 << 8)\n\n#define EB_BUSY (1 << 9)\n#define EB_DEB (1 << 10)\n#define EB_TOUT (1 << 11)\n#define EB_INV (1 << 12)\n#define EB_BOTH (1 << 13)\n#define EB_BISR (1 << 14)\n\n#define EB_EHLD (1 << 15)\n\n// базовый класс кнопки\nclass VirtButton {\n#ifdef __AVR__\n    typedef void (*ActionHandler)();\n#else\n    typedef std::function<void()> ActionHandler;\n#endif\n\n   public:\n    // ====================== SET ======================\n    // установить таймаут удержания, умолч. 600 (макс. 4000 мс)\n    void setHoldTimeout(const uint16_t tout) {\n#ifndef EB_HOLD_TIME\n        EB_HOLD_T = tout >> EB_SHIFT;\n#endif\n    }\n\n    // установить таймаут импульсного удержания, умолч. 200 (макс. 4000 мс)\n    void setStepTimeout(const uint16_t tout) {\n#ifndef EB_STEP_TIME\n        EB_STEP_T = tout >> EB_SHIFT;\n#endif\n    }\n\n    // установить таймаут ожидания кликов, умолч. 500 (макс. 4000 мс)\n    void setClickTimeout(const uint16_t tout) {\n#ifndef EB_CLICK_TIME\n        EB_CLICK_T = tout >> EB_SHIFT;\n#endif\n    }\n\n    // установить таймаут антидребезга, умолч. 50 (макс. 255 мс)\n    void setDebTimeout(const uint8_t tout) {\n#ifndef EB_DEB_TIME\n        EB_DEB_T = tout;\n#endif\n    }\n\n    // установить время таймаута, умолч. 1000 (макс. 4000 мс)\n    void setTimeout(const uint16_t tout) {\n#ifndef EB_TOUT_TIME\n        EB_TOUT_T = tout >> EB_SHIFT;\n#endif\n    }\n\n    // установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND)\n    void setBtnLevel(const bool level) {\n        bf.write(EB_INV, !level);\n    }\n\n    // кнопка нажата в прерывании (не учитывает btnLevel!)\n    void pressISR() {\n        if (!bf.read(EB_DEB)) tmr = EB_uptime();\n        bf.set(EB_DEB | EB_BISR);\n    }\n\n    // сбросить системные флаги (принудительно закончить обработку)\n    void reset() {\n        clicks = 0;\n        bf.clear(~EB_INV);  // все кроме EB_INV\n    }\n\n    // принудительно сбросить флаги событий\n    void clear(bool resetTout = false) {\n        if (resetTout) bf.clear(EB_TOUT);\n        if (bf.read(EB_CLKS_R)) clicks = 0;\n        bf.clear(EB_ALL_R);\n    }\n\n    // игнорировать все события до отпускания кнопки\n    void skipEvents() {\n        bf.set(EB_EHLD);\n    }\n\n    // подключить функцию-обработчик событий (вида void f())\n    void attach(ActionHandler handler) {\n#ifndef EB_NO_CALLBACK\n        cb = handler;\n#else\n        (void)handler;\n#endif\n    }\n\n    // отключить функцию-обработчик событий\n    void detach() {\n#ifndef EB_NO_CALLBACK\n        cb = nullptr;\n#endif\n    }\n\n    // ====================== GET ======================\n    // кнопка нажата [событие]\n    bool press() {\n        return bf.read(EB_PRS_R);\n    }\n\n    // кнопка нажата с предварительными кликами [событие]\n    bool press(const uint8_t num) {\n        return (clicks == num) && press();\n    }\n\n    // кнопка отпущена (в любом случае) [событие]\n    bool release() {\n        return bf.all(EB_REL_R | EB_REL);\n    }\n\n    // кнопка отпущена (в любом случае) с предварительными кликами [событие]\n    bool release(const uint8_t num) {\n        return (clicks == num) && release();\n    }\n\n    // клик по кнопке (отпущена без удержания) [событие]\n    bool click() {\n        return bf.eq(EB_REL_R | EB_REL | EB_HLD, EB_REL_R);\n    }\n\n    // клик по кнопке (отпущена без удержания) с предварительными кликами [событие]\n    bool click(const uint8_t num) {\n        return (clicks == num) && click();\n    }\n\n    // кнопка зажата (между press() и release()) [состояние]\n    bool pressing() {\n        return bf.read(EB_PRS);\n    }\n\n    // кнопка зажата (между press() и release()) с предварительными кликами [состояние]\n    bool pressing(const uint8_t num) {\n        return (clicks == num) && pressing();\n    }\n\n    // кнопка была удержана (больше таймаута) [событие]\n    bool hold() {\n        return bf.read(EB_HLD_R);\n    }\n\n    // кнопка была удержана (больше таймаута) с предварительными кликами [событие]\n    bool hold(const uint8_t num) {\n        return (clicks == num) && hold();\n    }\n\n    // кнопка удерживается (больше таймаута) [состояние]\n    bool holding() {\n        return bf.all(EB_PRS | EB_HLD);\n    }\n\n    // кнопка удерживается (больше таймаута) с предварительными кликами [состояние]\n    bool holding(const uint8_t num) {\n        return (clicks == num) && holding();\n    }\n\n    // импульсное удержание [событие]\n    bool step() {\n        return bf.read(EB_STP_R);\n    }\n\n    // импульсное удержание с предварительными кликами [событие]\n    bool step(const uint8_t num) {\n        return (clicks == num) && step();\n    }\n\n    // зафиксировано несколько кликов [событие]\n    bool hasClicks() {\n        return bf.eq(EB_CLKS_R | EB_HLD, EB_CLKS_R);\n    }\n\n    // зафиксировано указанное количество кликов [событие]\n    bool hasClicks(const uint8_t num) {\n        return (clicks == num) && hasClicks();\n    }\n\n    // получить количество кликов\n    uint8_t getClicks() {\n        return clicks;\n    }\n\n    // получить количество степов\n    uint16_t getSteps() {\n#ifndef EB_NO_FOR\n        return ftmr ? ((stepFor() + EB_GET_STEP_TIME() - 1) / EB_GET_STEP_TIME()) : 0;  // (x + y - 1) / y\n#endif\n        return 0;\n    }\n\n    // кнопка отпущена после удержания [событие]\n    bool releaseHold() {\n        return bf.eq(EB_REL_R | EB_REL | EB_HLD | EB_STP, EB_REL_R | EB_HLD);\n    }\n\n    // кнопка отпущена после удержания с предварительными кликами [событие]\n    bool releaseHold(const uint8_t num) {\n        return clicks == num && bf.eq(EB_CLKS_R | EB_HLD | EB_STP, EB_CLKS_R | EB_HLD);\n    }\n\n    // кнопка отпущена после импульсного удержания [событие]\n    bool releaseStep() {\n        return bf.eq(EB_REL_R | EB_REL | EB_STP, EB_REL_R | EB_STP);\n    }\n\n    // кнопка отпущена после импульсного удержания с предварительными кликами [событие]\n    bool releaseStep(const uint8_t num) {\n        return clicks == num && bf.all(EB_CLKS_R | EB_STP);\n    }\n\n    // кнопка отпущена после удержания или импульсного удержания [событие]\n    bool releaseHoldStep() {\n        return releaseHold() || releaseStep();\n    }\n\n    // кнопка отпущена после удержания или импульсного удержания с предварительными кликами [событие]\n    bool releaseHoldStep(const uint8_t num) {\n        return releaseHold(num) || releaseStep(num);\n    }\n\n    // кнопка ожидает повторных кликов [состояние]\n    bool waiting() {\n        return clicks && !bf.read(EB_PRS | EB_REL);\n    }\n\n    // идёт обработка [состояние]\n    bool busy() {\n        return bf.read(EB_BUSY);\n    }\n\n    // было действие с кнопки, вернёт код события [событие]\n    uint16_t action() {\n        switch (bf.mask(EB_ALL_R | EB_PRS | EB_HLD | EB_STP | EB_REL)) {\n            case (EB_PRS | EB_PRS_R): return EB_PRESS;\n            case (EB_PRS | EB_HLD | EB_HLD_R): return EB_HOLD;\n            case (EB_PRS | EB_HLD | EB_STP | EB_STP_R): return EB_STEP;\n            case (EB_REL | EB_REL_R):\n            case (EB_REL | EB_REL_R | EB_HLD):\n            case (EB_REL | EB_REL_R | EB_HLD | EB_STP):\n                return EB_RELEASE;\n            case (EB_REL_R): return EB_CLICK;\n            case (EB_CLKS_R): return EB_CLICKS;\n            case (EB_REL_R | EB_HLD): return EB_REL_HOLD;\n            case (EB_CLKS_R | EB_HLD): return EB_REL_HOLD_C;\n            case (EB_REL_R | EB_HLD | EB_STP): return EB_REL_STEP;\n            case (EB_CLKS_R | EB_HLD | EB_STP): return EB_REL_STEP_C;\n        }\n        if (timeoutState()) return EB_TIMEOUT;\n        return 0;\n    }\n\n    // было действие с кнопки, вернёт код события [событие]\n    EBAction getAction() {\n        return (EBAction)action();\n    }\n\n    // ====================== TIME ======================\n    // после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [событие]\n    bool timeout() {\n        if (timeoutState()) {\n            bf.clear(EB_TOUT);\n            return 1;\n        }\n        return 0;\n    }\n\n    // после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [состояние]\n    bool timeoutState() {\n        return bf.read(EB_TOUT) && (uint16_t)((uint16_t)EB_uptime() - tmr) >= EB_GET_TOUT_TIME();\n    }\n\n    // время, которое кнопка удерживается (с начала нажатия), мс\n    uint16_t pressFor() {\n#ifndef EB_NO_FOR\n        if (ftmr) return (uint16_t)EB_uptime() - ftmr;\n#endif\n        return 0;\n    }\n\n    // кнопка удерживается дольше чем (с начала нажатия), мс [состояние]\n    bool pressFor(const uint16_t ms) {\n        return pressFor() > ms;\n    }\n\n    // время, которое кнопка удерживается (с начала удержания), мс\n    uint16_t holdFor() {\n#ifndef EB_NO_FOR\n        if (bf.read(EB_HLD)) return pressFor() - EB_GET_HOLD_TIME();\n#endif\n        return 0;\n    }\n\n    // кнопка удерживается дольше чем (с начала удержания), мс [состояние]\n    bool holdFor(const uint16_t ms) {\n        return holdFor() > ms;\n    }\n\n    // время, которое кнопка удерживается (с начала степа), мс\n    uint16_t stepFor() {\n#ifndef EB_NO_FOR\n        if (bf.read(EB_STP)) return pressFor() - EB_GET_HOLD_TIME() * 2;\n#endif\n        return 0;\n    }\n\n    // кнопка удерживается дольше чем (с начала степа), мс [состояние]\n    bool stepFor(uint16_t ms) {\n        return stepFor() > ms;\n    }\n\n    // ====================== POLL ======================\n    // обработка виртуальной кнопки как одновременное нажатие двух других кнопок\n    bool tick(VirtButton& b0, VirtButton& b1) {\n        if (bf.read(EB_BOTH)) {\n            if (!b0.pressing() && !b1.pressing()) bf.clear(EB_BOTH);\n            if (!b0.pressing()) b0.reset();\n            if (!b1.pressing()) b1.reset();\n            b0.clear();\n            b1.clear();\n            return tick(true);\n        } else {\n            if (b0.pressing() && b1.pressing()) bf.set(EB_BOTH);\n            return tick(false);\n        }\n    }\n\n    // обработка кнопки значением\n    bool tick(bool s) {\n        clear();\n        s = pollBtn(s);\n#ifndef EB_NO_CALLBACK\n        if (s || timeoutState()) call();\n#endif\n        return s;\n    }\n\n    // обработка кнопки без сброса событий и вызова коллбэка\n    bool tickRaw(const bool s) {\n        return pollBtn(s);\n    }\n\n    // вызвать обработчик\n    void call(bool force = false) {  // todo force заменить на флаг\n#ifndef EB_NO_CALLBACK\n        if (cb && (force || action())) {\n            EB_self = this;\n            cb();\n            EB_self = nullptr;\n            timeout();  // todo clear tout\n        }\n#else\n        (void)force;\n#endif\n    }\n\n    uint8_t clicks;\n\n    // ===================== DEPRECATED =====================\n    void setButtonLevel(bool level) __attribute__((deprecated)) {\n        bf.write(EB_INV, !level);\n    }\n\n    // после взаимодействия с кнопкой (или энкодером EncButton) прошло указанное время, мс [событие]\n    bool timeout(const uint16_t tout) /*__attribute__((deprecated))*/ {\n        if (timeoutState(tout)) {\n            bf.clear(EB_TOUT);\n            return 1;\n        }\n        return 0;\n    }\n\n    // после взаимодействия с кнопкой (или энкодером EncButton) прошло указанное время, мс [состояние]\n    bool timeoutState(const uint16_t tout) /*__attribute__((deprecated))*/ {\n        return (bf.read(EB_TOUT) && (uint16_t)((uint16_t)EB_uptime() - tmr) > tout);\n    }\n\n    bool isStep() __attribute__((deprecated)) { return step(); }\n    bool isHold() __attribute__((deprecated)) { return holding(); }\n    bool isHolded() __attribute__((deprecated)) { return hold(); }\n    bool isHeld() __attribute__((deprecated)) { return hold(); }\n    bool isClick() __attribute__((deprecated)) { return click(); }\n    bool isRelease() __attribute__((deprecated)) { return release(); }\n    bool isPress() __attribute__((deprecated)) { return press(); }\n\n    // ====================== PRIVATE ======================\n   protected:\n    uint16_t tmr = 0;\n    encb::Flags<uint16_t> bf;\n\n#ifndef EB_NO_CALLBACK\n    ActionHandler cb = nullptr;\n#endif\n\n   private:\n#ifndef EB_NO_FOR\n    uint16_t ftmr = 0;\n#endif\n#ifndef EB_DEB_TIME\n    uint8_t EB_DEB_T = 50;\n#endif\n#ifndef EB_CLICK_TIME\n    uint8_t EB_CLICK_T = (500 >> EB_SHIFT);\n#endif\n#ifndef EB_HOLD_TIME\n    uint8_t EB_HOLD_T = (600 >> EB_SHIFT);\n#endif\n#ifndef EB_STEP_TIME\n    uint8_t EB_STEP_T = (200 >> EB_SHIFT);\n#endif\n#ifndef EB_TOUT_TIME\n    uint8_t EB_TOUT_T = (1000 >> EB_SHIFT);\n#endif\n\n    bool pollBtn(bool s) {\n        if (bf.read(EB_BISR)) {\n            bf.clear(EB_BISR);\n            s = 1;\n        } else s ^= bf.read(EB_INV);\n\n        if (!bf.read(EB_BUSY)) {\n            if (s) bf.set(EB_BUSY);\n            else return 0;\n        }\n\n        uint16_t ms = EB_uptime();\n        uint16_t deb = ms - tmr;\n\n        if (s) {                                         // кнопка нажата\n            if (!bf.read(EB_PRS)) {                      // кнопка не была нажата ранее\n                if (!bf.read(EB_DEB) && EB_DEB_T) {      // дебаунс ещё не сработал\n                    bf.set(EB_DEB);                      // будем ждать дебаунс\n                    tmr = ms;                            // сброс таймаута\n                } else {                                 // первое нажатие\n                    if (deb >= EB_DEB_T || !EB_DEB_T) {  // ждём EB_DEB_TIME\n                        bf.set(EB_PRS | EB_PRS_R);       // флаг на нажатие\n#ifndef EB_NO_FOR\n                        ftmr = ms;\n#endif\n                        tmr = ms;  // сброс таймаута\n                    }\n                }\n            } else {  // кнопка уже была нажата\n                if (!bf.read(EB_EHLD)) {\n                    if (!bf.read(EB_HLD)) {               // удержание ещё не зафиксировано\n                        if (deb >= EB_GET_HOLD_TIME()) {  // ждём EB_HOLD_TIME - это удержание\n                            bf.set(EB_HLD_R | EB_HLD);    // флаг что было удержание\n                            tmr = ms;                     // сброс таймаута\n                        }\n                    } else {  // удержание зафиксировано\n                        if (deb >= (uint16_t)(bf.read(EB_STP) ? EB_GET_STEP_TIME() : EB_GET_HOLD_TIME())) {\n                            bf.set(EB_STP | EB_STP_R);  // флаг степ\n                            tmr = ms;                   // сброс таймаута\n                        }\n                    }\n                }\n            }\n        } else {                                       // кнопка не нажата\n            if (bf.read(EB_PRS)) {                     // но была нажата\n                if (deb >= EB_DEB_T) {                 // ждём EB_DEB_TIME\n                    if (!bf.read(EB_HLD)) clicks++;    // не удерживали - это клик\n                    if (bf.read(EB_EHLD)) clicks = 0;  //\n                    bf.set(EB_REL | EB_REL_R);         // флаг release\n                    bf.clear(EB_PRS);                  // кнопка отпущена\n                }\n            } else if (bf.read(EB_REL)) {\n                if (!bf.read(EB_EHLD)) {\n                    bf.set(EB_REL_R);  // флаг releaseHold / releaseStep\n                }\n                bf.clear(EB_REL | EB_EHLD);\n                tmr = ms;                                                                       // сброс таймаута\n            } else if (clicks) {                                                                // есть клики, ждём EB_CLICK_TIME\n                if (bf.read(EB_HLD | EB_STP) || deb >= EB_GET_CLICK_TIME()) bf.set(EB_CLKS_R);  // флаг clicks\n#ifndef EB_NO_FOR\n                else ftmr = 0;\n#endif\n            } else if (bf.read(EB_BUSY)) {\n                bf.clear(EB_HLD | EB_STP | EB_BUSY);\n                bf.set(EB_TOUT);\n#ifndef EB_NO_FOR\n                ftmr = 0;\n#endif\n                tmr = ms;  // test!!\n            }\n            if (bf.read(EB_DEB)) bf.clear(EB_DEB);  // сброс ожидания нажатия (дебаунс)\n        }\n        return bf.read(EB_ALL_R);\n    }\n};\n"
  },
  {
    "path": "src/core/VirtEncButton.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"VirtButton.h\"\n#include \"VirtEncoder.h\"\n#include \"io.h\"\n\n#ifdef EB_FAST_TIME\n#define EB_FAST_T (EB_FAST_TIME)\n#endif\n\n#define EB_BUF_TURN (1 << 0)\n#define EB_BUF_DIR (1 << 1)\n#define EB_BUF_FAST (1 << 2)\n#define EB_BUF_LEN 3\n\n// базовый клас энкодера с кнопкой\nclass VirtEncButton : public VirtButton, public VirtEncoder {\n   public:\n    // ====================== SET ======================\n    // установить таймаут быстрого поворота, мс\n    void setFastTimeout(const uint8_t tout) {\n#ifndef EB_FAST_TIME\n        EB_FAST_T = tout;\n#endif\n    }\n\n    // сбросить флаги энкодера и кнопки\n    void clear(bool resetTout = false) {\n        VirtButton::clear(resetTout);\n        VirtEncoder::clear();\n    }\n\n    // ====================== GET ======================\n    // нажатый поворот энкодера [событие]\n    bool turnH() {\n        return turn() && bf.read(EB_EHLD);\n    }\n\n    // быстрый поворот энкодера [состояние]\n    bool fast() {\n        return ef.read(EB_FAST);\n    }\n\n    // поворот направо [событие]\n    bool right() {\n        return ef.eq(EB_ETRN_R | EB_DIR, EB_ETRN_R | EB_DIR) && !bf.read(EB_EHLD);\n    }\n\n    // поворот налево [событие]\n    bool left() {\n        return ef.eq(EB_ETRN_R | EB_DIR, EB_ETRN_R) && !bf.read(EB_EHLD);\n    }\n\n    // нажатый поворот направо [событие]\n    bool rightH() {\n        return ef.eq(EB_ETRN_R | EB_DIR, EB_ETRN_R | EB_DIR) && bf.read(EB_EHLD);\n    }\n\n    // нажатый поворот налево [событие]\n    bool leftH() {\n        return ef.eq(EB_ETRN_R | EB_DIR, EB_ETRN_R) && bf.read(EB_EHLD);\n    }\n\n    // нажата кнопка энкодера. Аналог pressing() [состояние]\n    bool encHolding() {\n        return bf.read(EB_EHLD);\n    }\n\n    // было действие с кнопки или энкодера, вернёт код события [событие]\n    uint16_t action() {\n        return turn() ? EB_TURN : VirtButton::action();\n    }\n\n    // было действие с кнопки или энкодера, вернёт код события [событие]\n    EBAction getAction() {\n        return (EBAction)action();\n    }\n\n    // ====================== POLL ======================\n    // ISR\n    // обработка в прерывании (только энкодер). Вернёт 0 в покое, 1 или -1 при повороте\n    int8_t tickISR(const bool e0, const bool e1) {\n        int8_t state = VirtEncoder::pollEnc(e0, e1);\n        if (!state) return state;\n\n#ifdef EB_NO_BUFFER\n        ef.set(EB_ISR_F);\n        ef.write(EB_DIR, state > 0);\n        ef.write(EB_FAST, _checkFast());\n#else\n        for (uint8_t i = 0; i < 15; i += EB_BUF_LEN) {\n            if (ebuffer & (EB_BUF_TURN << i)) continue;\n            ebuffer |= (EB_BUF_TURN | ((state > 0) * EB_BUF_DIR) | (_checkFast() * EB_BUF_FAST)) << i;\n            break;\n        }\n#endif\n        return state;\n    }\n\n    // TICK\n    // обработка энкодера и кнопки\n    bool tick(const bool e0, const bool e1, const bool btn) {\n        clear();\n        return _tick(_tickRaw(btn, pollEnc(e0, e1)));\n    }\n\n    // обработка энкодера (в прерывании) и кнопки\n    bool tick(const bool btn) {\n        clear();\n        return _tick(_tickRaw(btn));\n    }\n\n    // RAW\n    // обработка без сброса событий и вызова коллбэка\n    bool tickRaw(const bool e0, const bool e1, bool btn) {\n        return _tickRaw(btn, pollEnc(e0, e1));\n    }\n\n    // обработка без сброса событий и вызова коллбэка (кнопка)\n    bool tickRaw(const bool btn) {\n        return _tickRaw(btn);\n    }\n\n    // ===================== DEPRECATED =====================\n    bool isTurnH() __attribute__((deprecated)) { return turnH(); }\n    bool isTurn() __attribute__((deprecated)) { return turn(); }\n    bool isFast() __attribute__((deprecated)) { return fast(); }\n    bool isLeftH() __attribute__((deprecated)) { return leftH(); }\n    bool isRightH() __attribute__((deprecated)) { return rightH(); }\n    bool isLeft() __attribute__((deprecated)) { return left(); }\n    bool isRight() __attribute__((deprecated)) { return right(); }\n\n    // ===================== PRIVATE =====================\n   protected:\n#ifndef EB_FAST_TIME\n    uint8_t EB_FAST_T = 30;\n#endif\n\n#ifndef EB_NO_BUFFER\n    uint16_t ebuffer = 0;\n#endif\n\n   private:\n    bool _checkFast() {\n        uint16_t ms = EB_uptime();\n        bool f = (ms - tmr) < EB_FAST_T;\n        tmr = ms;\n        return f;\n    }\n\n    inline bool _tick(bool f) {\n#ifndef EB_NO_CALLBACK\n        if (f || timeoutState()) call(true);\n#endif\n        return f;\n    }\n\n    bool _tickRaw(bool btn, int8_t estate = 0) {\n#ifdef EB_NO_BUFFER\n        if (ef.read(EB_ISR_F)) {\n            ef.clear(EB_ISR_F);\n        }\n#else\n        if (ebuffer) {\n            ef.write(EB_DIR, ebuffer & EB_BUF_DIR);\n            ef.write(EB_FAST, ebuffer & EB_BUF_FAST);\n            ebuffer >>= EB_BUF_LEN;\n        }\n#endif\n        else if (estate) {\n            ef.write(EB_DIR, estate > 0);\n            ef.write(EB_FAST, _checkFast());\n        } else {\n            return VirtButton::tickRaw(btn);\n        }\n\n        if (bf.read(EB_PRS)) bf.set(EB_EHLD);  // зажать энкодер\n        else clicks = 0;\n        bf.set(EB_TOUT);    // таймаут\n        ef.set(EB_ETRN_R);  // флаг поворота\n\n        VirtButton::tickRaw(btn);\n        return true;\n    }\n};"
  },
  {
    "path": "src/core/VirtEncoder.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include \"flags.h\"\n#include \"io.h\"\n\n// ===================== CONST ======================\n#define EB_STEP4_LOW 0\n#define EB_STEP4_HIGH 1\n#define EB_STEP2 2\n#define EB_STEP1 3\n\n// ===================== FLAGS ======================\n#define EB_TYPE ((1 << 0) | (1 << 1))\n#define EB_REV (1 << 2)\n#define EB_FAST (1 << 3)\n#define EB_DIR (1 << 4)\n#define EB_ETRN_R (1 << 5)\n#define EB_ISR_F (1 << 6)\n#define EB_EISR (1 << 7)\n\n// базовый класс энкодера\nclass VirtEncoder {\n   public:\n    VirtEncoder() {\n        p0 = p1 = epos = 0;\n    }\n\n    // ====================== SET ======================\n    // инвертировать направление энкодера\n    void setEncReverse(const bool rev) {\n        ef.write(EB_REV, rev);\n    }\n\n    // установить тип энкодера (EB_STEP4_LOW, EB_STEP4_HIGH, EB_STEP2, EB_STEP1)\n    void setEncType(const uint8_t type) {\n        ef.clear(~EB_TYPE);\n        ef.set(type);\n    }\n\n    // использовать обработку энкодера в прерывании\n    void setEncISR(const bool use) {\n        ef.write(EB_EISR, use);\n    }\n\n    // инициализация энкодера\n    void initEnc(const bool e0, const bool e1) {\n        p0 = e0, p1 = e1;\n    }\n\n    // сбросить флаги событий\n    void clear() {\n        ef.clear(EB_ETRN_R);\n    }\n\n    // ====================== ОПРОС ======================\n    // был поворот [событие]\n    bool turn() {\n        return ef.read(EB_ETRN_R);\n    }\n\n    // направление энкодера (1 или -1) [состояние]\n    int8_t dir() {\n        return ef.read(EB_DIR) ? 1 : -1;\n    }\n\n    // ====================== POLL ======================\n    // ISR\n    // опросить энкодер в прерывании. Вернёт 1 или -1 при вращении, 0 при остановке\n    int8_t tickISR(const bool e0, const bool e1) {\n        int8_t state = pollEnc(e0, e1);\n        if (state) {\n            ef.set(EB_ISR_F);\n            ef.write(EB_DIR, state > 0);\n        }\n        return state;\n    }\n\n    // TICK\n    // опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановке\n    int8_t tick(const bool e0, const bool e1) {\n        int8_t state = tickRaw(e0, e1);\n        if (!state) clear();\n        return state;\n    }\n\n    // опросить энкодер (сам опрос в прерывании)\n    int8_t tick() {\n        int8_t state = tickRaw();\n        if (!state) clear();\n        return state;\n    }\n\n    // RAW\n    // опросить энкодер без сброса события поворота\n    int8_t tickRaw(const bool e0, const bool e1) {\n        int8_t state = tickRaw();\n        if (state) return state;\n\n        state = pollEnc(e0, e1);\n        if (state) {\n            ef.set(EB_ETRN_R);\n            ef.write(EB_DIR, state > 0);\n        }\n        return state;\n    }\n\n    // опросить энкодер без сброса события поворота (сам опрос в прерывании)\n    int8_t tickRaw() {\n        if (!ef.read(EB_ISR_F)) return 0;\n\n        ef.clear(EB_ISR_F);\n        ef.set(EB_ETRN_R);\n        return dir();\n    }\n\n    // POLL\n    // опросить энкодер без установки события поворота (быстрее). Вернёт 1 или -1 при вращении, 0 при остановке\n    int8_t pollEnc(const bool e0, const bool e1) {\n        if (!(p0 ^ p1 ^ e0 ^ e1)) return 0;\n\n        (p1 ^ e0) ? ++epos : --epos;\n        p0 = e0, p1 = e1;\n        if (!epos) return 0;\n\n        switch (ef.mask(0b11)) {\n            case EB_STEP4_LOW:\n                if (!(e0 & e1)) return 0;  // skip 01, 10, 00\n                break;\n            case EB_STEP4_HIGH:\n                if (e0 | e1) return 0;  // skip 01, 10, 11\n                break;\n            case EB_STEP2:\n                if (e0 ^ e1) return 0;  // skip 10 01\n                break;\n        }\n        int8_t state = ((epos > 0) ^ ef.read(EB_REV)) ? -1 : 1;\n        epos = 0;\n#ifndef EB_NO_COUNTER\n        counter += state;\n#endif\n        return state;\n    }\n\n#ifndef EB_NO_COUNTER\n    int32_t counter = 0;\n#endif\n\n    // ===================== PRIVATE =====================\n   protected:\n    encb::Flags<uint8_t> ef;\n\n   private:\n    int8_t p0 : 2;\n    int8_t p1 : 2;  // signed 2 bit!\n    int8_t epos : 4;\n};"
  },
  {
    "path": "src/core/flags.h",
    "content": "#pragma once\n#include <Arduino.h>\n\nnamespace encb {\n\ntemplate <typename T>\nstruct Flags {\n    T flags = 0;\n\n    inline T mask(const T x) __attribute__((always_inline)) {\n        return flags & x;\n    }\n    inline void set(const T x) __attribute__((always_inline)) {\n        flags |= x;\n    }\n    inline void clear(const T x) __attribute__((always_inline)) {\n        flags &= ~x;\n    }\n    inline bool read(const T x) __attribute__((always_inline)) {\n        return flags & x;\n    }\n    inline void write(const T x, const bool v) __attribute__((always_inline)) {\n        v ? set(x) : clear(x);\n    }\n    inline bool eq(const T x, const T y) __attribute__((always_inline)) {\n        return (flags & x) == y;\n    }\n    inline bool all(const T x) __attribute__((always_inline)) {\n        return eq(x, x);\n    }\n};\n\n}  // namespace encb"
  },
  {
    "path": "src/core/io.cpp",
    "content": "#include \"io.h\"\n\nbool __attribute__((weak)) EB_read(uint8_t pin) {\n    return gio::read(pin);\n}\n\nvoid __attribute__((weak)) EB_mode(uint8_t pin, uint8_t mode) {\n    gio::init(pin, mode);\n}\n\nuint32_t __attribute__((weak)) EB_uptime() {\n    return millis();\n}"
  },
  {
    "path": "src/core/io.h",
    "content": "#pragma once\n#include <Arduino.h>\n#include <GyverIO.h>\n\nbool EB_read(uint8_t pin);\nvoid EB_mode(uint8_t pin, uint8_t mode);\nuint32_t EB_uptime();"
  },
  {
    "path": "src/core/self.cpp",
    "content": "#include \"self.h\"\n\nvoid* EB_self = nullptr;"
  },
  {
    "path": "src/core/self.h",
    "content": "#pragma once\n\nextern void* EB_self;"
  }
]