Последние два года я разрабатывал собственную IoT платформу и сегодня готов показать ее альфа версию. Вместе с партнером мы создаем и поддерживаем IoT устройства. Мы разобрали не один сарай с граблями в процессе этой деятельности. ThingJS родилась не столько из желания, сколько из необходимости облегчить жизнь нам, а заодно, надеюсь, и вам. Статья будет интересна людям, которым близка тема IoT и они уже что-то делали в этой сфере. Важным замечанием будет то, что платформа должна заинтересовать (внезапно) JavaScript разработчиков, т.к. именно этот язык выбран как основа платформы. Конечно, и С/С++ разработчикам тоже будет что почитать. Сначала я расскажу о том, какие ключевые проблемы мы встретили при разработке IoT устройств, затем опишу, как платформа с ними справляется, а в завершении, все самое скучное: видео, техническая часть и можно будет потрогать все вживую.  В основе IoT лежит экосистема. Разработка ее концепции и технической архитектуры, действительно большой труд. Помимо этого, нужно еще разработать кучу прошивок для разнотипных устройств. Придумать и воплотить в жизнь транспорт для обмена данными между устройствами на различных физических и логических принципах. Развернуть облачные ресурсы. Проработать юзер-интерфейсы. И т.д. и т.п. Даже если отдельный специалист обладает нужными навыками, чтобы это все сделать, то ему просто не хватит времени (рук) на реализацию такой идеи. Пока он будет ее пилить, она морально устареет.  Разработка полноценной IoT экосистемы требует очень широкий технологический стек. Быть фулстеком в IoT это прям… сложно. Нужен опыт везде. Похвастаться таким широким спектром знаний, да еще и опытом, могут далеко не все. И тут вопрос не в умственных способностях. Это очевидный вывод из проблемы “коротких рук”. Для создания действительно состоятельной экосистемы требуется труд многих достаточно узких специалистов, но с глубокими знаниями в свой области. Говорят эти специалисты на разных языках, используют разные паттерны, да и часто элементарные термины они понимают по-разному. А учитывая, что IoT базируется на устройствах с ограниченными ресурсами, эффективные коммуникации критически важны для реализации задуманного. Сегодня есть вендоры, которые развивают свои экосистемы. Это Google, Microsoft, Yandex, Мегафон, МТС и т.д. Некоторые из них позволяют интегрировать собственные вещи в их экосистемы на их условиях. Это во многом закрывает выше описанные проблемы. Но создает новую — зависимость. А условия интеграции вендоры очень любят менять. И уж, тем более о самореализации в этой парадигме речи не идет. Выше описанные проблемы, фактически, закрывают доступ к разработке IoT для индивидуалов. Разработка платформы была начата с осознанием этих проблем. В основу было заложено развитие платформы через сообщество. Чтобы реализовать эту идею, платформа, естественно, поставляется с открытой кодовой базой, а также, имеет на всех слоях парадигму зависимостей.  Если вы не знаете, что такое зависимости, самое время с этим познакомиться. Но если попытаться очень просто объяснить, то разрабатываемый вами модуль может зависеть от другого, который напишет ваш друг. И вы будете обращаться к его модулю по заранее оговоренному интерфейсу. Таким образом, одновременно, независимо, множество людей могут разрабатывать собственные компоненты платформы и переиспользовать уже имеющиеся, разработанные кем-то. Это кардинально решает проблему “коротких рук”.   Также, решается проблема “Вавилонской башни”. Зависимости построены так, что различные уровни платформы, разрабатываемые на разных языках, имеют заранее определенный механизм выстраивания зависимостей между собой.  Например, разработчик на С может воспользоваться готовым компонентом фронтэнда, предоставив ему требуемый интерфейс. Или напротив, разработчик фронтэнда может использовать готовый компонент написанный на С. Т.е. каждый будет делать то, что он умеет лучше всего. Протокол общения между устройствами неопределен. Вместо него есть абстракция — шина данных. Устройство может послать в шину событие или прослушать шину. Кто в шину пишет и кто получает, заранее неясно. И когда тоже. Обмен данными асинхронный и доставка не гарантируется. В общем — ад. Без паники. Так задумано. Все дело в том, что экосистема это группа отдельных, самодостаточных устройств. В любой момент времени часть устройств может быть недоступна. По различным причинам. Останавливать активность прочих устройств, если недоступна часть — не лучший сценарий. Нужно узаконить то, что невозможно предотвратить. В платформе реализована парадигма обещаний по предоставлению событий. Первое устройство подписывается на обещание второго давать ему информацию. Но гарантий нет. Подписчик сам должен решать, что делать в случае несвоевременного предоставления ему данных. Проблема синхронной связи решается передачей через шину событий со ссылками на синхронные каналы. Протокол синхронного канала определяется самим типом события. К примеру, можно отправить событие с типом “do-render-video-stream” и как payload передать IP WEB-камеры. Таким образом, получатель будет знать, что нужно воспроизвести видеопоток с указанного адреса.  Но как физически работает шина? Реализация шины возлагается на сообщество. Шина расширяется тем транспортом, который требует ваш проект. Например, событие получается по http и ретранслируется по UART. Для всех элементов экосистемы внешне ничего не изменится. Для ThingJS вещью является не только физическая вещь, но и специальное приложение — виртуальная вещь. Более того, физическая вещь может содержать в себе несколько виртуальных вещей (приложений), которые используют ресурсы физической вещи.  Такой подход позволяет унифицировать взаимодействие между условным backend (контроллер/сервер/облако и т.п.) и frontend (браузер, приложение и т.п.), а также b2b и даже f2f. Построить матрицу, а не иерархию взаимодействия.  Простым примером может быть WEB-камера, которая в себе имеет виртуальную вещь — интерфейс пользователя. Когда пользователь заходит по адресу http://192.168.4.1 открывается WEB страница, где начинает “жить” виртуальная вещь. Камера (физическая вещь) и страница (виртуальная вещь) автоматически становятся экосистемой, где доступна унифицированная шина данных. Через нее виртуальная вещь общается с физической. В этом случае: физическая вещь сообщает виртуальной вещи через шину адрес видеопотока, свое состояние и т.п., а виртуальная демонстрирует пользователю видео и отдает необходимые команды физической вещи. Логическим продолжением становится возможность хостить виртуальные вещи в облаках и включать их в общую экосистему. А это, в свою очередь, позволяет создавать виртуальные устройства с огромными ресурсами, решающие задачи, например, доступные для ИИ.  Вы можете создавать сами такие устройства, или использовать уже созданные. Стокгольмский синдром побежден. Вы сами определяете от чего зависит ваш проект и как вы его будете развивать.  Аппаратной платформой выбран контроллер ESP32. Платформа проектировалась аппаратно независимой. Но, к сожалению, портировать на иные устройства не было времени. Для разработки прошивки используется рекомендованные Espressif средства. Прошивка разработана на языке С. Сборщик cmake. Проект использует концепцию компонентов, также продвигаемую Espressif. Помимо, esp-idf, используются Mongoose WEB Server, а также доработанный JavaScript интерпритатор Mongoose mJS. Для разработки приложений используется JavaScript с фреймворком VUE 2. Сборка приложений осуществляется посредством webpack. Менеджер пакетов — npm. Как основа для среды разработки использовался VUE CLI.  Для того чтобы стандартизировать визуализацию приложений и облегчить муки UI творчества, в платформу включен пакет vuetifyjs. Для JavaScript разработчиков (виртуальные вещи): Для С разработчиков (физические вещи): В настоящий момент поддерживается только ESP32. Чип популярен, ввиду своей доступности при поразительных технических характеристиках. На его базе создано достаточно много готовых IoT устройств, которые можно использовать под управлением ThingJS.  Предлагаю так далеко пока не забегать. Назвать конкурентами коммерческие платформы у меня сейчас язык не поворачивается. А опенсорсные появляются и исчезают не оставляя заметного следа. Поэтому, сравнение я не стал делать. Впрочем, если у кого-то появится желание, я готов тут разместить результат его работы. Для того чтобы попробовать платформу на реальном железе, вам понадобится любое устройство на базе ESP32 c flash 4mb и возможностью его прошивать через USB. Но лучше всего подойдет ESP32 core board v2.  Купить такие штуки можно без проблем на Aliexpress или Ebay. Более того, есть даже представительства в России. Я лично покупаю в Питере. Для того чтобы проверить работу тестового приложения “Blink”, потребуется подключить светодиод. На некоторых версиях плат есть предустановленный светодиод, подключенный к GPIO2. Если у вас такая плата, то можно ничего не делать. Blink должен работать без лишних движений. Если у вас только один диод (питание), то придется подключить индикаторный диод самому. В этом нет ничего сложного.  Вам понадобится любой индикационный светодиод и сопротивление от 1 до 5КОм.  Осталось дело за малым — развернуть пользовательский пакет на устройстве. Взять его можно тут. Инструкция по развертыванию находится там же. Blink это простейшая экосистема состоящая из одного виртуального устройства, реализующего интерфейс пользователя, и одного физического. Виртуальное устройство запускается из физического при обращении к нему через браузер. Сценарий прост. При установке приложения на физическом устройстве начинает мигать светодиод (заранее к нему подключенный) с частотой 1Гц. Пользователь может включать или выключать мигание диода из интерфейса. Видео можно посмотреть в разделе “Мне только посмотреть”.  Исходники лежат в репозитории src/applications/blink. Для того чтобы собрать blink и с ним “поиграться”, вам понадобится только этот репозиторий. Убедитесь, что git, npm и nodejs у вас уже установлены. Если все прошло гладко, то в результате вы получите примерно следующее:  Поздравляю! Вы собрали свое первое приложение для ThingJS. Найти его можно в папке dist/apps/blink и сразу же попробовать поставить на устройство, руководствуясь видео из раздела "Мне только посмотреть". Со всеми деталями приложения вы можете познакомиться самостоятельно. Я заострю внимание на нескольких файлах. Как следует из названия файла, это манифест приложения. В нем есть метаданные общего характера о назначении которых легко догадаться. Помимо них, есть три важных блока. Давайте рассмотрим их пристально: Блок описывает всю компонентную базу приложения. Поле “source” указывает на точку монтирования компонента (см. blink.js) и является точкой входа сборки для webpack (entry). Таким образом, каждый компонент будет оформлен в отдельный бандл. Загрузка этого бандла будет выполняться по мере необходимости (lazy load). Важной структурой является “intent_filter”. Если вам случалось программировать для Android, вы найдете нечто знакомое для себя. И не ошибетесь. В системе возникают интерфейсные и сервисные события на которые подписывается компонент. Если возникает событие, удовлетворяющее условиям фильтрации, то компонент будет загружен и управление будет передано точке монтирования. В данном случае, компонент “blink-app” подписан на событие запуска основного интерфейсного компонента приложения. Когда лаунчер будет запускать приложение, именно этот компонент будет представлен. Если модифицировать манифест, изменив строчку  thingjs.intent.category.LAUNCH >> thingjs.intent.category.PREFERENCE , то после его сборки и установки окажется, что приложение перестало открываться на рабочем столе. Но появилась новая “плитка” в разделе “Settings”. При этом функционально ничего не поменялось. Таким образом, мы указали лаунчеру, что данный компонент является интерфейсным элементом для настройки нашего приложения. И этот компонент стал представляться в настройках. Этот блок аналогичен по функции блоку “components”, но описывает компонентную базу приложения на стороне контроллера.  В нем явно указывается точка входа. В поле “entry”. Отдельно обращу внимание, что при установке приложения скрипт не запускается сразу. Он запускается только тогда, когда наступает одно из событий, на которые скрипт подписан. За подписки отвечает поле “subscriptions”. Сейчас в нем указаны два события: В блоке “modules” следует описание состава скриптов. Отмечу два поля: Наверное вы уже обратили внимание на то, что при установке приложения нужно выбирать пин на котором будет мигать светодиод. При этом по умолчанию он уже выбран как GPIO2. Этот блок как раз и отвечает за эти настройки. В этом блоке указываются зависимости. В данном случае, чтобы приложение могло функционировать, ему требуется предоставить интерфейс с типом “bit_port”. Этот интерфейс является обязательным требованием (required = true) и по умолчанию, указывается GPIO2 (default = 2). В скрипт он будет спроецирован с именем “blink”. При установке приложения учитывается профиль оборудования, на которое будут разворачиваться скрипты. В этом профиле перечисляются доступные интерфейсы и доступные для них аппаратные ресурсы (в частности, пины и их комбинации). Проверяется совместимость требований и оборудования. Если оборудование может удовлетворить требованиям приложения, пользователю отображается схема распределения ресурсов, где первично ресурсы распределены автоматически с учетом рекомендаций из манифеста. Т.е. из того самого поля “default”. Таким образом, на одном устройстве могут быть установлены несколько приложений, которые могут делить между собой аппаратные ресурсы. Файл является точкой монтирования компонента, анонсированного в манифесте (см. manifest.js/components) В нем выполняется регистрация VUE компонента ‘blink-app’ через метод абстракции $exportComponent, а также регистрируется языковой пакет. Возможно, вы спросите — зачем такие сложности? Почему не регистрировать сразу VUE компонент, который указывать в source? Дело в том, что манифест описывает публичные компоненты. Эти компоненты могут запрашиваться сторонними приложениями (runtime зависимости). Точка монтирования, в свою очередь, может зарегистрировать сопутствующие компоненты (для внутреннего пользования), а также сервисы. Т.е., подготовить окружение компонента. Код говорит сам за себя. При изменении свойства “blink_state” отправляется сообщение в шину ($bus) с текущим значением. Это все, что нужно сделать, чтобы скрипт на стороне контроллера получил нужную команду. В целом, код очень похож на классическое использование таймера в JavaScript. За исключением того, что в данном диалекте JavaScript его нет. Он реализован в платформе. Знакомьтесь, это mJS. Более подробно о нем можно узнать на официальной странице проекта.  Для нужд платформы диалект доработан. Завезены таймеры, а также такая полезная команда как “debugger”. Ну, и сам отладчик. Подробнее об этом отдельно в разделе “Среда разработки”. Обратите внимание на глобальные объекты платформы. Они именуются с символа “$”. Т.к. приложение запросило интерфейс с типом “bit_port” (см. profile.json/requires) и именем “blink”, ему он был выдан как $res.blink. Интерфейс реализует всего три функции: Для функции direction описаны доступные константы через тот же интерфейс $res.blink.: DIR_MODE_DISABLE; DIR_MODE_DEF_INPUT; DIR_MODE_DEF_OUTPUT; DIR_MODE_INPUT_OUTPUT_OD; DIR_MODE_INPUT_OUTPUT. Подписка на события шины осуществляется через метод $bus.on. При этом в обработчик будут приходить все события, на которые скрипт подписан. Обработчик принимает три параметра: Интерфейсы расширяемы. Ниже вы найдете описание, как создать собственный интерфейс. ThingJS позволяет расширять доступные аппаратные и сервисные ресурсы через специальные интерфейсы. Вы можете самостоятельно создать интерфейс, который будет реализовывать любой сложный, точный, нагруженный и т.п. функционал. Например, вы можете реализовать интерфейс интеграции со своим облачным сервисом. Или фоновый, асинхронный процесс, с которым скрипт сможет обмениваться сообщениями. Ну или реализовать поддержку дисплея. Это будет одинаково легко как сделать, так и использовать. Как вам, так и другим. Правда для этого вам потребуется знать С. Рассмотрим реализацию интерфейса “bit_port”, который используется в примере “Blink”. Для того чтобы начать, потребуется развернуть проект ThingJS-template alpha релиз. Документация по развертыванию находится в самом проекте. В проект входят компоненты: Нас интересует проект ThingJS-stdi. Его структура следующая: По сути, нас интересует только один файл — implementation/tgsi_bit_port.c. Именно в нем содержится все, что требует отдельного пояснения. Функция thingjsBitPortRegister регистрирует компонент в ядре ThingJS. Для этого она вызывает функцию thingjsRegisterInterface, которой передает структуру с описанием интерфейса.  Конструктор интерфейса монтирует интерфейс в машину mJS. Как параметры передаются: Создается объект mJS “interface”, куда монтируются методы и свойства интерфейса: Также, монтируются константы, которыми смогут оперировать скрипты (DIR_MODE_DISABLE, DIR_MODE_DEF_INPUT и т.д.). После создания интерфейса, он монтируется под определенным идентификатором (в примере Blink это “blink”) в глобальный объект $res. Пример использования можно посмотреть в разделе Blink (scripts/blink.js). Вы можете оформлять интерфейсы в отдельные компоненты или пакеты. Это позволит собирать прошивку как лего. Среда разработки приложений основывается на VUE CLI, который был доработан для соответствия потребностям платформы ThingJS. Это жесткий форк, т.ч. новых фич из VUE CLI стоит ждать в том случае, если они прям сильно облегчают жизнь. Для развертывания среды необходимо клонировать проект ThingJS-front alpha-релиз. Убедитесь, что git, npm и nodejs у вас уже установлены. При разработке рекомендую использовать IDE WEBStorm. Состав и структура проекта наследует VUE CLI. Я отражу значимые отличия:  Для пробы, можно тут же запустить dev-сервер. Хотя полноценно вы не сможете проводить разработку без физической “железки”, разрабатывать интерфейс это не мешает. И так, запускам dev-сервер: Результат должен быть примерно таким:  Открыв браузер и введя в адресную строку http://0.0.0.0:8080 вы увидите платформу в режиме разработки:  Сам процесс разработки интерфейса мало чем отличается от классической разработки фронта на VUE. За исключением того, что есть глобальные объекты платформы, о которых нужно знать: Мультиязычность реализована простейшим образом — через фильтр “lang”. Указываете языковую константу, она интерпретируется в текст в зависимости от языка интерфейса. Для того чтобы оценить возможности среды разработки в полной мере, вам потребуется подготовленный (прошитый) контроллер. Вы можете собрать прошивку сами из проекта или воспользоваться готовой прошивкой и утилитой отсюда. После прошивки контроллера и подключения в сеть, нужно убедиться, что контроллер доступен по IP с вашего компьютера. Для этого в браузере наберите http://[IP контроллера]. Должен открыться WEB-интерфейс. Теперь требуется указать адрес контроллера в файле config/dev.env.js Если dev-сервер был запущен, остановите его и вновь запустите. В будущем, после изменения файлов сборки, конфигурации, а также манифестов приложений, всегда перезапускайте dev-сервер. Хотя при работе в dev-среде отображаются все приложения, которые есть в папке src/application как установленные, полнофункционально будут работать только те, которые действительно установлены на контроллер. Это не фича, а баг альфы. В будущем синхронизация железки и dev-среды будет происходить автоматически. Но пока, нужно поставить в ручном режиме приложение на контроллер, чтобы среда его “зацепила” и синхронизировала с тем, что есть в dev. Собираем приложение в прод режиме: Устанавливаем собранные приложения на контроллер напрямую. Не через dev-сервер. Вот теперь можно приступить к разработке. Любые ваши изменения файлов будут автоматически запускать пересборку приложений и картинка на экране будет меняться (hot reload). Это же правило распространяется на срипты контроллера. К примеру, можно добавить в скрипт приложения blink команду debugger и посмотреть результат. Теперь при изменении состояния чекбокса приложения Blink, среда разработки выкинет такое сообщение:  Кликнув по ссылке “Start debugger”, вы попадете в отладчик. Отобразится строка на которой произошла остановка.  Сам процесс отладки мало чем отличается от прочих отладчиков.  Отладчик разделен на четыре секции. В центральной сам код. Слева установленные приложения на контроллер. Их структура и состав. Справа, инспектор. Снизу отображается лог. Слева внизу есть текущий статус связи с контроллером. Среда отладки находится в процессе интенсивной доработки. Предстоит встроить еще много инструментов мониторинга и отладки. Заранее прошу прощение, за возможные баги. Разработка прошивки основывается на концепции предложенной Espressif. Переплюнуть родную документацию на этот счет я не смогу.  Для быстрого старта подготовлен репозиторий. В нем содержится информация по развертыванию. Пример использования смотрите в разделе “Реализация интерфейса”. Сборка очень простая и буквально через 1-2 часа вы будете уже собирать прошивки без проблем. Дальше, если платформа заинтересует сообщество, планируется: Также, я ищу людей, которые хотели бы развивать платформу со мной. Она уже очень большая по размаху и амбициям. Я предполагаю равноправное сотрудничество, целью которого станет развитие платформы до полноценной на принципах OpenSource.  Для вступления в проект необходимо сделать ценные pull-реквесты в интересующие вас компоненты платформы. Ресурсы проекта ThingJS: Репозитории проекта ThingJS: Используемые проекты: Тут будут появляться ответы на вопросы в комментариях. Archi Product Owner