JavaScript
C++
Программирование микроконтроллеров
Разработка для интернета вещей
Интернет вещей
7 ноября

ThingJS v1.0-alpha


Последние два года я разрабатывал собственную 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 это простейшая экосистема состоящая из одного виртуального устройства, реализующего интерфейс пользователя, и одного физического. Виртуальное устройство запускается из физического при обращении к нему через браузер.


Сценарий прост. При установке приложения на физическом устройстве начинает мигать светодиод (заранее к нему подключенный) с частотой 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 Манифест приложения

Со всеми деталями приложения вы можете познакомиться самостоятельно. Я заострю внимание на нескольких файлах.



{
 "name": "Blink",
 "vendor" : "rpiontik",
 "version" : 1,
 "subversion" : 0,
 "patch" : 0,
 "description": {
   "ru": "Мигание диодом",
   "en": "Blink Example"
 },
 "components": {...},
 "scripts": {...},
 "requires" : {...}
}

Как сле��ует из названия файла, это манифест приложения. В нем есть метаданные общего характера о назначении которых легко догадаться. Помимо них, есть три важных блока. Давайте рассмотрим их пристально:



"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": {
 "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" : {
 "interfaces" : {
   "blink" : {
     "type" : "bit_port",
     "required" : true,
     "default" : 2,
     "description" : {
       "ru" : "LED индикатор",
       "en" : "LED indicator"
     }
   }
 }
}

Наверное вы уже обратили внимание на то, что при установке приложения нужно выбирать пин на котором будет мигать светодиод. При этом по умолчанию он уже выбран как GPIO2. Этот блок как раз и отвечает за эти настройки.


В этом блоке указываются зависимости. В данном случае, чтобы приложение могло функционировать, ему требуется предоставить интерфейс с типом “bit_port”. Этот интерфейс является обязательным требованием (required = true) и по умолчанию, указывается GPIO2 (default = 2). В скрипт он будет спроецирован с именем “blink”.


При установке приложения учитывается профиль оборудования, на которое будут разворачиваться скрипты. В этом профиле перечисляются доступные интерфейсы и доступные для них аппаратные ресурсы (в частности, пины и их комбинации). Проверяется совместимость требований и оборудования. Если оборудование может удовлетворить требованиям приложения, пользователю отображается схема распределения ресурсов, где первично ресурсы распределены автоматически с учетом рекомендаций из манифеста. Т.е. из того самого поля “default”.


Таким образом, на одном устройстве могут быть установлены несколько приложений, которые могут делить между собой аппаратные ресурсы.



import App from './Blink.vue';
import Langs from './langs';

$includeLang(Langs);
$exportComponent('blink-app', App);

Файл является точкой монтирования компонента, анонсированного в манифесте (см. manifest.js/components) В нем выполняется регистрация VUE компонента ‘blink-app’ через метод абстракции $exportComponent, а также регистрируется языковой пакет.


Возможно, вы спросите — зачем такие сложности? Почему не регистрировать сразу VUE компонент, который указывать в source? Дело в том, что манифест описывает публичные компоненты. Эти компоненты могут запрашиваться сторонними приложениями (runtime зависимости). Точка монтирования, в свою очередь, может зарегистрировать сопутствующие компоненты (для внутреннего пользования), а также сервисы. Т.е., подготовить окружение компонента.



export default {
 name: 'Blink',
 watch: {
   blink_state (state) {
     // Send event to script
     this.$bus.$emit($consts.EVENTS.UBUS_MESSAGE, 'blink', state);
   }
 },
 data () {
   return {
     blink_state: true
   };
 }
};

Код говорит сам за себя. При изменении свойства “blink_state” отправляется сообщение в шину ($bus) с текущим значением. Это все, что нужно сделать, чтобы скрипт на стороне контроллера получил нужную команду.



let active = true;
let state = true;

// Set port direction
$res.blink.direction($res.blink.DIR_MODE_DEF_OUTPUT);

// Run background process
setInterval(function () {
 if (active) {
   // $res - is container with required resources
   $res.blink.set(state);
   // Do invert
   state = !state;
 }
}, 1000);

// Event listener
// $bus - system bus interface
$bus.on(function (event, content, data) {
 if (event === 'blink') {
   active = !!JSON.parse(content);
 }
}, null);

В целом, код очень похож на классическое использование таймера в JavaScript. За исключением того, что в данном диалекте JavaScript его нет. Он реализован в платформе. Знакомьтесь, это mJS. Более подробно о нем можно узнать на официальной странице проекта.


Для нужд платформы диалект доработан. Завезены таймеры, а также такая полезная команда как “debugger”. Ну, и сам отладчик. Подробнее об этом отдельно в разделе “Среда разработки”.


Обратите внимание на глобальные объекты платформы. Они именуются с символа “$”.


  • $res — содержит ресурсы, которые выделены скрипту;
  • $bus — интерфейс шины.

Т.к. приложение запросило интерфейс с типом “bit_port” (см. profile.json/requires) и именем “blink”, ему он был выдан как $res.blink. Интерфейс реализует всего три функции:


  • set(value) — установить уровень GPIO
  • get() — получить текущий уровень GPIO
  • direction(value) — установить режим GPIO

Для функции 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. При этом в обработчик будут приходить все события, на которые скрипт подписан. Обработчик принимает три параметра:


  • event — идентификатор события. В этом случае возможны всего два: “$-script-restart” и “blink”. Из которых обрабатывается всего одно — blink. Подписка на второе нужна лишь для того, чтобы скрипт запускался сразу при старте системы.
  • content — с событием могут приходить данные. Их размер ограничен 126 байтами с учетом длины идентификатора события.
  • data — данные, которые передаются при подписке на событие как второй параметр. И в данном случае, равны null.

Интерфейсы расширяемы. Ниже вы найдете описание, как создать собственный интерфейс.


Реализация интерфейса


ThingJS позволяет расширять доступные аппаратные и сервисные ресурсы через специальные интерфейсы. Вы можете самостоятельно создать интерфейс, который будет реализовывать любой сложный, точный, нагруженный и т.п. функционал.


Например, вы можете реализовать интерфейс интеграции со своим облачным сервисом. Или фоновый, асинхронный процесс, с которым скрипт сможет обмениваться сообщениями. Ну или реализовать поддержку дисплея. Это будет одинаково легко как сделать, так и использовать. Как вам, так и другим. Правда для этого вам потребуется знать С.


Рассмотрим реализацию интерфейса “bit_port”, который используется в примере “Blink”. Для того чтобы начать, потребуется развернуть проект ThingJS-template alpha релиз. Документация по развертыванию находится в самом проекте.


git clone --branch alpha https://github.com/rpiontik/ThingJS-template

В проект входят компоненты:


  • ThingJS-boards — содержит в себе конфигурации устройств. Пока только ESP32_CORE_BOARD V2 и совместимые;
  • ThingJS-extern — библиотеки сторонних проектов, которые использует ThingJS;
  • ThingJS-core — ядро платформы;
  • ThingJS-front — среда разработки приложений;
  • ThingJS-stdi — стандартные интерфейсы.

Нас интересует проект ThingJS-stdi. Его структура следующая:


Файл Описание
implementation/tgsi_bit_port.c Реализация интерфейса bit_port
implementation/tgsi_bit_port.h Заголовочный файл интерфейса bit_pro
CMakeLists.txt cmake скрипт сборки
README.md
sdti_utils.h Хелперы
thingjs_stdi.c Точка монтирования интерфейсов
thingjs_stdi.h Заголовочный файл точки монтирования

По сути, нас интересует только один файл — implementation/tgsi_bit_port.c. Именно в нем содержится все, что требует отдельного пояснения.


void thingjsBitPortRegister(void) {
   static int thingjs_bit_port_cases[] = DEF_CASES(
           DEF_CASE(GPIO0),  DEF_CASE(GPIO2), DEF_CASE(GPIO3), DEF_CASE(GPIO4),
           DEF_CASE(GPIO5),  DEF_CASE(GPIO12), DEF_CASE(GPIO13), DEF_CASE(GPIO14),
           DEF_CASE(GPIO15), DEF_CASE(GPIO16), DEF_CASE(GPIO17), DEF_CASE(GPIO18),
           DEF_CASE(GPIO19), DEF_CASE(GPIO21), DEF_CASE(GPIO22), DEF_CASE(GPIO23),
           DEF_CASE(GPIO25), DEF_CASE(GPIO26), DEF_CASE(GPIO27), DEF_CASE(GPIO32),
           DEF_CASE(GPIO33)
   );

   static const struct st_thingjs_interface_manifest interface = {
           .type           = "bit_port",
           .constructor    = thingjsBitPortConstructor,
           .cases          = thingjs_bit_port_cases
   };

   thingjsRegisterInterface(&interface);
}

Функция thingjsBitPortRegister регистрирует компонент в ядре ThingJS. Для этого она вызывает функцию thingjsRegisterInterface, которой передает структуру с описанием интерфейса.


  • type — идентификатор интерфейса. Именно он указывается как тип в файле manifest.json приложения;
  • constructor — ссылка на конструктор интерфейса. Функция вызывается каждый раз, когда нужно создать новый экземпляр интерфейса;
  • cases — массив, описывающий возможные аппаратные ресурсы, которые может использовать интерфейс для своей работы. В данном случае, это одиночные GPIO. Но могут быть отдельно описаны их комбинации или зависимости.

Конструктор интерфейса монтирует интерфейс в машину mJS.


mjs_val_t thingjsBitPortConstructor(struct mjs *mjs, cJSON *params) {
   //Validate preset params
   //The params must have pin number
   if (!cJSON_IsNumber(params))
       return MJS_UNDEFINED;
   //Get pin number
   gpio_num_t gpio = params->valueint;
   //Create mjs object
   mjs_val_t interface = mjs_mk_object(mjs);
   /* Configure the IOMUX register for pad BLINK_GPIO (some pads are
      muxed to GPIO on reset already, but some default to other
      functions and need to be switched to GPIO. Consult the
      Technical Reference for a list of pads and their default
      functions.)
   */
   gpio_pad_select_gpio(gpio);
   //Add protected property to interface
   mjs_set(mjs, interface, "gpio", ~0, mjs_mk_number(mjs, gpio));
   //Set protected flag
   mjs_set_protected(mjs, interface, "gpio", ~0, true);
   //Bind functions
   mjs_set(mjs, interface, "set", ~0,
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) thingjsBitPortSet));
   mjs_set(mjs, interface, "get", ~0,
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) thingjsBitPortGet));
   mjs_set(mjs, interface, "direction", ~0,
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) thingjsBitPortDirection));
   //Consts
   mjs_set(mjs, interface, "DIR_MODE_DISABLE", ~0, mjs_mk_number(mjs, GPIO_MODE_DISABLE));
   mjs_set(mjs, interface, "DIR_MODE_DEF_INPUT", ~0, mjs_mk_number(mjs, GPIO_MODE_DEF_INPUT));
   mjs_set(mjs, interface, "DIR_MODE_DEF_OUTPUT", ~0, mjs_mk_number(mjs, GPIO_MODE_DEF_OUTPUT));
   mjs_set(mjs, interface, "DIR_MODE_INPUT_OUTPUT_OD", ~0, mjs_mk_number(mjs, GPIO_MODE_INPUT_OUTPUT_OD));
   mjs_set(mjs, interface, "DIR_MODE_INPUT_OUTPUT", ~0, mjs_mk_number(mjs, GPIO_MODE_INPUT_OUTPUT));
   //Return mJS interface object
   return interface;
}

Как параметры передаются:


  • mjs — глобальный контекст выполнения;
  • params — параметры инициализации интерфейса. В данном случае это номер GPIO.

Создается объект mJS “interface”, куда монтируются методы и свойства интерфейса:


  • gpio — readonly свойство, в котором хранится номер используемого GPIO;
  • set — метод установки уровня сигнала;
  • get — метод получения текущего уровня сигнала;
  • direction — установка режима GPIO;

Также, монтируются константы, которыми смогут оперировать скрипты (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 у вас уже установлены.


git clone --branch alpha https://github.com/rpiontik/ThingJS-front
cd ThingJS-front
npm install

При разработке рекомендую использовать IDE WEBStorm.


Состав и структура проекта наследует VUE CLI. Я отражу значимые отличия:


  1. Переработаны скрипты сборки в папке build.
  2. В конфиг dev среды (config/dev.env.js) добавлена переменная окружения “HW_DEVICE_URL”. В ней необходимо указывать ссылку на физическое устройство с которым вы будете работать.
  3. Появилась системная папка src/applications. В ней содержатся приложения, которые будут собираться автоматически. В частности, в ней находятся два приложения: ante (лаунчер) и blink (приложение).
  4. Все, что выше папки src/applications считается платформенными модулями и ресурсами. Вы, конечно, можете вносить изменения в них, но в этом случае, они появятся в контроллере только после его перепрошивки. Т.ч. если специально вы себе цели не ставили, лучше их не трогать.

Для пробы, можно тут же запустить dev-сервер. Хотя полноценно вы не сможете проводить разработку без физической “железки”, разрабатывать интерфейс это не мешает. И так, запускам dev-сервер:


npm run dev

Результат должен быть примерно таким:



Открыв браузер и введя в адресную строку http://0.0.0.0:8080 вы увидите платформу в режиме разработки:



Сам процесс разработки интерфейса мало чем отличается от классической разработки фронта на VUE. За исключением того, что есть глобальные объекты платформы, о которых нужно знать:


  • $const — содержит в себе платформенные константы, а также языковые пакеты;
  • $bus — Шина данных;
  • $store — глобальное хранилище (VUEX).
    Из примеров вы сможете понять как их использовать.

Мультиязычность реализована простейшим образом — через фильтр “lang”. Указываете языковую константу, она интерпретируется в текст в зависимости от языка интерфейса.


v-bind:label="'BLINK_SATE' | lang"

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


После прошивки контроллера и подключения в сеть, нужно убедиться, что контроллер доступен по IP с вашего компьютера. Для этого в браузере наберите http://[IP контроллера]. Должен открыться WEB-интерфейс.


Теперь требуется указать адрес контроллера в файле config/dev.env.js


'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
 NODE_ENV: '"development"',
 HW_DEVICE_URL: '"http://[IP контроллера]"'
 //HW_DEVICE_URL: '"http://192.168.8.105"',
 //HW_DEVICE_URL: '"http://192.168.4.1"',
})

Если dev-сервер был запущен, остановите его и вновь запустите. В будущем, после изменения файлов сборки, конфигурации, а также манифестов приложений, всегда перезапускайте dev-сервер.


Хотя при работе в dev-среде отображаются все приложения, которые есть в папке src/application как установленные, полнофункционально будут работать только те, которые действительно установлены на контроллер. Это не фича, а баг альфы. В будущем синхронизация железки и dev-среды будет происходить автоматически. Но пока, нужно поставить в ручном режиме приложение на контроллер, чтобы среда его “зацепила” и синхронизировала с тем, что есть в dev.


Собираем приложение в прод режиме:


npm run prod

Устанавливаем собранные приложения на контроллер напрямую. Не через dev-сервер.


Вот теперь можно приступить к разработке. Любые ваши изменения файлов будут автоматически запускать пересборку приложений и картинка на экране будет меняться (hot reload). Это же правило распространяется на срипты контроллера. К примеру, можно добавить в скрипт приложения blink команду debugger и посмотреть результат.


// Event listener
// $bus - system bus interface
$bus.on(function (event, content, data) {
 if (event === 'blink') {
   debugger;
   active = !!JSON.parse(content);
 }
}, null);

Теперь при изменении состояния чекбокса приложения Blink, среда разработки выкинет такое сообщение:



Кликнув по ссылке “Start debugger”, вы попадете в отладчик. Отобразится строка на которой произошла остановка.



Сам процесс отладки мало чем отличается от прочих отладчиков.



Отладчик разделен на четыре секции. В центральной сам код. Слева установленные приложения на контроллер. Их структура и состав. Справа, инспектор. Снизу отображается лог. Слева внизу есть текущий статус связи с контроллером.


Среда отладки находится в процессе интенсивной доработки. Предстоит встроить еще много инструментов мониторинга и отладки. Заранее прошу прощение, за возможные баги.


Разработка прошивки


Разработка прошивки основывается на концепции предложенной Espressif. Переплюнуть родную документацию на этот счет я не смогу.


Для быстрого старта подготовлен репозиторий. В нем содержится информация по развертыванию. Пример использования смотрите в разделе “Реализация интерфейса”.


Сборка очень простая и буквально через 1-2 часа вы будете уже собирать прошивки без проблем.


Что дальше?


Дальше, если платформа заинтересует сообщество, планируется:


  • Развитие среды отладки;
  • Стандартизация именований интерфейсов, событий, компонентов;
  • Подробная документация на платформу;
  • Облачный хостинг для виртуальных вещей;
  • Runtime репозитории;
  • Партирование на различные готовые устройства.

Также, я ищу людей, которые хотели бы развивать платформу со мной. Она уже очень большая по размаху и амбициям. Я предполагаю равноправное сотрудничество, целью которого станет развитие платформы до полноценной на принципах OpenSource.


Для вступления в проект необходимо сделать ценные pull-реквесты в интересующие вас компоненты платформы.


Ссылки


Ресурсы проекта ThingJS:



Репозитории проекта ThingJS:



Используемые проекты:



FAQ


Тут будут появляться ответы на вопросы в комментариях.


+24
4,8k 46
Комментарии 24