Сценарий знакомый до зубной боли: на форуме нашёлся скетч ровно под вашу задачу — ИК-выключатель для гирлянды, пятнадцать строк, комментарии заботливые. Вставляете, жмёте загрузку — и компилятор выкатывает простыню: decode(&results) объявлен устаревшим, тип decode_results не найден, дальше полэкрана красных строк. Виновата не рука набравшего, а поколение библиотеки: тот скетч писался под IRremote 2.x или 3.x, а в Менеджере библиотек у вас четвёртая мажорная версия, вышедшая в 2021 году. Между поколениями перепахали весь слой декодирования, и это не переименование пары имён: сменилась сама единица, которой библиотека отвечает на вопрос «что прилетело».
В третьей версии декодер возвращал плоскую структуру decode_results, и вся добыча лежала в одном 32-битном поле results.value — сырое слово без разборов. Четвёртая выдаёт структуру decodedIRData, где кадр уже разложен по полочкам: протокол, адрес, команда, флаги повтора. Оба поколения API и порядок установки разобраны в уроке про установку IRremote 4 — я не пересказываю его, а решаю одну задачу: у вас в руках чужой старый скетч, и надо за пятнадцать минут понять, что в нём переименовать, что вычеркнуть, а что честнее переписать с нуля.
Хорошая новость: девяносто процентов переезда — пять механических замен, сводимых в таблицу ниже. Плохая: оставшиеся десять процентов не сводятся ни к чему, и именно на них сгорают вечера. Разберу обе части по порядку. И запасной план есть и для худшего случая: скетч, который не переезжает за полчаса, почти всегда просит не миграции, а свежей реализации на новых полях.
Что именно сломалось: decode(&results) и results.value
Смотрите на старый скетч из любого туториала 2015 года: IRrecv irrecv(RECV_PIN); decode_results results; в setup — irrecv.enableIRIn(), в loop — if (irrecv.decode(&results)). В четвёртой версии от этого каркаса остался только приёмник, и то с новым именем. Он теперь синглтон IrReceiver: старт — IrReceiver.begin(пин, ENABLE_LED_FEEDBACK), decode() зовётся без аргументов, а после обработки ветки обязан идти IrReceiver.resume(). Компилятор в 4.x подсказывает прямо в предупреждении: функция decode(&results) устарела и может работать не так, как ожидается, — пользуйтесь decode() без параметров. Редкая удача: библиотека сама пишет маршрут переезда.
enableIRIn() из второго поколения заменён целиком на begin(): старт, настройка таймера и опциональная индикация пином 13 через ENABLE_LED_FEEDBACK — один вызов вместо двух. Если в чужом скетче enableIRIn() стоит ещё и внутри loop(), это признак не только старой версии, но и старой логики: раньше приёмник перезапускали после каждой передачи, в 4.x приём возвращается сам — подробности в уроке про неблокирующий resume.
Вторая половина поломки — чтение результата. В results.value лежало сырое 32-битное слово: у NEC-пультов там адрес, команда и их инверсии, склеенные в одну пачку. Как эта пачка устроена по байтам, я раскладывал в уроке про 32 бита NEC, здесь важен вывод: одно число несло три смысла, и каждый скетч расшифровывал его своими сдвигами и масками. Четвёртая версия забрала расшифровку себе.
#include <IRremote.h> int RECV_PIN = 11;IRrecv irrecv(RECV_PIN);decode_results results; void setup() { Serial.begin(9600); irrecv.enableIRIn();} void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); if (results.value == 0xFFFFFFFF) { // так отделяли повтор Serial.println("povtor"); } irrecv.resume(); }}#include <IRremote.hpp> const int RECV_PIN = 11; void setup() { Serial.begin(9600); IrReceiver.begin(RECV_PIN, ENABLE_LED_FEEDBACK); Serial.println("4.x: zhmi knopki pulta");} void loop() { if (IrReceiver.decode()) { if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT) { Serial.println("povtor"); // флаг вместо сравнения с 0xFFFFFFFF } else { Serial.print("adres 0x"); Serial.print(IrReceiver.decodedIRData.address, HEX); Serial.print(", komanda 0x"); Serial.println(IrReceiver.decodedIRData.command, HEX); } IrReceiver.resume(); }}Сопоставьте листинги: имена сменились (irrecv → IrReceiver, enableIRIn → begin), аргумент у decode исчез, а главное — вместо единого results.value вырос набор полей. Адрес и команда разделены, протокол назван, признак повтора вынесен в флаги. Проверка if (results.value == 0xFFFFFFFF), которой в старых скетчах отделяли повторные кадры, в 4.x теряет смысл: у повтора есть честный флаг IRDATA_FLAGS_IS_REPEAT, а механика повторных кадров разобрана в уроке про NEC-повторы.
Ловушка посередине: соблазн заменить results.value на decodedIRData.decodedRawData и больше ничего не трогать. С пультом из набора это даже заработает — в decodedRawData лежит то же сырое слово. Но сырьё не знает про флаги и про байтовый порядок протоколов, у которых слово развёрнуто иначе: сравнения с константами начнут врать на чужих пультах и на повторах. Честный переезд — на address и command; сырьё оставить для неопознанных кадров и сырых дампов. Второй тихий случай — numberOfBits: старые проверки results.bits != 32 после переезда сравнивают с пустотой, потому что поле в новой структуре называется иначе.
Пять замен и два случая, когда скетч честнее переписать
Рабочий порядок переезда, проверенный на дюжине форумных находок. Первым делом меняете шапку на IRremote.hpp, а конструктор с enableIRIn() — на IrReceiver.begin(). Вторым — decode(&results) на decode() без аргументов. Третьим — каждое results.value разворачиваете в поля: адрес, команду, флаги. Четвёртым — resume() переносите на IrReceiver.resume() и проверяете, что он стоит после всей обработки, а не в начале ветки. Пятый шаг — компиляция и сверка чисел в мониторе с тем, что печатал старый скетч на том же пульте.
Переписывать целиком честнее в двух случаях. Первый — скетчи на sendRaw и ультрасырых интервалах: там логика завязана на структуру decode_results целиком, и половина переноса превращается в археологию; кадр по интервалам мы разбирали в уроке про разбор кадра, оттуда же берётся готовый путь для неопознанных пультов. Второй случай — скетчи, трогающие таймеры руками: tone(), Servo, прямые записи в регистры. На Uno приём IRremote держит второй таймер, и любые соседи по нему конфликтуют — разбор собран в уроке про таймер 2.
И последнее, про качество находки. Форумный скетч писался под конкретный пульт, и его константы вам ничего не обещают: сначала прогоните мигрированный код как читалку — пусть печатает адрес и команду каждой вашей кнопки, — потом возвращайте свои константы. Готовый приём словаря команд в PROGMEM лежит в соседних уроках раздела. А если в старом скетче циклы на delay(), замените их на millis-планировщик из урока про millis вместо delay — иначе приёмник начнёт ронять кадры, сколько байт вы ни переименуете.
Правило грубое, но рабочее: если чужой скетч занимает полэкрана, а вы помните только его назначение, переезд почти всегда проигрывает переписыванию по шаблонам раздела. Половина форумных находок живёт с избыточной обвязкой — Serial.begin без монитора, бесполезные флаги, двойные декларации, — и миграция тащит этот мусор за собой в новый проект. Переезд берёт тот, кто помнит и код, и его замысел; остальным честнее начать с чистого листа.
| Старое (2.x/3.x) | Новое (4.x) | Что учесть |
|---|---|---|
| #include <IRremote.h> | #include <IRremote.hpp> | старый .h оставлен для совместимости, жить лучше в новом |
| IRrecv irrecv(pin); irrecv.enableIRIn(); | IrReceiver.begin(pin, ENABLE_LED_FEEDBACK) | старт, таймер и индикация пином 13 — один вызов |
| irrecv.decode(&results) | IrReceiver.decode() | без аргументов; компилятор подсказывает это в предупреждении |
| results.value; == 0xFFFFFFFF | decodedIRData.command/.address; flags & IRDATA_FLAGS_IS_REPEAT | сырьё — в decodedRawData, сравнения с константами пересчитать |
Разберу переезд на живом примере, как он прошёл у меня на прошлой неделе. С форума приехал скетч ночника 2016 года: IRremote.h в шапке, irrecv.decode(&results), сравнение results.value == 0xFFA25D и гашение по 0xFFFFFFFF. Первые пять минут — шапка и begin(): декларации меняются за один проход, компилятор смолкает. Ещё пять — decode() без аргументов и перенос resume() на место после обработки. Десятую минуту съело самое главное: разворот сравнений. Печать command и address одной строкой показала: код кнопки — 0x45 при адресе 0xFF00, а старая константа 0xFFA25D — то же слово, прочитанное целиком.
Гашение по 0xFFFFFFFF заменилось флагом IRDATA_FLAGS_IS_REPEAT, и тут же вскрылась вторая деталь: старый скетч глушил лампу на каждом повторе, потому что сравнение с сырым словом ловило и удержание кнопки. Мигрированная версия различила первое нажатие и повтор — поведение выровнялось без единой строчки про тайминги. Итого пятнадцать минут, из которых десять ушло на разворот сырого слова в поля.
Сколько переездов ни делаю, порядок всегда один: шапка, старт, decode, resume, и только потом — поля и константы. Если начать с полей, компилятор завалит сообщениями про несуществующие имена, и маршрут переезда потеряется в шуме. Пятнадцать минут дисциплины экономят вечер; мигрированный листинг после этого читается как чужой, но понятный текст, а не как архивная загадка.
- 1. Скетч с форума печатает предупреждение про decode(&results). Какой вызов правильный для IRremote 4.x?
- 2. Где в 4.x лежит команда кнопки NEC?
- 3. Старый скетч отделял повтор строкой results.value == 0xFFFFFFFF. Как это делается в 4.x?
- 4. Что заменяет irrecv.enableIRIn() в четвёртой версии?
- 5. Скетч мигрировали заменой results.value на decodedIRData.decodedRawData, сравнения с константами оставили. Что произойдёт?
- Диалект узнаётся по двум словам: IRremote.h и enableIRIn() — старьё; IRremote.hpp и IrReceiver.begin() — четвёртая версия
- decode(&results) → decode() без аргументов, результат в IrReceiver.decodedIRData: protocol, address, command, flags
- Повтор ловится флагом IRDATA_FLAGS_IS_REPEAT, сравнением с 0xFFFFFFFF в 4.x не ловится
- Замена на decodedRawData возвращает сырьё: родной пульт узнается, повторы и чужие протоколы — нет
- Целиком переписывают скетчи на sendRaw и ручных таймерах; константы старого скетча сначала переснимают своей читалкой
Частые вопросы по теме урока
- Скетч с форума печатает предупреждение про decode(&results). Какой вызов правильный для IRremote 4.x?
- В четвёртой версии decode() не принимает параметров: результат живёт в глобальном IrReceiver.decodedIRData. Старая сигнатура с указателем на структуру осталась в предупреждении как устаревшая и полноценно не работает. IrReceiver.decode() без аргументов
- Где в 4.x лежит команда кнопки NEC?
- Команда — decodedIRData.command, адрес — decodedIRData.address, сырые 32 бита — decodedRawData. Флаг повтора отношения к команде не имеет: он лишь помечает кадр как повторный. в decodedIRData.command
- Старый скетч отделял повтор строкой results.value == 0xFFFFFFFF. Как это делается в 4.x?
- Повторные кадры в 4.x приходят разобранными, признак — флаг. Сравнение с сырым словом ненадёжно: 0xFFFFFFFF у сырых кадров встречается и по другим причинам, не связанным с повтором. проверить флаг IRDATA_FLAGS_IS_REPEAT
- Что заменяет irrecv.enableIRIn() в четвёртой версии?
- begin() стартует приёмник, настраивает таймер и при желании включает индикацию пином 13. enableIRIn() — подпись второго поколения, по ней старый скетч узнаётся мгновенно. IrReceiver.begin(pin, ENABLE_LED_FEEDBACK)
- Скетч мигрировали заменой results.value на decodedIRData.decodedRawData, сравнения с константами оставили. Что произойдёт?
- decodedRawData — то же сырьё, поэтому родной пульт узнается. Но флагов повтора и байтовых порядков чужих протоколов в сыром слове нет: сравнения начнут врать там, где раньше совпадали. на родном пульте сработает, на чужих протоколах и повторах поведение поплывёт