- PVSM.RU - https://www.pvsm.ru -

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

Nerves — это одна из самых существенных библиотек Elixir. Phoenix [4] позволяет писать веб-серверы, превосходящие по скорости гепарда, Ecto [5] позволяет общаться с широким спектром баз данных, а Nx [6] позволяет подчинить себе мощь машинного обучения и искусственного интеллекта. Nerves [7] же позволяет беспроблемно развёртывать 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 может похвастаться потрясающей документацией [8], помогающей в течение всего процесса настройки системы. Даже несмотря на то. что в половине случаев я почти не понимал, что делаю, мне удалось упаковать lirc-tools в пользовательское пространство [9], внедрить поддержку инфракрасного пульта управления в ядро Linux [10] и настроить свой проект Nerves на работу с этой операционной системой [11]. Но потом… ничего не произошло, устройства /dev/lircX не стали доступны сразу волшебным образом.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Автор: ru_vds

Источник [28]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/pul-t-distantsionnogo-upravleniya/391826

Ссылки в тексте:

[1] многие: https://medium.com/@camilloaddis/smart-air-conditioner-with-raspberry-pi-an-odissey-2a5b438fe984

[2] уже: https://www.instructables.com/Zero-to-Air-Conditioner-Controller-With-Raspberry-/

[3] проложили: https://blog.bschwind.com/2016/05/29/sending-infrared-commands-from-a-raspberry-pi-without-lirc/

[4] Phoenix: https://www.phoenixframework.org/

[5] Ecto: https://hexdocs.pm/ecto/Ecto.html

[6] Nx: https://github.com/elixir-nx/nx/tree/main/nx

[7] Nerves: https://nerves-project.org/

[8] потрясающей документацией: https://hexdocs.pm/nerves/customizing-systems.html

[9] упаковать lirc-tools в пользовательское пространство: https://github.com/Cellane/minetti_os/commit/c0677d4ac1d141d75a0404cf4185164dfb3eb99f#diff-d7e2af1905e196c2cc08fc681d156432d1cf845dceee9c6ef49705cefe3392f3

[10] поддержку инфракрасного пульта управления в ядро Linux: https://github.com/Cellane/minetti_os/commit/64fc95cc7593eb6047983a272ccbe09eb7c59754#diff-9b91e2e2d7d2d703307dd4d74f2c4b93f461abe21b276e79650c714d401380bd

[11] настроить свой проект Nerves на работу с этой операционной системой: https://github.com/Cellane/minetti_fw/commit/8ebf84badd1e8d0f5d54b491b8ca4f583d7fa9ee

[12] переписать стандартный fwup.conf: https://github.com/Cellane/minetti_fw/commit/456cded319adbbad79cef9f4d0d28f4ce7498645

[13] переписать стандартный config.txt: https://github.com/Cellane/minetti_fw/commit/574ec4a27baa2e57a2cd0b5f6fc6562a2dc6c469

[14] загрузить мой device tree overlay: https://github.com/Cellane/minetti_fw/commit/7ad488b4592568e5bb99a6b0fa705a7ead805841

[15] поломанный плагин default.so LIRC на тот: https://github.com/Cellane/minetti_fw/commit/8b15a8d8fea7fa2809224b347667ab42b95ef5b1

[16] я сообщил о баге разработчикам Nerves: https://github.com/nerves-project/nerves/issues/974

[17] папку /var/run/lirc: https://github.com/Cellane/minetti_fw/commit/14d4d73c793818dee528fa12e7e6910259a1dbc7

[18] сконфигурировать параметры LIRC: https://github.com/Cellane/minetti_fw/commit/45ee0ab156e717a6440bf6e37f33800727f1b6b8

[19] переопределить erlinit.config так, чтобы tmpfs: https://github.com/Cellane/minetti_fw/commit/68b8cdf3c531b1814ff3bb60bb34a75c60c98e8d

[20] написанию простого декодера сигналов: https://github.com/Cellane/minetti_fw/commit/b1634c84796facacdeb97fca0c9a3d448561ef3c#diff-1543ad3918c37800b7baedda353aa47409a748d6e1de2a5b0871fead64dbbf12

[21] найденной мной спецификации: https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol

[22] позже исправленному: https://github.com/Cellane/minetti_fw/commit/cba714c73a8de7f80a3eb47321f3e16c8aa20379#diff-1543ad3918c37800b7baedda353aa47409a748d6e1de2a5b0871fead64dbbf12

[23] действительно огромную Google-таблицу: https://docs.google.com/spreadsheets/d/1c8gXpYuVukQZrgvFq1DJUf-xkyHC-3_pfQpSveNrfj4/edit?usp=sharing

[24] написанию: https://github.com/Cellane/minetti_fw/commit/b1634c84796facacdeb97fca0c9a3d448561ef3c#diff-d8ce94ff16e1a931f19e415bc73713003544bf7ee5f35c776cf98a3fdd0992bf

[25] исправлению: https://github.com/Cellane/minetti_fw/commit/cba714c73a8de7f80a3eb47321f3e16c8aa20379#diff-d8ce94ff16e1a931f19e415bc73713003544bf7ee5f35c776cf98a3fdd0992bf

[26] очень простой, но функциональный веб-интерфейс: https://github.com/Cellane/minetti_ui/commit/2f899a2b88e9b73f242e4eb6d0968266957d719f

[27] Claude.ai: https://claude.ai/

[28] Источник: https://habr.com/ru/companies/ruvds/articles/818021/?utm_source=habrahabr&utm_medium=rss&utm_campaign=818021