Как стать автором
Обновить
2592.07
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Делаем кондиционер умным с помощью Elixir и Nerves

Уровень сложностиСредний
Время на прочтение7 мин
Количество просмотров5.5K
Автор оригинала: Milan Vit

С каждым днём всё ближе обжигающее японское лето, поэтому я всё больше думал о своей давней идее: дистанционном управлении кондиционером воздуха в моей спальне через Интернет. Простым нажатием кнопки за десять минут до отправления ко сну я мог бы включить кондиционер, который бы превращал спальню в прохладный комфортный оазис к тому моменту, как я почищу зубы и поднимусь на второй этаж. В прошлом году это так и осталось идеей; в этом году я довёл её до реализации.

Хотя идея дистанционного управления кондиционером далеко не нова (многие уже проложили этот путь до меня, став для меня источником вдохновения), я считаю, что моя реализация достаточно уникальна.

Самым простым было купить необходимые компоненты — Raspberry Pi Zero WH и Infrared Transceiver Hat; нетерпеливо ждать их оказалось уже не так легко.

После получения заказа я осознал, что transceiver hat прибыл без самого инфракрасного датчика и инфракрасных диодов, поэтому мне пришлось заказать необходимые компоненты с Amazon, самостоятельно припаять их и убедиться, что я могу получать инфракрасный сигнал в Raspberry Pi OS при помощи команды LIRC с интуитивно понятным названием mode2.

Заметили, чего не хватает? Ещё хочу отметить, что меня постоянно поражает крошечный размер Raspberry Pi Zero. Приложил для сравнения крышку от бутылки.

На этом этапе мне нужно было принять важное решение: насколько амбициозным я хочу сделать проект? У меня было четыре варианта.

Первая сложность связана с сигналами пульта кондиционера. В отличие от пультов телевизоров или ламп, которые обычно отправляют при каждом нажатии на кнопку один и тот же сигнал, позволяя приёмнику интерпретировать и согласовывать состояние, пульты кондиционеров сами хранят состояние и при каждом нажатии кнопки передают целиком обновлённое состояние. Возникает вопрос: устроит ли меня простая запись и воспроизведение нескольких известных состояний пульта, или я хочу погрузиться глубже — декодировать, проанализировать и воспроизводить сигнал любого нужного состояния?

Вторая сложность связана с развёртыванием веб-сервера, предоставляющего интерфейс пользователя для управления кондиционером. Выбрать ли мне простое развёртывание веб-сервера Phoenix на Raspberry Pi OS или пойти поболее сложному, но, безусловно, и более крутому пути связывания Phoenix с Nerves?

В обоих случаях я выбрал вторые варианты, более безумные, но и, бесспорно, более впечатляющие. Давайте сначала поговорим о Nerves.

Наверно, не самое лучшее соединение в моей жизни, но должен сказать, что прошло много лун с тех пор, как я в последний раз держал в руках паяльник.[1]

[1] Примечание: изначально я припаял только один IR-диод, потому что так выглядели платы во всех статьях, которые я видел в Интернете. Результат меня разочаровал, потому что плата никак не реагировала на мои команды. Расстроившись, я наудачу прикрепил ещё один диод, даже не припаяв его, ни на что особо не надеясь. Затем я проверил диоды объективом своего iPhone и, к своему искреннему удивлению, увидел, что они оба испускают инфракрасное излучение. Думаю, это связано с сектором SJ1, соединённым каплей припоя на большинстве плат, но не на моей; впрочем, эту теорию я не проверял.

▍ Elixir на встроенных системах


Nerves — это одна из самых существенных библиотек Elixir. Phoenix позволяет писать веб-серверы, превосходящие по скорости гепарда, Ecto позволяет общаться с широким спектром баз данных, а Nx позволяет подчинить себе мощь машинного обучения и искусственного интеллекта. Nerves же позволяет беспроблемно развёртывать Elixir на встроенных устройствах, например, на Raspberry Pi. И у меня наконец-то появилась возможность воспользоваться ею!

Nerves объединяет ваше приложение с урезанным ядром Linux и несколькими необходимыми утилитами в прошивку, которую можно «прожечь» на карту microSD, что позволит встроенному устройству напрямую загружать BEAM (Erlang Virtual Machine). Такой минималистичный подход, в отличие от развёртывания приложения Elixir в полнофункциональной Raspberry Pi OS, обеспечивает множество преимуществ:

  • Весь бандл операционной системы и приложения занимает десятки, а не сотни мегабайтов[2]; [2] К тому же он потребляет микроскопический объём памяти.
  • система загружается и делает приложение доступным за секунды, а не за десятки секунд;
  • обновления приложения и его зависимостей обрабатываются как часть вашего кода, а не через медленный и сложный в воспроизведении менеджер пакетов;
  • благодаря малому количеству ПО вектор атаки существенно меньше;
  • вместо работы с системами init Linux вы определяете приложения Erlang;
  • при этом всё равно сохраняется способность ядра Linux взаимодействовать с оборудованием через драйверы Linux.

Создание и запуск базового приложения Nerves оказались очень лёгким процессом, зато гораздо сложнее было заставить работать в Nerves инфракрасный приёмник и передатчик; недостаточно было просто выполнить apt install lirc и отредактировать config.txt, как в Raspberry Pi OS.

К счастью, Nerves может похвастаться потрясающей документацией, помогающей в течение всего процесса настройки системы. Даже несмотря на то. что в половине случаев я почти не понимал, что делаю, мне удалось упаковать lirc-tools в пользовательское пространство, внедрить поддержку инфракрасного пульта управления в ядро Linux и настроить свой проект Nerves на работу с этой операционной системой. Но потом… ничего не произошло, устройства /dev/lircX не стали доступны сразу волшебным образом.

Оказалось, что нужно было выполнить ещё несколько шагов и преодолеть ещё несколько препятствий. В частности:


Закончив с этим, я приступил к написанию простого декодера сигналов, который должен превращать вывод команды LIRC mode2 в строку CSV; затем можно будет импортировать её в Google Sheets для анализа!

SSH к IEx никогда не перестанет меня удивлять.

▍ Взламываем код


Прочитав упомянутые выше статьи, я начал догадываться, что инфракрасный сигнал пульта закодирован модифицированной версией [3] протокола NEC. Этот протокол представляет двоичный ноль в виде последовательностей кратковременных импульсов (562,5 мкс), за которыми идёт короткая пауза, а двоичную единицу — в виде последовательностей кратковременных импульсов, за которыми следует длинная пауза (1,6875 мс) [4].

[3] Модифицированной, потому что, по крайней мере, согласно найденной мной спецификации, протокол NEC достаточно строго относится к общей структуре сигнала; он начинается с пакета импульсов 9 мс, за которым идёт пауза 4,5 мс; затем следует сочетание address и address’ (логической инверсии), а заканчивается сигнал command и command’. Ничто из этого не оказалось применимо к сигналу, генерируемому пультом моего кондиционера: вначале шёл пакет импульсов 4,5 мс и пауза 4,5 мс; addressи address’ присутствовали только в первых двух датаграммах, но отсутствовали в третьей; вместо одного блока из command и command’ было два блока команд, а в некоторых ситуациях второй блок command’ заменялся другой структурой (иными словами, Toshiba отказалась от блока чётности command’ и использовала этот раздел под другие цели).

[4] Кстати, именно из-за этого строгого тайминга мне пришлось использовать LIRC для отправки сигналов с моей стороны. Когда я попробовал передавать сигнал при помощи только одного кода Elixir, мне не удавалось подобрать тайминг достаточно верно.


Благодаря упомянутому в предыдущем разделе декодеру (позже исправленному, чтобы он различал части начала и конца блока) мой процесс работы стал похож на нечто такое:

  • открываем SSH-подключение к моему приложению Nerves, чтобы получить доступ к приложению через IEx shell, не в bash или чём-то подобном;
  • вызываем функцию моего декодера, которая использует MuonTrap для запуска mode2, 3 секунды ждёт вывода, а затем прекращает mode2 (mode2 выполняется бесконечно, но меня интересует сканирование только одной команды за раз);
  • нажимаем кнопку на пульте дистанционного управления кондиционером для сканирования отправляемого кода; делаем упор на внесение наименьших возможных изменений[5] по сравнению с ранее отсканированной командой, чтобы с лёгкостью различать части сигнала;
  • берём декодированную строку CSV и вставляем её в мою огромную Google-таблицу.

[5] Например, на одном этапе я сканирую код, обозначающий режим охлаждения при 20,0°C с автоматической скоростью вентилятор. На следующем этапе я оставляю тот же режим и скорость вентилятора, но поднимаю температуру на один градус. Разница между этими двумя сигналами покажет, где в сигнале закодирована температура. (На самом деле, оказалось, что она закодирована в двух местах — половина градуса 20,5°C хранилась далеко от целой части.)

Когда я говорю «огромную», я подразумеваю действительно огромную Google-таблицу.

Эта таблица гораздо длиннее необходимого? Да, это так. Было ли весело её создавать. Да, было. Оценил ли я благодаря ей преимущества сверхширокого дисплея? Вам действительно нужен ответ на этот вопрос?[6]

[6] Если кто-то найдёт логику или причину подобного кодирования температуры, то я с удовольствием выслушаю. Она не увеличивается монотонно со значением температуры и я не смог найти никакой логики даже с учётом разной endianness.

Единственное, с чем было немного сложно разобраться, был самый последний байт — контрольная сумма. К счастью, это оказалась простая сумма байтов в третьей датаграмме (минус часть с переполнением); это даже не потребовало учёта различий в endianness.

Имея на руках готовую таблицу, можно было приступать к написанию (а потом и исправлению) модуля кодировщика, который сможет преобразовать любое состояние в представление сигнала, понятное кондиционеру. К счастью, кондиционер в моей гостиной уже был умным и в то же время понимал сигнал, испускаемый пультом кондиционера в спальне; поэтому я мог воспользоваться преимуществом цикла обратной связи: пробуем отправить сигналпроверяем в мобильном приложении умного кондиционера, понял ли он его так, как должен.

Спустя несколько часов я смог полностью управлять кондиционером из сессии SSH, а ещё через несколько часов у меня уже был очень простой, но функциональный веб-интерфейс, созданный при помощи Phoenix и LiveView[7]. Меня не перестаёт удивлять то, что благодаря всего двум строкам на LiveView можно синхронизировать состояние между двумя браузерными окнами на двух разных устройствах. Поистине волшебное чувство!

[7] Забавный факт: фундамент UI я сгенерировал, отправив фото пульта своего кондиционера на Claude.ai и попросив ИИ сгенерировать код на HTML и TailwindCSS, который бы был похож на предмет из реального мира.

Я решил сделать интерфейс пользователя на японском, и не спрашивайте меня, зачем.

Меня сильно впечатлила простота и увлекательность разработки моего первого IoT-устройства при помощи Nerves, поэтому я уже придумываю новые идеи для дальнейшей реализации.

Кроме того, я благодарю сообщество Elixir/Nerves в канале #nerves на официальном Discord-сервере Elixir. Оно стало источником вдохновения и оказало огромную поддержку!

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻
Теги:
Хабы:
+41
Комментарии28

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds