Ночник у кровати включается в темноте: глаз не хочет искать кнопку, а рука не хочет щуриться. Пульт из набора лежит рядом, и у него есть одна выпирающая кнопка EQ — идеальный кандидат под палец. Из ограничения родился интерфейс: раз кнопка одна, пусть она листает сцены по кольцу — выкл, ночник, дремота, ярко — и снова выкл. Каждое нажатие — шаг, четыре нажатия — полный круг. За месяц привычки большего не понадобилось ни разу.
С диммером из урока про клик и удержание родство здесь только по выходу: там палец едет по уровню непрерывно, здесь уровень прыгает между четырьмя фиксированными сценами, и промежуточных значений нет нарочно. Машина состояний из трёх байтов — номер сцены, целевой уровень, текущий выход — даёт интерфейс, который не надо объяснять: ткнул — сцена сменилась, и глаз сам видит, в какой точке кольца находишься.
Числа сцен — мои: 30 из 255 для ночника, 120 для чтения, полный свет для поиска упавшего под кровать. Они подобраны рукой под лампу на 30 ватт и комнату в десять метров; вашим глазам захочется других значений, и правятся они в одном массиве за минуту.
Рубильник из урока про панель команд здесь сознательно проиграл. Выключатель «там-да-нет» заставляет вспоминать, на каком уровне ночник остался вчера; кольцо само возвращает точку отсчёта — следующее нажатие всегда следующий шаг. Состояние живёт в одной переменной и честно видно по яркости: машина состояний здесь не пафос, а три строчки кода, которые спасают интерфейс от лишних вопросов.
Кольцо и замок шага
Кольцо — это массив уровней и один указатель: каждый валидный кадр прибавляет единицу по модулю четырёх. Вся сложность — в слове «валидный». NEC-пульт при удержании шлёт repeat-кадры каждые 108 миллисекунд, и без фильтра одно удержание прокручивает кольцо девять сцен в секунду: палец дрогнул — вы уже на противоположной стороне круга. Флаг IRDATA_FLAGS_IS_REPEAT отсекает повторы, а замок шага в 300 миллисекунд ставит барьер и честным полным кадрам: два быстрых щелчка не утащат две сцены. Фильтр в связке с millis-окном разобран в уроке про фильтр повторов.
Доезд уровня я оставил прежним — единица за 4 миллисекунды, та самая механика из урока про диммер: прыжок от нуля к 120 бьёт по глазам в темноте, а полсекунды подъёма делают смену сцены мягкой. Почему именно EQ: у пульта из набора она выпирает и стоит особняком, рука находит клавишу без света; в скетче команда — константа, переклейка на любую другую стоит одной строки. Скетч:
// Цикл сцен ночника: EQ шагает по кольцу из четырёх сцен#include <IRremote.hpp> const int IR_PIN = 11;const int LED_PIN = 9; // ШИМ вне Таймера 2: на плате берите 9 или 10 const int CMD_STEP = 9; // EQconst byte LEVELS[4] = {0, 30, 120, 255}; // выкл, ночник, дремота, яркоconst char* NAMES[4] = {"vykl", "nochnik", "dremota", "yarko"}; const unsigned long STEP_LOCK = 300; // замок шага, мс byte scene = 0;int out = 0;unsigned long lastStep = 0;unsigned long lastRamp = 0; void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); pinMode(LED_PIN, OUTPUT); Serial.println("sceny: EQ - shag po kolcu");} void loop() { if (IrReceiver.decode()) { bool isRepeat = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT; int cmd = IrReceiver.decodedIRData.command; if (cmd == CMD_STEP && !isRepeat && millis() - lastStep > STEP_LOCK) { lastStep = millis(); scene = scene + 1; if (scene > 3) scene = 0; // кольцо замкнулось Serial.print("scena "); Serial.print(scene); Serial.print(" - "); Serial.print(NAMES[scene]); Serial.print(", uroven "); Serial.println(LEVELS[scene]); } IrReceiver.resume(); } if (millis() - lastRamp >= 4) { // доезд: единица за 4 мс lastRamp = millis(); int target = LEVELS[scene]; if (out < target) out = out + 1; if (out > target) out = out - 1; analogWrite(LED_PIN, out); }}Обратите внимание на порядок проверок: сначала флаг повтора, потом команда, потом замок. Каждая ступень дешёвая, но их порядок определяет поведение: поменяйте местами команду и замок — и честный второй щелчок на 299-й миллисекунде молча пропадёт; уберите флаг после команды — удержание прорвётся в кольцо. Такие мелочи не видны в тёплой комнате и вылезают в темноте, когда скетч уже живёт у кровати.
Граница применимости кольца — четыре-пять сцен. Дальше палец теряет счёт кругам, и интерфейс честнее отдать меню: пульт листает строки, каждая строка — своя сцена, приём разобран в уроке про меню режимов. У кровати я остался на четырёх — больше не понадобилось ни разу, и это не лень, а верхний предел, где кольцо перестаёт быть предсказуемым.
Про подбор уровней есть короткий метод. Вечером, когда глаза устали, включите вторую сцену и крутите уровень с шагом десять, пока не станет «чтобы дойти до кровати». Повторите с третьей сценой при светлом экране телефона. Два значения, подобранные под свои глаза, живут годами: мои 30 и 120 не менялись с первой недели.
Старт-сцена и спор о памяти
Сцена при подаче питания — нулевая: ночник просыпается погашенным. Решение противоречивое, и я его осознанно защищаю: после моргания света посреди ночи лампа, честно зажёгшаяся на вчерашнем уровне, станет сюрпризом; погашенный ночник сюрпризов не делает. Тем, кому память нужнее тишины, дорога в EEPROM показана в уроке про обучаемый пульт, а привязки кнопка-действие без EEPROM — в уроке про обучение привязок в ОЗУ.
Спор о направлении кольца я закрыл в одну сторону. Кольцо с шагом вперёд и назад по двум кнопкам логичнее, но у кровати в темноте искать вторую кнопку — работа для глаз; одна выпирающая клавиша и лишняя секунда пути по кругу выигрывают у навигации в обе стороны. Это моё мнение, и оно сломалось бы в гостиной: там пульт лежит на виду, и симметричные пары кнопок возвращаются.
И про пару «цель и текущий». Переменных две не для красоты: target хранит сцену, out — то, что сейчас на пине, и доезд едет между ними. Писать на пин прямо из обработчика кадров — сцена прыгнет рывком; хранить только out — кольцо потеряет цель при быстром двойном шаге. Один и тот же скелет работает у диммера, у дыхания розетки и здесь — повторяется он нарочно: один скелет переезжает между приборами вместе с привычками.
Отдельно про нижний уровень. Тридцать из 255 на моей лампе — не произвол, а порог различимости в темноте: ниже глаз не видит ничего, и сцена «ночик» превращается в гадание. Свой порог ищется за минуту: включайте уровень в темноте и убавляйте, пока свечение не перестанет различаться, потом добавьте десять сверху на старость. Та же грабля мёртвой зоны живёт у диммера из соседнего урока — там она разобрана подробнее.
| Сцена | Уровень ШИМ | Когда уместна |
|---|---|---|
| 0 — выкл | 0 из 255 | день и сон: старт-сцена после подачи питания |
| 1 — ночник | 30 из 255 | дойти до кровати, не разбудив соседа по комнате |
| 2 — дремота | 120 из 255 | чтение перед сном при тёплом свете |
| 3 — ярко | 255 из 255 | поиск упавшего под кровать, уборка |
Что дальше по этой ветке. Сцены прекрасно дружат с таймаутом: ночная сцена сама гаснет через полчаса — той же механикой пройденного пути, что в уроке про таймер розетки, только вместо реле гасится ШИМ. Второй шаг — цвет: лента WS2812B держит сцены из оттенков, и кольцо остаётся тем же, меняется только смысл уровня.
Отдельно про таймер на плате: приёмник держит Таймер 2, и ШИМ на пинах 3 и 11 с ним не дружит — на настоящей плате с уровнями яркости берите пин 9 или 10, как в уроке про диммер; в симуляторе таймеров нет, и диод спокойно живёт на своём двенадцатом пине. Разбор конфликта таймеров — в соседнем уроке раздела, здесь он просто учтён выбором пина.
И последняя оговорка. Кольцо — интерфейс одной кнопки, и сила его в предсказуемости: всегда следующий шаг, всегда по кругу. Как только сценам понадобится произвольный порядок или подтверждение выбора, кольцо своё отработало — дальше начинается меню, и это здорово: у каждого интерфейса своя комната.
- 1. Зачем кольцу сцен замок шага в 300 миллисекунд?
- 2. Чем кольцо сцен отличается от диммера с разгоном?
- 3. Почему ночник просыпается в нулевой сцене, а не на вчерашнем уровне?
- 4. Что случится без гашения флага IRDATA_FLAGS_IS_REPEAT?
- 5. Когда кольцо сцен пора менять на меню?
- Кольцо — массив уровней и один указатель: валидный кадр прибавляет единицу по модулю числа сцен.
- Флаг IRDATA_FLAGS_IS_REPEAT и замок 300 мс работают парой: один гасит удержание, другой — серию быстрых щелчков.
- Порядок проверок важен: флаг повтора, затем команда, затем замок — перестановка молча меняет поведение.
- Пара «target и out» даёт плавный доезд единицей за 4 мс и не теряет цель при двойном шаге.
- Старт в нулевой сцене — осознанный выбор предсказуемости против памяти на EEPROM.
Частые вопросы по теме урока
- Зачем кольцу сцен замок шага в 300 миллисекунд?
- Замок ставит барьер полным кадрам: два быстрых щелчка ближе 300 мс считаются одним шагом. Флаг повтора при этом убирает удержание целиком — они работают парой, а не вместо друг друга. чтобы серия честных кадров не утащила две сцены за одно быстрое нажатие
- Чем кольцо сцен отличается от диммера с разгоном?
- У диммера промежуточные уровни — суть интерфейса; у кольца их нет нарочно: сцена — точка на круге, и глаз сам видит, где находишься. Выход ШИМ общий, контракт управления разный. диммер едет по уровню непрерывно, кольцо прыгает между фиксированными сценами
- Почему ночник просыпается в нулевой сцене, а не на вчерашнем уровне?
- После моргания света предсказуемость важнее памяти: погашенный ночник сюрпризов не делает. Тем, кому память нужнее, дорога в EEPROM — приём слотов показан в уроке про обучаемый пульт. память ОЗУ чиста, а EEPROM для ночника я сознательно не пишу: зажёгшийся после сброса свет — сюрприз посреди ночи
- Что случится без гашения флага IRDATA_FLAGS_IS_REPEAT?
- Repeat-кадры идут каждые 108 мс, и каждый прорвался бы как шаг. Замок 300 мс пропускает один из трёх — этого мало. Только пара «флаг + замок» делает кольцо ручным. одно удержание прокрутит кольцо со скоростью около девяти сцен в секунду
- Когда кольцо сцен пора менять на меню?
- Кольцо живёт на трёх-четырёх сценах, где шаг предсказуем. Шесть и больше — интерфейс отдаётся меню с экраном: пульт листает строки, каждая строка выбирает сцену явно. когда сцен больше пяти: палец теряет счёт кругам, нужен выбор по кодам или строкам меню