В гостиной у меня теперь три пульта: телевизор, звуковая колонка и приставка. Макетка-роутер на полке слушала их всех: свет включался от PLAY, реле вентилятора щёлкало от чего-то ещё. Планировалось хозяйство, а вышло наоборот: покрутишь громкость на колонке — свет мигал; переключишь канал приставки — вентилятор вздрагивал. Сосед по дивану спросил, у меня всё в порядке.
Разобрался быстро, но вывод выписал на бумажку: скетч читал только command и не смотрел address. Команда — номер кнопки внутри своего пульта: у колонки и приставки нашлись одинаковые байты на разных кнопках, и каждый кадр, до которого дотягивался приёмник, честно исполнялся. А кадр NEC несёт восемь бит адреса — паспорт устройства, — и в уроке про анатомию NEC на него уже вешали простую защиту от чужих пультов. Здесь шаг дальше: не игнорировать чужие, а маршрутизировать все.
Цель урока — скетч, который слушает три пульта и каждому отдаёт свои действия: свет, вентилятор, громкость. Плюс разбор ловушки, на которой я погорел второй раз: у безымянных пультов адрес совпадает, и фильтр по нему бессилен.
Адрес: восьмибитный паспорт устройства
Восемь бит адреса — 256 значений, и производитель раздаёт их своей технике, чтобы пульт телевизора не переключал каналы ресиверу. Это не шифрование и не защита от соседей — внутренняя маршрутизация семьи устройств. Для нас адрес — готовый ключ: сравнение одного байта отделяет пульт ТВ от пульта колонки без всяких таблиц соответствия и без режима «угадай по кнопке». И это же самый дешёвый фильтр во всём пультовом деле: ни прерываний, ни таймеров, одна строка if.
Снимается адрес той же читалкой из первого урока раздела: жмите кнопку — сводка печатает протокол, адрес и команду одной строкой. Пять минут на три пульта, и у вас три константы. В шапку скетча я вписываю их сразу в hex — привычка из урока про bin, hex и dec: 0x1C в сравнении читается глазами, а 28 — уже нет. И не берите адреса из чужих статей: у типового пульта из набора ноль, у фирменной техники свои диапазоны, и эта часть таблицы снимается только дома, на своих кнопках.
| Способ разделения | Как выглядит в скетче | Когда хватает |
|---|---|---|
| по адресу | if (addr == MY_ADDR) | фирменная техника: у ТВ, колонки и приставки свои диапазоны |
| по паре адрес + команда | if (addr == A && cmd == C) | дом с двумя безымянными пультами, у обоих 0x00 |
| по протоколу | protocol и numberOfBits | NEC-пульт и RC5 живут в одной комнате |
| геометрией | приёмник развёрнут в сторону комнаты | когда коды не разделить: прямой луч бьёт надёжнее чужого с другого конца |
Ловушка, из-за которой у меня мигал свет: у безымянных пультов адрес одинаковый. Пульт из набора Arduino, пульт от люстры, ещё пара клонов — у всех 0x00. Производители дешёвых пультов не заморачиваются: нулевой адрес в их прошивках — норма. Фильтр вида addr == 0 пропускает всех без разбора, и защита от чужих пультов защищала от всех, кроме виновников.
Лечится по тяжести симптома. Совпадают адреса, но команды разные — привязывайте действие к паре адрес плюс команда: команда 21 с адреса 0x00 от пульта люстры в моей таблице не значила ничего, а от пульта набора — прибавляла яркость. Совпадает и пара — остаются два хода: развести приёмник и пульт геометрией, ведь ИК идёт лучом и отражениями, и близкий прямой луч бьёт надёжнее чужого с другого конца комнаты, или заменить один из близнецов. У меня победила замена: новый пульт люстры вписался в таблицу отдельным адресом.
Мнение после пары лет с пультовым хозяйством: карту адресов снимайте у себя и держите в шапке скетча. Два пульта одной модели делят и адрес, и команды — тут фильтры бессильны в принципе, и честнее отодвинуть макетку от прямого луча, чем искать несуществующую разницу в кодах.
#include <IRremote.hpp> const int IR_PIN = 2;const int RELE = 7; // свет через реле// адреса снимайте своей читалкой, а не из чужих статейconst byte ADDR_SVET = 0x00; // пульт из набора - светconst byte ADDR_ZVUK = 0x11; // пульт колонки - громкость (впишите свой)const byte ADDR_TV = 0x1C; // пульт телевизора - пока игнорируем void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); pinMode(RELE, OUTPUT);} void loop() { if (IrReceiver.decode()) { byte addr = IrReceiver.decodedIRData.address; byte cmd = IrReceiver.decodedIRData.command; IrReceiver.resume(); // всегда, даже для чужих адресов Serial.print("addr=0x"); Serial.print(addr, HEX); Serial.print(" cmd="); Serial.println(cmd); if (addr == ADDR_SVET) { if (cmd == 67) { digitalWrite(RELE, HIGH); } if (cmd == 70) { digitalWrite(RELE, LOW); } } else if (addr == ADDR_ZVUK) { if (cmd == 21) { Serial.println("kolonka=louder"); } if (cmd == 7) { Serial.println("kolonka=quieter"); } } // пульт ТВ сознательно молчит: чужой адрес - чужие дела }}Один скетч — три устройства
Роутер собирается из того же словаря: addr выбирает устройство, cmd — действие. Снаружи сравнение адреса, внутри — switch по команде. Приёмник при этом один, пины не кончаются, а устройств может быть больше трёх: с реле роутер управляет светом, с ИК-диодом передаёт команды технике, которая иначе не слушается, а с HTTP-шлюзом из первого урока этой тройки — сервером умного дома. Таблица маршрутов растёт вниз, скелет не меняется, и новый прибор — это новая ветка, а не новый скетч.
Границы приёма честные: 256 адресов на протокол выглядят просторно, но у двух пультов одной фирмы совпадают целиком — и адрес, и команды. Поэтому маршрутизация работает, пока пульты из разных семей, и ломается на близнецах, о чём говорила таблица выше. И ещё: не каждое совпадение — беда. Сонный вечер, один пульт на всю комнату, и совпавшая кнопка становится фичей, а не полтергейзмом.
Прежде чем клеить таблицу маршрутов, снимите карту конфликтов: понажимайте оба пульта в читалку и выпишите пары, где команда совпала. Пять минут до сборки экономят вечер с мигающим светом. Симулятор ниже показывает скелет такого роутера на одном пульте — адреса в нём нет, зато порядок «кадр, паспорт, маршрут» виден на каждой строке.
- Адрес — восемь бит паспорта устройства: 256 значений, производитель раздаёт их своей технике
- Адрес снимается читалкой за пять минут и вписывается в константу — не из чужих статей
- У безымянных пультов адрес 0x00: фильтр по адресу пропустит всех близнецов
- Второй рубеж — пара адрес + команда; при полном совпадении — геометрия или замена пульта
- Роутер: addr выбирает устройство, cmd — действие; приёмник один, устройств сколько угодно
- Печатайте addr до фильтра: строка лога заменяет гадание по кнопкам
- 1. Два пульта шлют одну и ту же команду 21. Что различит их в скетче?
- 2. Пульт из набора и пульт люстры оба имеют адрес 0x00. Что сделает фильтр addr == 0?
- 3. Где взять адрес своего пульта для константы?
- 4. Где в скетче-роутере стоит IrReceiver.resume()?
- 5. Сколько разных адресов помещается в восемь бит?
Адресный фильтр закрывает и последнюю пультовую загадку — фантомные команды без видимого пульта: у любого принятого кадра есть адрес, и печать addr показывает, чей он. Почему приёмник вообще видит чужие кадры — отражения, наводки по питанию и слепые зоны — разобрано в уроке про подключение приёмника. А если в руки попался пульт вне NEC — Sony с его пятибитным адресом устройства или RC5 с toggle-битом, — словарь для них лежит в уроке про другие протоколы. Пультовое хозяйство теперь читается как открытая книга: кадр, паспорт, маршрут.
Частые вопросы по теме урока
- Два пульта шлют одну и ту же команду 21. Что различит их в скетче?
- Команда — номер кнопки внутри своего пульта; границу между устройствами рисует восьмибитный адрес. Одно сравнение байта — и чужой кадр уходит мимо. Адрес из decodedIRData.address
- Пульт из набора и пульт люстры оба имеют адрес 0x00. Что сделает фильтр addr == 0?
- Нулевой адрес у безымянных пультов — норма прошивок. Фильтр честно пропустит обоих, различать придётся парой адрес плюс команда или геометрией. Пропустит оба: различить их по адресу невозможно
- Где взять адрес своего пульта для константы?
- Адрес снимается с собственного пульта читалкой из первого урока раздела. Чужие таблицы не совпадают, а случайное число фильтр просто отвергнет вместе с вашими командами. Из читалки: навести пульт и напечатать decodedIRData.address
- Где в скетче-роутере стоит IrReceiver.resume()?
- resume() освобождает буфер и обязан вызываться для каждого кадра, включая отфильтрованные чужие. Иначе роутер застрянет на первой же чужой посылке. Вне фильтра адреса — для любого принятого кадра
- Сколько разных адресов помещается в восемь бит?
- Восемь бит дают 256 значений. Просторно, но близнецы одной модели делят адрес целиком — адрес не защита от дубликатов, а паспорт семьи устройств. 256 — от 0x00 до 0xFF