Ночник в детской: клик — свет вкл и выкл, удержание две секунды — режим настройки, в котором кнопка листает уровни яркости. Логика на два часа работы, а упирается в вопрос, на котором спотыкается каждый первый: когда считать клик свершившимся? Ответите сразу при нажатии — и через полторы секунды удержания свет уже переключился, хотя человек собирался в настройки. Перенесёте решение на отпускание — придёте к правильному порядку, но придётся объяснить автомату, чем клик отличается от начала удержания, и ещё до поры не пугать пользователя темнотой. Именно этот выбор — где выносить вердикт — определяет и число состояний, и поведение прибора в руках живого человека, поэтому разберём обе схемы на числах, а не на вкусах.
Соседний урок про кнопку-автомат уже разобрал тройку жестов — клик, двойной клик и удержание на 600 мс, и там событие удержания стреляло в момент перевала порога. Здесь другой контракт: клик обязан переключить свет, а удержание — отменить его переключение. Поэтому я разбираю автомат с отменой, порогом 2000 мс и обратной связью, без которой пользователь жмёт дважды и считает прибор сломанным. База по таймингам берётся из урока про millis вместо delay — ни одной паузы в петле. Антидребезг перед автоматом обязателен, и его четыре варианта разложены в предыдущем уроке кластера — здесь они уже считаются готовым слоем снизу.
Где решать: на пороге или на отпускании
Архитектур ровно две. Ранняя: как только удержание перевалило порог, выдать событие — не дожидаясь отпускания. Отзыв мгновенный, прибор меняет режим, пока палец ещё на кнопке; так устроен автомат из соседнего урока, где удержание срабатывало на 600-й миллисекунде. Цена — необходимость отмены: палец отпустил на 2,1 секунде, и это был не клик, хотя нажатие до сих пор числится незавершённым. Клик гасится флагом, который взводится при срабатывании удержания и проверяется при отпускании. Отдельно скажу, почему флаг, а не «просто не переключать»: в ранней схеме после срабатывания порога нажатие ещё живёт неопределённо долго — палец может лежать на кнопке и пять секунд, и каждый круг loop обязан знать, что этому нажатию уже всё выдано.
Поздняя: ничего не решаем до отпускания. Меряем длину нажатия и на отпускании выносим вердикт: короче порога — клик, длиннее — режим. Отменять нечего, автомат живёт в двух состояниях. Минус один: реакция клика сдвигается на момент отпускания, на 100-150 мс позже касания. Человек этой разницы не чувствует: палец на обычном клике живёт 80-150 мс, и свет вспыхивает тогда, когда рука уже начала подниматься. Ночник я собрал на поздней архитектуре: случайная смена режима у прибора в детской дороже ста миллисекунд, а каждое сэкономленное состояние — это минус одна грабля.
Почему порог 2000 мс, а не 600 из соседнего урока: там удержание — третий жест меню, ему нужна скорость, и 600 мс отзывчивее. Здесь смена режима — дорогая операция, в которую легко попасть случайно, листая яркость ребёнку. Обычный клик живёт в пределах 150 мс, значит двухсекундный порог лежит в тринадцати и больше длинах клика — случайно задеть его невозможно, а осознанно держать две секунды рука успевает. Обратная цена та же, что у любой безопасности: две секунды без отклика кажутся отказом, и лечится это не порогом, а обратной связью. Порог при этом не высечен в камне: для прибора с крупными кнопками и перчатками я двигаю его до 2500, для пульта в руке — до 1500, и всегда проверяю на людях, а не на себе: у привыкшего пользователя порог ползёт вверх, у новичка вниз.
Автомат: состояния, отмена, отзыв
Состояний три. IDLE — ждём нажатие. PRESSED — нажатие зафиксировано, tPress запомнил millis(), флаг holdFired чист. HOLD — держат дольше порога: светодиод мигает трижды, holdFired взводится, и отпускание отсюда возвращается в IDLE без клика. Клик выдаётся ровно в одном месте: отпускание из PRESSED при чистом holdFired — переключаем свет и печатаем вердикт. Скелет машины тот же, что я раскладывал на светофоре в уроке про конечный автомат, ввод у неё другой — палец вместо таймера. Для приборов с несколькими кнопками этот же скелет уезжает в класс — как в уроке про класс Button, там массив объектов вытесняет зоопарк переменных.
Обратная связь — не украшение, а половина решения. Мигание при входе в HOLD говорит: отпусти, ты уже в режиме. Без него человек держит две секунды, не понимает, отпускает раньше — получает клик вместо режима, жмёт снова, и интерфейс зарабатывает репутацию глючного за один вечер. Посмотрите на любой пульт с жестами: длинное нажатие всегда отзывается вибрацией, миганием или щелчком — иначе его просто не находят. Режимы, в которые попадает кнопка после удержания, удобно раскидывать по switch — та же механика, что в уроке про switch-case.
const int PIN_BTN = 2;const int PIN_LED = 12;const unsigned long POROG = 2000; // порог удержания const int S_IDLE = 0;const int S_PRESSED = 1;const int S_HOLD = 2; int state = S_IDLE;unsigned long tPress = 0;bool holdFired = false; // удержание сработало — клик отменёнbool light = false;int flashLeft = 0; // половинки мигания: 6 = три вспышкиunsigned long tFlash = 0; void setup() { pinMode(PIN_BTN, INPUT_PULLUP); pinMode(PIN_LED, OUTPUT); Serial.begin(9600); Serial.println("click = svet, hold 2s = rezhim nastroyki");} void loop() { unsigned long now = millis(); bool pressed = digitalRead(PIN_BTN) == LOW; if (flashLeft > 0 && now - tFlash >= 200) { // отзыв: три вспышки tFlash = now; if (flashLeft % 2 == 0) digitalWrite(PIN_LED, HIGH); else digitalWrite(PIN_LED, LOW); flashLeft--; if (flashLeft == 0) digitalWrite(PIN_LED, light); } if (state == S_IDLE && pressed) { state = S_PRESSED; tPress = now; holdFired = false; Serial.println("nagatie"); } if (state == S_PRESSED && !holdFired && now - tPress >= POROG) { holdFired = true; state = S_HOLD; flashLeft = 6; tFlash = now; Serial.println("hold 2s: rezhim nastroyki, click otmenen"); } if (!pressed) { if (state == S_PRESSED) { // отпускание с чистым флагом = клик light = !light; digitalWrite(PIN_LED, light); Serial.println("click: svet pereklyuchen"); } if (state == S_HOLD) { Serial.println("otpuskanie: ostayus v rezhime"); } state = S_IDLE; }}Грабля, ради которой всё затевалось, выглядит так: клик обрабатывается прямо при нажатии, а удержание — на пороге. Один жест выдаёт два действия: свет уже мигнул в момент касания, а через две секунды включился ещё и режим. Убрать это позднее перечитыванием флагов почти никогда не выходит — свет переключён необратимо. Лечится только архитектурой: вердикт выносится в одном месте (отпускание или порог), а второй кандидат гасится флагом, как holdFired выше. И держите в голове порядок слоёв: перед автоматом на живой плате обязан стоять антидребезг — окно из урока про механику контакта, иначе отпускание выдаст серию кликов, и автомат честно их отработает. Вторая тихая засада — отзыв, съеденный петлей: если вспышки сделать на delay(600), то во время отзыва не работают ни счёт кликов, ни датчики, и следующее быстрое нажатие теряется; в скетче выше мигание отмерено millis и никому не мешает.
После сборки автомат проверяю на живом человеке, и вот что прошу сделать. Пять обычных кликов — в мониторе пять пар nagatie/click, светодиод щёлкается пять раз: значит, порог не задевается даже торопливой рукой. Десять удержаний — десять заходов в режим с миганием и ни одного лишнего клика между ними: значит, отмена работает. Самая показательная проба — отпускание у порога: держите 1,9 и 2,1 секунды вперемешку; если на 1,9 иногда проскакивает режим, порог стоит слишком близко к человеческой руке, и его стоит отодвинуть. В симуляторе то же самое кликается мышью: границу между кликом и удержанием видно в мониторе построчно.
И не смешивайте жесты на одной кнопке без нужды: клик, двойной клик и удержание в одном автомате — это три окна ожидания, которые конфликтуют между собой: двойной клик требует ждать после отпускания, а быстрый интерфейс требует не ждать. Вторая кнопка стоит дешевле пары месяцев споров с интерфейсом, а заодно снимает вопрос об окне двойного клика целиком. В моей детской лампе жестов два, и это потолок, к которому я вернулся после попытки впихнуть четвёртый. Пробы из предыдущего абзаца гоняйте на разных людях: темп руки у каждого свой, и цифры в мониторе скажут о пороге больше, чем любой совет.
| Где вердикт | Задержка клика | Отмена | Где применяю |
|---|---|---|---|
| на нажатии, сразу | нулевая | невозможна: свет уже переключился до удержания | когда клик и удержание делают совместимое: двойной клик меню |
| на пороге (ранняя) | нулевая | флаг holdFired гасит клик при отпускании | жесты с быстрым отзывом: 600 мс из урока про кнопку-автомат |
| на отпускании (поздняя) | плюс длина отпускания: 100-150 мс | отменять нечего: вердикт один | приборы, где случайный режим дороже сотни миллисекунд |
- 1. Клик обрабатывается сразу при нажатии, человек держит кнопку 2 с. Что произойдёт?
- 2. Поздняя архитектура: когда выносится вердикт клика?
- 3. Зачем в автомате флаг holdFired?
- 4. Почему порог 2000 мс, а не 600 мс из урока про кнопку-автомат?
- 5. Что даёт мигание светодиода при входе в режим настройки?
- Две архитектуры: вердикт на пороге с отменой клика или на отпускании без отмены — третьей нет
- Поздняя схема платит 100-150 мс задержки и покупает простоту: два состояния и ни одной отмены
- Порог 2000 мс лежит в 13+ длинах клика: случайно задеть невозможно; 600 мс — для быстрых жестов меню
- Флаг holdFired гасит клик после сработавшего удержания — иначе один жест выдаёт два действия
- Отзыв при входе в режим (мигание трижды) обязателен: без него пользователь жмёт дважды
Частые вопросы по теме урока
- Клик обрабатывается сразу при нажатии, человек держит кнопку 2 с. Что произойдёт?
- Свет уже переключён необратимо, а порог удержания выдаст ещё и режим. Лечится переносом вердикта в одно место и отменой второго кандидата флагом. Свет переключится и режим включится: один жест выдаёт два действия
- Поздняя архитектура: когда выносится вердикт клика?
- До отпускания ничего не решается: отпустили короче порога — клик, длиннее — режим. Отменять нечего, автомат живёт в двух состояниях, цена — 100-150 мс задержки. В момент отпускания
- Зачем в автомате флаг holdFired?
- После срабатывания удержания нажатие всё ещё числится незавершённым. Флаг запрещает ветке отпускания выдать клик — без него жест снова даст два действия. Гасит клик при отпускании, если удержание уже сработало
- Почему порог 2000 мс, а не 600 мс из урока про кнопку-автомат?
- Порог лежит в тринадцати и больше длинах обычного клика: случайно в него не попасть. Обратная цена — две секунды тишины, которую прикрывает мигание-отзыв. Смена режима — дорогая операция, случайный задев дороже двух секунд ожидания
- Что даёт мигание светодиода при входе в режим настройки?
- Без отзыва две секунды тишины кажутся отказом: пользователь отпускает раньше, получает клик и жмёт снова. Отзыв — половина решения, а не украшение. Ускоряет срабатывание клика