Последние два года я разрабатывал собственную IoT платформу и сегодня готов показать ее альфа версию.
Вместе с партнером мы создаем и поддерживаем IoT устройства. Мы разобрали не один сарай с граблями в процессе этой деятельности. ThingJS родилась не столько из желания, сколько из необходимости облегчить жизнь нам, а заодно, надеюсь, и вам.
Статья будет интересна людям, которым близка тема IoT и они уже что-то делали в этой сфере. Важным замечанием будет то, что платформа должна заинтересовать (внезапно) JavaScript разработчиков, т.к. именно этот язык выбран как основа платформы. Конечно, и С/С++ разработчикам тоже будет что почитать.
Сначала я расскажу о том, какие ключевые проблемы мы встретили при разработке IoT устройств, затем опишу, как платформа с ними справляется, а в завершении, все самое скучное: видео, техническая часть и можно будет потрогать все вживую.
Проблемы IoT:
— Проблема коротких рук
В основе IoT лежит экосистема. Разработка ее концепции и технической архитектуры, действительно большой труд. Помимо этого, нужно еще разработать кучу прошивок для разнотипных устройств. Придумать и воплотить в жизнь транспорт для обмена данными между устройствами на различных физических и логических принципах. Развернуть облачные ресурсы. Проработать юзер-интерфейсы. И т.д. и т.п.
Даже если отдельный специалист обладает нужными навыками, чтобы это все сделать, то ему просто не хватит времени (рук) на реализацию такой идеи. Пока он будет ее пилить, она морально устареет.
— Проблема Вавилонской башни
Разработка полноценной IoT экосистемы требует очень широкий технологический стек. Быть фулстеком в IoT это прям… сложно. Нужен опыт везде. Похвастаться таким широким спектром знаний, да еще и опытом, могут далеко не все. И тут вопрос не в умственных способностях. Это очевидный вывод из проблемы “коротких рук”.
Для создания действительно состоятельной экосистемы требуется труд многих достаточно узких специалистов, но с глубокими знаниями в свой области. Говорят эти специалисты на разных языках, используют разные паттерны, да и часто элементарные термины они понимают по-разному. А учитывая, что IoT базируется на устройствах с ограниченными ресурсами, эффективные коммуникации критически важны для реализации задуманного.
— Проблема Стокгольмского синдрома
Сегодня есть вендоры, которые развивают свои экосистемы. Это Google, Microsoft, Yandex, Мегафон, МТС и т.д. Некоторые из них позволяют интегрировать собственные вещи в их экосистемы на их условиях. Это во многом закрывает выше описанные проблемы. Но создает новую — зависимость. А условия интеграции вендоры очень любят менять. И уж, тем более о самореализации в этой парадигме речи не идет.
Решения проблем:
— Сообщество, зависимости, модно, молодежно
Выше описанные проблемы, фактически, закрывают доступ к разработке IoT для индивидуалов. Разработка платформы была начата с осознанием этих проблем. В основу было заложено развитие платформы через сообщество.
Чтобы реализовать эту идею, платформа, естественно, поставляется с открытой кодовой базой, а также, имеет на всех слоях парадигму зависимостей.
Если вы не знаете, что такое зависимости, самое время с этим познакомиться. Но если попытаться очень просто объяснить, то разрабатываемый вами модуль может зависеть от другого, который напишет ваш друг. И вы будете обращаться к его модулю по заранее оговоренному интерфейсу.
Таким образом, одновременно, независимо, множество людей могут разрабатывать собственные компоненты платформы и переиспользовать уже имеющиеся, разработанные кем-то. Это кардинально решает проблему “коротких рук”.
Также, решается проблема “Вавилонской башни”. Зависимости построены так, что различные уровни платформы, разрабатываемые на разных языках, имеют заранее определенный механизм выстраивания зависимостей между собой.
Например, разработчик на С может воспользоваться готовым компонентом фронтэнда, предоставив ему требуемый интерфейс. Или напротив, разработчик фронтэнда может использовать готовый компонент написанный на С. Т.е. каждый будет делать то, что он умеет лучше всего.
— Больше обещаний и абстракций
Протокол общения между устройствами неопределен. Вместо него есть абстракция — шина данных. Устройство может послать в шину событие или прослушать шину. Кто в шину пишет и кто получает, заранее неясно. И когда тоже. Обмен данными асинхронный и доставка не гарантируется. В общем — ад. Без паники. Так задумано.
Все дело в том, что экосистема это группа отдельных, самодостаточных устройств. В любой момент времени часть устройств может быть недоступна. По различным причинам. Останавливать активность прочих устройств, если недоступна часть — не лучший сценарий. Нужно узаконить то, что невозможно предотвратить.
В платформе реализована парадигма обещаний по предоставлению событий. Первое устройство подписывается на обещание второго давать ему информацию. Но гарантий нет. Подписчик сам должен решать, что делать в случае несвоевременного предоставления ему данных.
Проблема синхронной связи решается передачей через шину событий со ссылками на синхронные каналы. Протокол синхронного канала определяется самим типом события. К примеру, можно отправить событие с типом “do-render-video-stream” и как payload передать IP WEB-камеры. Таким образом, получатель будет знать, что нужно воспроизвести видеопоток с указанного адреса.
Но как физически работает шина? Реализация шины возлагается на сообщество. Шина расширяется тем транспортом, который требует ваш проект. Например, событие получается по http и ретранслируется по UART. Для всех элементов экосистемы внешне ничего не изменится.
— Виртуальные IoT устройства
Для ThingJS вещью является не только физическая вещь, но и специальное приложение — виртуальная вещь. Более того, физическая вещь может содержать в себе несколько виртуальных вещей (приложений), которые используют ресурсы физической вещи.
Такой подход позволяет унифицировать взаимодействие между условным backend (контроллер/сервер/облако и т.п.) и frontend (браузер, приложение и т.п.), а также b2b и даже f2f. Построить матрицу, а не иерархию взаимодействия.
Простым примером может быть WEB-камера, которая в себе имеет виртуальную вещь — интерфейс пользователя. Когда пользователь заходит по адресу http://192.168.4.1 открывается WEB страница, где начинает “жить” виртуальная вещь. Камера (физическая вещь) и страница (виртуальная вещь) автоматически становятся экосистемой, где доступна унифицированная шина данных. Через нее виртуальная вещь общается с физической. В этом случае: физическая вещь сообщает виртуальной вещи через шину адрес видеопотока, свое состояние и т.п., а виртуальная демонстрирует пользователю видео и отдает необходимые команды физической вещи.
Логическим продолжением становится возможность хостить виртуальные вещи в облаках и включать их в общую экосистему. А это, в свою очередь, позволяет создавать виртуальные устройства с огромными ресурсами, решающие задачи, например, доступные для ИИ.
Вы можете создавать сами такие устройства, или использовать уже созданные. Стокгольмский синдром побежден. Вы сами определяете от чего зависит ваш проект и как вы его будете развивать.
Техническая информация
Структура приложения ThingJS
Стек технологий
Аппаратной платформой выбран контроллер ESP32. Платформа проектировалась аппаратно независимой. Но, к сожалению, портировать на иные устройства не было времени.
Для разработки прошивки используется рекомендованные Espressif средства. Прошивка разработана на языке С. Сборщик cmake. Проект использует концепцию компонентов, также продвигаемую Espressif.
Помимо, esp-idf, используются Mongoose WEB Server, а также доработанный JavaScript интерпритатор Mongoose mJS.
Для разработки приложений используется JavaScript с фреймворком VUE 2. Сборка приложений осуществляется посредством webpack. Менеджер пакетов — npm. Как основа для среды разработки использовался VUE CLI.
Для того чтобы стандартизировать визуализацию приложений и облегчить муки UI творчества, в платформу включен пакет vuetifyjs.
Возможности среды разработки
Для JavaScript разработчиков (виртуальные вещи):
- Рекомендованная IDE — WEBStorm;
- Все профиты, которые дает VUE CLI и IDE;
- Внутрисистемная отладка приложений (дебаггер mJS на контроллере);
- В mJS реализована команда debugger, позволяющая вызывать отладчик в произвольном месте;
- Горячая загрузка обновленных файлов на контроллер при разработке (JavaScript девелоперы уже без этой фичи не живут);
- Runtime разработка в паре с реальным контроллером. Программируешь и, тут же, видишь результат на железе;
- Настроенный ESLint для понимания объектов платформы.
Для С разработчиков (физические вещи):
- Рекомендованная IDE — CLion;
- Все профиты esp-idf и IDE;
- Платформа разделена на компоненты в рамках концепции esp-idf;
- Легкая интеграция с платформой собственных компонентов.
Поддерживаемые устройства
В настоящий момент поддерживается только ESP32. Чип популярен, ввиду своей доступности при поразительных технических характеристиках. На его базе создано достаточно много готовых IoT устройств, которые можно использовать под управлением ThingJS.
Сравнение с конкурентами
Предлагаю так далеко пока не забегать. Назвать конкурентами коммерческие платформы у меня сейчас язык не поворачивается. А опенсорсные появляются и исчезают не оставляя заметного следа. Поэтому, сравнение я не стал делать. Впрочем, если у кого-то появится желание, я готов тут разместить результат его работы.
Быстрый старт
Мне только посмотреть
Хочу попробовать
Для того чтобы попробовать платформу на реальном железе, вам понадобится любое устройство на базе ESP32 c flash 4mb и возможностью его прошивать через USB. Но лучше всего подойдет ESP32 core board v2.
Купить такие штуки можно без проблем на Aliexpress или Ebay. Более того, есть даже представительства в России. Я лично покупаю в Питере.
Для того чтобы проверить работу тестового приложения “Blink”, потребуется подключить светодиод. На некоторых версиях плат есть предустановленный светодиод, подключенный к GPIO2. Если у вас такая плата, то можно ничего не делать. Blink должен работать без лишних движений. Если у вас только один диод (питание), то придется подключить индикаторный диод самому. В этом нет ничего сложного.
Вам понадобится любой индикационный светодиод и сопротивление от 1 до 5КОм.
Осталось дело за малым — развернуть пользовательский пакет на устройстве. Взять его можно тут. Инструкция по развертыванию находится там же.
Blink приложение
Сборка из исходников
Blink это простейшая экосистема состоящая из одного виртуального устройства, реализующего интерфейс пользователя, и одного физического. Виртуальное устройство запускается из физического при обращении к нему через браузер.
Сценарий прост. При установке приложения на физическом устройстве начинает мигать светодиод (заранее к нему подключенный) с частотой 1Гц. Пользователь может включать или выключать мигание диода из интерфейса. Видео можно посмотреть в разделе “Мне только посмотреть”.
Исходники лежат в репозитории src/applications/blink. Для того чтобы собрать blink и с ним “поиграться”, вам понадобится только этот репозиторий. Убедитесь, что git, npm и nodejs у вас уже установлены.
git clone --branch alpha https://github.com/rpiontik/ThingJS-front
cd ThingJS-front
npm install
npm run build
Если все прошло гладко, то в результате вы получите примерно следующее:
Поздравляю! Вы собрали свое первое приложение для ThingJS. Найти его можно в папке dist/apps/blink и сразу же попробовать поставить на устройство, руководствуясь видео из раздела "Мне только посмотреть".
Состав приложения
Файл | Описание |
---|---|
scripts/blink.js | Скрипт, который устанавливается на контроллер |
blink.js | Точка монтирования компонента приложения |
Blink.vue | VUE компонент, реализующий интерфейс пользователя |
favicon.svg | Иконка приложения |
langs.js | Языковой пакет приложения |
manifest.json | Манифест приложения |
Со всеми деталями приложения вы можете познакомиться самостоятельно. Я заострю внимание на нескольких файлах.
manifest.json
{
"name": "Blink",
"vendor" : "rpiontik",
"version" : 1,
"subversion" : 0,
"patch" : 0,
"description": {
"ru": "Мигание диодом",
"en": "Blink Example"
},
"components": {...},
"scripts": {...},
"requires" : {...}
}
Как следует из названия файла, это манифест приложения. В нем есть метаданные общего характера о назначении которых легко догадаться. Помимо них, есть три важных блока. Давайте рассмотрим их пристально:
Блок “components”
"components": {
"blink-app": {
"source": "blink.js",
"intent_filter": [
{
"action": "thingjs.intent.action.MAIN",
"category": "thingjs.intent.category.LAUNCH"
}
]
}
}
Блок описывает всю компонентную базу приложения. Поле “source” указывает на точку монтирования компонента (см. blink.js) и является точкой входа сборки для webpack (entry). Таким образом, каждый компонент будет оформлен в отдельный бандл. Загрузка этого бандла будет выполняться по мере необходимости (lazy load).
Важной структурой является “intent_filter”. Если вам случалось программировать для Android, вы найдете нечто знакомое для себя. И не ошибетесь. В системе возникают интерфейсные и сервисные события на которые подписывается компонент. Если возникает событие, удовлетворяющее условиям фильтрации, то компонент будет загружен и управление будет передано точке монтирования.
В данном случае, компонент “blink-app” подписан на событие запуска основного интерфейсного компонента приложения. Когда лаунчер будет запускать приложение, именно этот компонент будет представлен.
Если модифицировать манифест, изменив строчку
thingjs.intent.category.LAUNCH >> thingjs.intent.category.PREFERENCE
, то после его сборки и установки окажется, что приложение перестало открываться на рабочем столе. Но появилась новая “плитка” в разделе “Settings”. При этом функционально ничего не поменялось.
Таким образом, мы указали лаунчеру, что данный компонент является интерфейсным элементом для настройки нашего приложения. И этот компонент стал представляться в настройках.
Блок “scripts”
"scripts": {
"entry": "blink",
"subscriptions" : ["$-script-restart", "blink"],
"modules": {
"blink": {
"hot_reload": true,
"source": "scripts/blink.js",
"optimize": false
}
}
}
Этот блок аналогичен по функции блоку “components”, но описывает компонентную базу приложения на стороне контроллера.
В нем явно указывается точка входа. В поле “entry”. Отдельно обращу внимание, что при установке приложения скрипт не запускается сразу. Он запускается только тогда, когда наступает одно из событий, на которые скрипт подписан.
За подписки отвечает поле “subscriptions”. Сейчас в нем указаны два события:
- $-script-restart — возникает в случае запуска или перезапуска системы;
- blink — кастомное событие, которое релевантно для экосистемы blink.
В блоке “modules” следует описание состава скриптов. Отмечу два поля:
- hot_reload — если это поле установлено в true, то при изменении файла в режиме разработки он автоматически будет загружаться на контроллер (hot reload);
- optimize — если true, то при сборке проекта скрипт будет оптимизироваться и аглифицироваться.
Блок “requires”
"requires" : {
"interfaces" : {
"blink" : {
"type" : "bit_port",
"required" : true,
"default" : 2,
"description" : {
"ru" : "LED индикатор",
"en" : "LED indicator"
}
}
}
}
Наверное вы уже обратили внимание на то, что при установке приложения нужно выбирать пин на котором будет мигать светодиод. При этом по умолчанию он уже выбран как GPIO2. Этот блок как раз и отвечает за эти настройки.
В этом блоке указываются зависимости. В данном случае, чтобы приложение могло функционировать, ему требуется предоставить интерфейс с типом “bit_port”. Этот интерфейс является обязательным требованием (required = true) и по умолчанию, указывается GPIO2 (default = 2)