Проект на IRremote 2.x похож на старую мебель: работает годами, трогать не хочется. У меня таким был выключатель гирлянды на Uno, собранный по туториалу десятилетней давности, — два года на стене без единого сбоя. Сломала его не логика, а привычка: для нового прибора понадобилась та же библиотека, я поставил из Менеджера свежую четвёртую версию, а заодно решил «обновить» и старый проект. Проверка выкатила простыню красного текста, и скетч, переживший два переезда квартиры, перестал собираться за одну минуту.
К миграции раздел подходил уже дважды и с разных сторон: установку четвёртой версии и синглтоны IrReceiver и IrSender разобрали в уроке про установку IRremote 4, а перетаскивание чужих скетчей с форумов — в уроке про форумную миграцию. Оба ведут читателя от скетча к скетчу; здесь я кладу переезд в одну плоскость: полная карта «старое → новое», сторона передатчика, которой там не хватало, и привычка читать красный текст компилятора как диагноз, а не приговор. По трём типовым строкам диагноз ставится быстрее, чем гуглится, и это не красота слов — после третьего проекта я проверяю.
Половина переезда — механические замены, и они сведены в таблицу ниже. Вторая половина — смысловая: повтор при удержании перестал быть кадром 0xFFFFFFFF и стал флагом, а сырое слово сменило разрядность на новых платах. Обе ловушки компилятор молча пропускает: код собирается, а поведение плывёт — именно на них сгорают вечера, поэтому после таблицы разбираю их отдельно.
И одна оговорка про источник. Если скетч кочует по проекту третий год, первым делом загляните в Менеджер библиотек — процедуру установки я разбираю в отдельном уроке: число рядом с IRremote — это и есть поколение, на которое вы мигрируете. Откатить версию на старую ради старого скетча можно, но это прокат на один сезон: следующий проект всё равно пересядет на четвёртую, и платить за переезд придётся дважды.
Три строки, которые ставят диагноз
Первая строка — 'enableIRIn' was not declared in this scope или её вариант 'class IRrecv' has no member named 'enableIRIn'. Это визитка второго поколения: метод снят с продажи, а вместе с ним и объект — IRrecv irrecv(RECV_PIN) заменён синглтоном IrReceiver, который библиотека создаёт сама. Старт теперь один вызов: IrReceiver.begin(пин, ENABLE_LED_FEEDBACK). Заодно это готовая проверка переезда: флаг в begin() мигает светодиодом платы на каждый принятый кадр, и живость приёмника видно без монитора — самый дешёвый индикатор в разделе, и после переезда он ценнее любых логов, потому что отделяет «кадры не летят» от «кадры летят, а скетч не читает» одним взглядом на плату.
Вторая строка — 'decode_results' does not name a type. Структуру decode_results удалили ещё в третьей версии, в феврале 2021 года; в четвёртой от decode(&results) осталась тень: она живёт в устаревшем заголовке IRremote.h, печатает в монитор подсказку про API версии 2.0 и терпеливо ждёт переезда. Новое чтение результата — IrReceiver.decode() без аргументов, добыча лежит в decodedIRData, после ветки обязан идти resume(). Третья строка ловит передатчик: no matching function for call to 'IRsend::sendNEC(long unsigned int, int)' — сигнатура сменилась, сырого слова и числа бит больше нет.
Четвёртая строка встречается реже, но сбивает с толку сильнее: 'resume' was not declared in this scope или приёмник, который ловит первую кнопку и молчит дальше. Это не новая версия виновата, а старая логика внутри loop: во втором поколении приёмник перезапускали enableIRIn() после каждого кадра, и в мигрированном скетче эта строка иногда выживает, глотая всё после первого кадра. Четвёртая версия возобновляет приём через resume() после ветки — почему без него буфер стоит, разобрано в уроке про неблокирующий resume.
Привычка, которая экономит по вечеру: читать первую красную строку, а не последнюю. IDE выводит сообщения в обратном порядке, и внизу простыни лежит мусор от каскада — одна настоящая причина, раздутая десятком следствий. После третьей замены по таблице каскад умирает сам: девять из десяти строк диагноза в старом ИК-скетче — следы одних и тех же пяти переименований, а не пять разных поломок.
#include <IRremote.hpp> // вместо IRremote.h const int IR_PIN = 11; // IRrecv irrecv(IR_PIN); // старое: объект создавали сами// decode_results results; // старое: структура результата void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); // вместо irrecv.enableIRIn()} void loop() { if (IrReceiver.decode()) { // вместо irrecv.decode(&results) // results.value -> decodedIRData.decodedRawData (сырое слово) // результат по кнопкам -> decodedIRData.command и .address // повтор при удержании -> decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT // (раньше: results.value == 0xFFFFFFFF) Serial.print("cmd: "); Serial.print(IrReceiver.decodedIRData.command, DEC); Serial.print(" addr: 0x"); Serial.println(IrReceiver.decodedIRData.address, HEX); IrReceiver.resume(); // обязателен после ветки }} // Передатчик: irsend.sendNEC(0x00FF6897, 32) -> IrSender.sendNEC(0x00FF, 0x68, 2)// irsend.enableIROut(38) -> IrSender.enableIROut(38)| Скетч 2.x/3.x | IRremote 4.x | Что помнить при переезде |
|---|---|---|
| IRrecv irrecv(11); irrecv.enableIRIn(); | IrReceiver.begin(11, ENABLE_LED_FEEDBACK); | объект больше не создаётся: приёмник — готовый синглтон |
| irrecv.decode(&results) | IrReceiver.decode() | без аргументов; после ветки — resume(), иначе приёмник молчит |
| results.value | decodedIRData.command и .decodedRawData | команда — готовый байт; сырое слово с 4.0.0 на 32-битных платах 64-битное |
| results.value == 0xFFFFFFFF | flags & IRDATA_FLAGS_IS_REPEAT | повтор NEC стал флагом, а не магическим числом |
| irsend.sendNEC(0x00FF6897, 32) | IrSender.sendNEC(0x00FF, 0x68, 2) | адрес и команда разложены отдельно; третье число — повторы |
decodedRawData, повторы и сторона передатчика
Сырое слово никуда не пропало: decodedIRData.decodedRawData — прямой наследник results.value, у NEC это те же 32 бита с адресом, командой и двумя инверсиями, чьё устройство разобрано в уроке про 32 бита NEC. Нюанс прячется в changelog: с релиза 4.0.0 поле стало 64-битным на 32-битных платах вроде ESP32. Старые сравнения с константами работают как работали, но печатать поле целиком теперь стоит с оглядкой на разрядность — на ESP32 в мониторе окажутся восемь байт вместо четырёх, и это не порча кадра, а новая ширина типа.
Повторы — вторая смысловая замена. Старая версия при удержании слала слово 0xFFFFFFFF, и скетчи ловили его сравнением; как устроен кадр повтора внутри, разобрано в уроке про repeat-кадры. Четвёртая версия ставит флаг IRDATA_FLAGS_IS_REPEAT в decodedIRData.flags, и фильтр умещается в одну строку с побитовым И. Ставку на сравнение с 0xFFFFFFFF держать рискованно уже на расширенных NEC-командах, где слово не влезает в каноничную форму, — ещё один аргумент перевести фильтры на флаг, пока проект молодой.
Передатчик переезжает по тем же правилам: enableIROut() переехала на IrSender с прежней логикой килогерц, а sendNEC развёрнут из (слово, биты) в (адрес, команда, число повторов) — с релиза 4.4.0 команда принимает 16 бит ради расширенного NEC из баз кодов. Любимая мелочь — printIRSendUsage(): после пойманного кадра она печатает готовый вызов отправки, и дубликат любой кнопки чужого пульта собирается копипастом без единого расчёта сдвигов.
Мой ответ на вопрос «какую версию ставить в новый проект»: четвёртую и ничего другого. Она собирается для ESP32 и ESP8266, в 4.2.0 дотянулась до Uno R4, неиспользуемые протоколы и передатчик вырезаются на компиляции, а флаги повторов достаются из коробки — на платах с восемью килобайтами ОЗУ модульная компиляция решает, поместится ли проект вообще. Список аргументов за откат на 2.x в моём блокноте пуст уже три года: каждый пункт там был про страх перед переездом, а не про технику — и на свежем проекте этот страх стоит дороже самой миграции.
Откат уместен ровно в одном случае: чужой огромный скетч на 2.x, который жалко трогать и который не предстоит развивать. Тогда держите его на отдельной копии библиотеки рядом со старым проектом и не подпускайте Менеджер библиотек на выстрел. По моим замерам проект на сто двадцать строк переезжает за полчаса с перерывом на чай — быстрее, чем потом жить с двумя версиями и помнить, на какой плате какая стоит.
И про темп переезда. Таблица закрывает каркас, но за ней остаются строки, которые компилятор пропустит молча: сравнения с 0xFFFFFFFF, собственные маски повторов, ручные сдвиги по сырому слову. Их я вычищаю поиском по проекту — один вечер поиска дешевле месяца плавающего поведения. Если на копипаст нет сил, ставьте пометку прямо в комментарий над такой строкой: непометенное уплывает безвозвратно, и через месяц вы уже не вспомните, что тут должно быть флагом, а не сравнением.
Тупик, в который я полез первым делом: попытка полечить старый скетч, добавив новый заголовок рядом со старым. Получилось два декодера в одном бинарнике и предупреждения на каждый чих; вылечило только полное удаление .h и полчаса по таблице. С тех пор правило одно: заголовок .hpp в проекте ровно один, а всё, что компилятор назвал устаревшим, переезжает в тот же день — отложенная миграция дорожает втрое быстрее, чем кажется в момент откладывания.
- 1. Что означает строка 'class IRrecv' has no member named 'enableIRIn'?
- 2. Где в 4.x лежит то самое сырое 32-битное слово из results.value?
- 3. Как четвёртая версия отличает повтор при удержании?
- 4. Что не так со строкой IrSender.sendNEC(0x00FF6897, 32) в четвёртой версии?
- 5. Почему я не ставлю 2.x в новый проект?
- Строка 'enableIRIn' was not declared = визитка 2.x: приёмник стал синглтоном IrReceiver, старт — begin() с флагом индикации.
- decode() зовётся без аргументов, после ветки — resume(); decode(&results) доживает только в устаревшем IRremote.h.
- results.value разложился на decodedIRData.command (байт) и .decodedRawData (сырое слово, 64-бит на 32-битных платах с 4.0.0).
- Повтор при удержании — флаг IRDATA_FLAGS_IS_REPEAT вместо кадра 0xFFFFFFFF; фильтр — одна строка с побитовым И.
- sendNEC развёрнут в адрес + команда + число повторов; printIRSendUsage() печатает готовый вызов отправки для пойманного кадра.
Частые вопросы по теме урока
- Что означает строка 'class IRrecv' has no member named 'enableIRIn'?
- Метод enableIRIn() убран вместе с объектом IRrecv: приёмник стал синглтоном IrReceiver, старт — IrReceiver.begin(пин, ENABLE_LED_FEEDBACK). Это визитка старого API, а не поломка платы. метод снят: приёмник теперь синглтон IrReceiver и стартует через begin()
- Где в 4.x лежит то самое сырое 32-битное слово из results.value?
- decodedRawData — прямой наследник results.value. Нюанс: с 4.0.0 на 32-битных платах поле 64-битное, сравнения с константами работают, а печать целиком меняет ширину. в decodedIRData.decodedRawData
- Как четвёртая версия отличает повтор при удержании?
- Повтор стал флагом в decodedIRData.flags — фильтр умещается в одну строку с побитовым И. Сравнение с 0xFFFFFFFF не ловит повторы расширенных NEC-команд и держится только по инерции. по флагу IRDATA_FLAGS_IS_REPEAT в decodedIRData.flags
- Что не так со строкой IrSender.sendNEC(0x00FF6897, 32) в четвёртой версии?
- Сигнатура развёрнута: адрес и команда разложены по отдельным аргументам, третье число — повторы. Компилятор отвечает на старый вызов строкой no matching function for call to 'IRsend::sendNEC'. sendNEC теперь ждёт адрес, команду и число повторов отдельными аргументами
- Почему я не ставлю 2.x в новый проект?
- Четвёртая версия собирается под ESP32/ESP8266, дотянулась до Uno R4, режет лишние протоколы на компиляции и даёт флаги повторов из коробки. Аргументов за откат в новом проекте нет. нет поддержки новых плат, а флаги повторов пришлось бы ловить вручную