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

Erlang для IoT

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

Erlang для IoT - 1

По мере роста производительности и возможностей все больше обороты набирает практика использования интерпретируемых языков высокого уровня, таких как Lua, Python, JS для разработки приложений. Некоторые языки постепенно проникают и в “младших братьев”, микроконтроллеры, правда, в очень ограниченном варианте.

Причин тому несколько:

  • быстрое прототипирование — при всем моем уважении к языку Си, на котором, в основном, производится разработка для микроконтроллеров, назвать его лаконичным очень сложно. Языки высокого уровня позволяют писать меньше кода и упрощают внесение изменений в уже написанное, что на стадии создания прототипа очень важно;
  • автоматическое управление памятью и абстрагирование механизмов сложных вычислений — думаю, не нуждается в комментариях, оба процесса в ручном исполнении при достаточном объеме проекта превращаются в источник большого количества головной боли;
  • упрощение отладки и тестирования — интерпретируемый код проще проверить на рабочей станции до момента полевых испытаний;
  • борьба со сложностью — зачастую, большая производительность порождает естественное прагматичное желание запихнуть на устройство побольше предварительной обработки и анализа, что отнюдь не добавляет простоты в разработке.

Увы, за все удобства приходиться платить. В данном случае цена за удобства — самые ценные ресурсы, производительность и размер кода (во многих случаях придется приходится носить с собой довольно объемную среду исполнения). Поэтому полевое применение языков высокого уровня в SoC и SoM — штука неоднозначная и, местами, компромиссная.

Мы используем для разработки язык Erlang, применяя его как по прямому назначению (создание серверных приложений и control plane), так и весьма непривычно — web-приложения. Поэтому идея использовать инфраструктуру этого языка для создания IoT-решений возникла задолго до появления плат, на которых среда выполнения Erlang-а могла бы работать без проблем.

Причин использовать Erlang было множество, самые весомые:

  • Erlang очень удобен для разбора и создания бинарных последовательностей. Сопоставление с образцом (pattern matching) в паре с обработкой битовых данных позволяет реализовывать бинарные протоколы весьма быстро и немногословно;
  • отсутствие глобальных переменных и иммутабельность для большинства данных — позволяет писать и, что не менее важно, поддерживать надежные приложения, в котором сложно случайно поменять что-то не то;
  • легкие процессы с обработкой сообщений, очень похожие на то, с чем разработчикам встраиваемых систем приходится иметь дело. По сути они представляют собой процедуру инициализации и процедуру, которая в бесконечном цикле обрабатывает входящие сообщения, изменяя внутреннее состояние. Очень похоже на Arduino, только процессов может быть много и работают они параллельно, кроме того, в промежутке между сообщениями процедуру обработки можно поменять на лету (hot code reload), что бывает весьма удобно в случае, когда надо исправить незначительные ошибки или скорректировать поведение;
  • изолированное окружение и автоматическое выделение памяти, думаю, в пояснении не нуждаются;
  • кросс-платформеность — байт-код для среды выполнения ERTS может быть собран на машине разработчика, а затем без проблем перенесен на целевое устройство;
  • отличные средства интроспекции — возможность подключиться к работающему приложению по сети и посмотреть, что оно так подтормаживает, нередко бывает очень полезно.

Первой рабочей платой, на которой мы попробовали Erlang, была Carambola 2 [1] литовских разработчиков 8devices [2], собранной на популярном чипе AR9331. Первая версия этой платы, увы, имела недостаточный объем flash-памяти, чтобы поместить среду выполнения. А вот вторая версия уже вполне себе позволяла вместить как ERTS, так и небольшое приложение.

Erlang для IoT - 2

Установка производилась классическим для такого рода устройств методом — сборкой образа OpenWRT, содержащего Erlang, с последующей прошивкой его в flash-память устройства. Первый запуск среды, увы, привел к разочарованию — все повисло. Причины этого я уже рассказывал на конференции InoThings 2018 [3], но, увы, как потом оказалось, ввел коллег в заблуждение, ошибочно назвав источник такого поведения.

Вкратце перескажу. При работе с файлами виртуальная машина ERTS использует тип off_t, размер которого в дистрибутиве вычисляется при автоконфигурировании сборки (если она идет на целевой платформе) или же подставляется из среды кросс-компиляции, как то в случае OpenWRT. Непонятно почему, но в настройках для процессоров MIPS и производных в файле конфигурации сборки стоит вдвое больший размер, чем есть по факту. Проблемы бы не возникало, если бы код виртуальной машины использовал не директивы препроцессора типа

#if SIZEOF_OFF_T == 4

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

if (sizeof(off_t) == 4) { 

В результате собранная на первой итерации ERTS при попытке прочитать при старте файл ~/.erlang.cookie (своеобразный пароль для идентификации при сетевом взаимодействии) благополучно получал мусор в старших разрядах и с треском зависал.

Исправляющий патч [4] и пакет с предпоследней версией ERTS для OpenWRT можно скачать с GitHub [5]. Помимо этого проблем пока не наблюдалось, все работало как и ожидается.

Второй аппаратной платформой, на которой мы попробовали Erlang, стал конструктор LinkIt Smart 7688 [6] от компаний Mediatek и SeeedStudio [7], специально созданный для быстрого прототипирования и изучения основ. Эта плата просто апофеоз разврата в плане ресурсов — подросшая в полтора раза частота MIPS-ядра, больше оперативной памяти (для ERTS это важно, GC не дремлет) и больше flash-памяти, а также наличие MicroSD-карты и возможности использования со-процессора Atmel Atmega 32U4 в версии Duo [8] для работы с периферией.
Erlang для IoT - 3
В общем, платформа очень даже подходила для продолжения банкета, а наличие дополнительных подключаемых устройств, соединяющихся без пайки, позволяет быстро и условно красиво собрать на коленке стенд для испытаний.

В комплекте с платформой идет ПО с собственным web-интерфейсом, а также с Python и NodeJS-библиотеками для разработки. Экосистема для сборки не изменилась — это, по-прежнему, OpenWRT. Если по каким-то причинам вы посчитаете лишним это все многообразие, то на вышеупомянутом репозитории есть пакеты, содержащие минимальный набор обязательных компонентов [9]. После сборки образ flash-памяти записывается на устройство и после перезагрузки можно смело пользоваться REPL-ом.

Для построения приложений на Erlang-е для IoT необходимо решить один архитектурный вопрос.

Язык проектировался и разрабатывался с прицелом на то, что будет служить control plane, т.е. управляющим слоем, в то время как работать с железом предполагалось посредством FFI. Для этого предусмотрены три типа взаимодействия:

  1. порты (ports) — отдельно работающие процессы, написанные на любом языке, взаимодействие с которыми происходит через потоки ввода/вывода. Могут перезапускаться в случае падения, но из-за метода взаимодействия их производительность в плане коммуникации невелика (впрочем, нам будет достаточно);
  2. NIF-функции — выглядят как стандартные функции языка, но их вызов порождает выполнение скомпилированного кода в пространстве среды исполнения. В случае ошибки могут утянуть за собой виртуальную машину целиком.
  3. C-node — когда работа целиком производится в отдельном процессе, а взаимодействие осуществляется как с отдельно запущенной средой выполнения по сети. Этот вариант мы рассматривать не будем в силу достаточно больших накладных расходов в рамках одного слабого устройства.

Дилемма заключается в следующем — мы можем вынести в FFI только транспортные вещи (т.е. поддержку GPIO, I2C, SPI, PWM, UART и т.д.), а взаимодействие непосредственно с датчиками и прочими устройствами реализовать на Erlang-е, или же, наоборот, вынести драйвера устройств целиком в код внешних модулей, оставив приложению получение сырых данных и их обработку, в этом случае, возможно, имеет смысл воспользоваться уже написанным кодом.

Мы решили использовать первый вариант. Причин тому несколько:

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

Поэтому довольно быстро была найдена библиотека ErlangALE [10], которая содержала в себе уже реализованную поддержку GPIO, I2C и SPI через интерфейсы ядра, а о них, в свою очередь, уже позаботились разработчики аппаратной платформы. Библиотека для работы с UART [11] у нас уже была проверенная, плюс довеском подключили erlexec [12] — приложение, позволяющее создавать и управлять процессами ОС.

Все перечисленные приложения использовали порты (отдельно запускаемые бинарные процессы) для работы с оборудованием и ОС, что требовало поддержки кросс-компиляции для языков С и С++, для чего был написан достаточно вычурный shell-скрипт [13], конфигурирующий среду сборки на применение нужных компиляторов.

Для тестирования принятых решений мы собрали простое устройство из LinkIt Smart 7866, двух I2C устройств (датчика температуры и давления BMP280 и OLED-дисплея 128 на 64 точки) и USB GPS-модуля, отдающего данные по UART. GPIO был проверен на светодиоде на плате, он работает, а SPI-дисплей подключать показалось ненужным на данном этапе усложнением.

Erlang для IoT - 4

Получилось достаточно компактное и простое приложение, исходный код [14] можно посмотреть на Github-е.

Я не буду углубляться в фрагменты кода, а постараюсь обзорно описать, как работает приложение.

Драйвера всех устройств выполнены в виде gen_server-процессов, это удобно, потому что позволяет складывать в состояние дополнительные параметры и, иногда, состояние устройства. Последнее можно видеть на примере uart_gps [15] — данные с UART-а приходят асинхронно, разбираются парсером NMEA0183 [16] и результаты записываются в состояние процесса, откуда достаются по запросу.

Основной цикл приложения описан в модуле gps_temp_display [17] — ежесекундно процесс вычитывает данные GPS и запрашивает состояние температуры и давления у BMP280 и выдает их на OLED-дисплей. Ради интереса можно посмотреть на драйвера дисплея [18] и датчика BMP280 [19] — все получилось достаточно лаконично, 150-170 строк на модуль.

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

Разумеется, наши попытки применения Erlang-а для встраиваемых систем отнюдь не единственные. Существует несколько интересных проектов на этот счет:

  • nerves-project.org [20] — нацелен на создание встраиваемой экосистемы на базе Elixir;
  • www.grisp.org [21] — еще более радикальный подход, предполагающий запуск ERTS напрямую на железе (bare metal Erlang system);
  • github.com/bettio/AtomVM [22] и github.com/cloudozer/ling [23] — попытки создать компактную версию ERTS, пригодную для применения в микроконтроллерах и слабых SoC/SoM.

Автор: Максим

Источник [24]


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

Путь до страницы источника: https://www.pvsm.ru/diy-ili-sdelaj-sam/286131

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

[1] Carambola 2: https://www.8devices.com/products/carambola-2

[2] 8devices: https://www.8devices.com/

[3] рассказывал на конференции InoThings 2018: https://www.youtube.com/watch?v=Cns_pqejk9g

[4] Исправляющий патч: https://github.com/mkrentovskiy/openwrt-repo/blob/master/lang/erlang/patches/103-fix-of_t.diff

[5] GitHub: https://github.com/mkrentovskiy/openwrt-repo

[6] LinkIt Smart 7688: https://www.seeedstudio.com/LinkIt-Smart-7688-p-2573.html

[7] SeeedStudio: https://www.seeedstudio.com/

[8] версии Duo: https://www.seeedstudio.com/LinkIt-Smart-7688-Duo-p-2574.html

[9] минимальный набор обязательных компонентов: https://github.com/mkrentovskiy/openwrt-repo/tree/master/linkit

[10] ErlangALE: https://github.com/esl/erlang_ale

[11] Библиотека для работы с UART: https://github.com/relabsoss/uart

[12] erlexec: https://github.com/saleyn/erlexec

[13] вычурный shell-скрипт: https://github.com/relabsoss/erlem/blob/master/crosscompile.sh

[14] исходный код: https://github.com/relabsoss/erlem

[15] uart_gps: https://github.com/relabsoss/erlem/blob/master/src/drivers/uart/uart_gps.erl

[16] парсером NMEA0183: https://github.com/relabsoss/erlem/blob/master/src/proto/nmea0183.erl

[17] gps_temp_display: https://github.com/relabsoss/erlem/blob/master/src/device/gps_temp_display.erl

[18] драйвера дисплея: https://github.com/relabsoss/erlem/blob/master/src/drivers/groove/oled_display.erl

[19] датчика BMP280: https://github.com/relabsoss/erlem/blob/master/src/drivers/groove/bmp280.erl

[20] nerves-project.org: https://nerves-project.org/

[21] www.grisp.org: https://www.grisp.org/

[22] github.com/bettio/AtomVM: https://github.com/bettio/AtomVM

[23] github.com/cloudozer/ling: https://github.com/cloudozer/ling

[24] Источник: https://habr.com/post/417245/?utm_source=habrahabr&utm_medium=rss&utm_campaign=417245