Конференции Олега Бунина (Онтико) corporate blog
JavaScript
Programming microcontrollers
IOT
DIY
23 July 2019

Как поговорить с микроконтроллером из JS

Зачем нужен микроконтроллер? Например, чтобы устроить дома пивоварню. Если своего пивного заводика мало, то можно и что-то масштабнее: построить квест-комнату, оформить презентацию, интерактивный фонтан, который рисует картину каплями, или выставочный стенд для большой компании. С микроконтроллером можно сделать что угодно — все зависит от фантазии.

Есть заблуждение, что для создания своих железок требуется знать ассемблер, C/C++, уметь управлять памятью и глубоко понимать электричество. Когда-то так и было, но технологии развиваются и сейчас для полноценной реализации своего проекта достаточно только JS!



Ок, JavaScript мы знаем, а как соединить его с железом? Какое вообще бывает железо и что умеет? Как настроить всю систему? В расшифровке доклада Виктор Накорякова на FrontendConf узнаем: как, с помощью одного лишь JS, управлять сервоприводами, как физически объединить систему с PC, и о вариантах коммуникации приложения на JS. Обсудим пакеты serialport и Firmata, serialport с самописной прошивкой на C++, Espruino и программирование контроллера прямо на JS, Raspberry Pi, HTTP в локальной сети и HTTP и MQTT через облако.



Виктор Накоряков (nailxx) — технический директор, сооснователь компании «Амперка». Любит передовые технологии разработки, функциональное программирование и physical computing. «Амперка» производит и продает электронные модули, чтобы непрофессионалы создавали умные устройства своими руками, обучающие наборы и отдельные строительные кубики, которые можно добавлять к своему устройству — моторы, GPS, SMS.

Куда писать JavaScript


В Espruino — автономный микроконтроллер с JavaScript. Платформа Espruino позволяет писать JS прямо в микроконтроллер. Это автономная вещь в себе: подключили к компьютеру, прошили, и дальше работает самостоятельно.

В Raspberry Pi — маленький компьютер с GPIO.

В веб-приложение — на фронтенд или бэкенд.

Работу системы покажу на примере лягушки. Заходите с телефона на toad.amperka.ru — появится нехитрый веб-интерфейс.



Левая колонка управляет левым глазом, правая — правым. Принцип простой — нажал на кнопку, сервомотор крутит глаз.



Видео демонстрации стенда с лягушкой во время доклада.

Как работает лягушка


При открытии toad.amperka.ru, вы получаете статичную веб-страницу со статичным JS, который использует библиотеку MQTT.js. Эта библиотека связывается с брокером MQTT в облаке.



Если знакомы Redis, Publish/Subscribe, RabbitMQ или другие очереди сообщений, то сразу поняли о чем речь. MQTT — это брокер сообщений Machine-to-Machine. Легковесный и простой, чтобы даже слабые железки могли им пользоваться.

Для MQTT требуется брокер на сервере. Но вам даже не нужно его поднимать — арендуйте. За пару долларов в месяц у вас будет свой собственный MQTT-брокер. Бэкенд не нужен.

С другой стороны от брокера MQTT находится контроллер. Существует много разных устройств с этой ролью, например, Wi-Fi Slot. Это контроллер, который умеет соединяться с интернетом.



Он работает на базе популярного чипа ESP8266. К чипу добавлена возможность питания через микро-USB и подключение внешней периферии через тройные контакты для соединения с другими устройствами.

Как программировать железо на JS


Ничего необычного, JS обычный — почти полный ES6. В стандартную библиотеку добавлены некоторые функции и объекты для работы на низком уровне с электрическими сигналами. Например, функции для простого цифрового считывания и записи.

digitalRead — цифровое считывание. Это вопрос контроллеру: «Есть три вольта на пине №4?». Если напряжение есть, он вернет TRUE, если нет — FALSE. Так реализуется считывание простых бинарных датчиков: кнопок, переключателей, герконовых замков и инфракрасных датчиков движения.

digitalWrite — простая цифровая запись. Говорим digitalWrite TRUE — с pin уходит 3V. Говорим digitalWrite FALSE — 0V. С помощью этого простого принципа можно зажечь/погасить светодиодную ленту или запустить ядерную ракету. Мы посылаем слабый сигнал на реле, оно коммутирует большую нагрузку и ракета полетела.

Также есть функции для работы с промежуточными значениями между 0 и 3V:

  • analogRead;
  • analogWrite;
  • setWatch;
  • digitalPulse.

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

Дальше идут объекты для работы с интерфейсами, которые приняты в микроконтроллерном мире. Если в вебе нам понятны и знакомы HTTP, WebSocket, TCP, то для микроконтроллера это:

  • Serial — последовательный порт;
  • шина I2C;
  • шина SPI;
  • шина OneWire.

Как пинать сервомотор


Для примера расскажу, как управлять мотором, который стоит в гипножабе. Протокол мотора простой. На контрольный пин подается 0V — нижняя граница. Раз в 20 мкс его нужно пинать, давая стабильную единичку — 3V, а через некоторое время — сбрасывать до 0.



Дальше все повторяется заново. В зависимости от длины единички, получаем разную скорость вращения. При длине импульса в 1500 мкс мотор стоит на месте. При отклонении в ту или иную сторону он крутится по часовой или против часовой стрелки. Величина отклонения влияет на скорость вращения.

Как программировать


Программирование платформы Espruino производится в одноименной среде Espruino IDE. Плата микроконтроллера подсоединяется к компьютеру микро-USB кабелем. Единственное, придется поставить драйвер, что занимает 1,5 минуты. Драйвер ставится для MAC и Windows, а на Linux всё работает из коробки.

Вот пример программы, которая загружается в среду одной кнопкой. Она мигает светодиодом раз в секунду:

var on = false;
setInterval(function() {
    on = !on;
    LED1.write(on);
},500);

Слева в среде находится REPL-интерпретатор. Вводим «1+1». Программа выдаёт ответ «2». Чудо!

Чудо в том, что при нажатии кнопок, цифра «1», знак «+» и следующая единичка ушли по кабелю на контроллер. При нажатии «ENTER», выражение исполнилось внутри микроконтроллера, а не компьютера. Микроконтроллер получил результат «2» и вернул его обратно по кабелю на компьютер. На мониторе высветилось «2».

JavaScript исполняется внутри железа.

Кроме развлечений с арифметикой, можно крутить моторы. Понадобится функция «analogWrite», которая посылает квадратную волну. Говорим на какой пин выдавать волну. Например, у меня на плате он подписан как A7. Затем указываем длительность — например, 1300 мкс из 20 000 мкс будем подавать единицу. Также требуется опция, которая задает частоту этого пинания — 50 раз в секунду, это и есть 20 000 мкс.

>analogWrite(A7, 1300 / 2000, {freq: 50}}
=undefined
>

Перевалим за 1500 — заставим крутиться в другую сторону с большей скоростью.

>analogWrite(A7, 2300 / 2000, {freq: 50}}
=undefined
>

Или скажем остановиться.

>digitalWrite(A7, 0)
=undefined
>

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

Библиотеки


Не всегда удобно вспоминать детали реализации протокола. Поэтому для всевозможного железа создана масса библиотек, от маленьких до гигантских. Они содержат все технические особенности. В библиотеках используются понятные методы: считать данные с nfc-метки, прочитать концентрацию углекислого газа в промилле, или отправить сообщение в Telegram.
  • servo.write;
  • barometer.init;
  • barometer.read;
  • barometer.temperature;
  • nfc.listen;
  • nfc.on('tag', ...);
  • nfc.readPage;
  • nfc.writePage;
  • relay.turnOn;
  • relay.turnOff;
  • gas.calibrate;
  • gas.read('CO2');
  • telegram.sendMessage.

Полезно понимать низкоуровневые команды, но, чтобы начать творить — знать не обязательно.

Код на клиенте


Итак, когда нажимаем одну из кнопок левого или правого столбика в нашей лягушке, на топик toad/eye/left или right отправляется значение, которое мы хотим установить на соответствующий сервопривод.



<button
    class="toad__eye__control"     
    data-queue="left"     
    data-payload="1300">
    -1
</button>

1300 — это длительность импульса. Откуда берется left и 1300? Я их просто добавил в HTML в виде data-attributes.

В JS мы пишем простой код.

import mqtt from 'mqtt'; 

const client = mqtt.connect(`ws://${location.hostname}:9001`);

function onEyeControlClick() {
    const { queue, payload } = this.dataset;   
    client.publish(`toad/eye/${queue}`, payload);
}

document.querySelectorAll(".toad__eye__control")
    .forEach(e => e.addEventListener('click', onEyeControlClick));

Разберем код по частям. На старте подключаемся к брокеру, который по умолчанию работает на порту 9001: const client = mqtt.connect(`ws://${location.hostname}:9001`);.

При нажатии на любую из кнопок публикуем новый message с payload, который достали из data-attribute: client.publish(`toad/eye/${queue}`, payload);.

Дальше публикуем на топик, который сформировали тоже на основе data-attribute. Это весь наш JS-код в браузере.

Код на плате


Когда стартует Wi-fi Slot, он подписывается на интересующие его топики и принимает данные. Когда они приходят, слот реагирует и заставляет работать моторы.



Код на плате условно разбит на несколько частей. Для начала мы подключаем библиотеки. В частности, это как раз библиотека, которая управляет Servo, чтобы не вспоминать детали. Они находятся в скоупе «amperka».

const ssid = "Droidxx";
const password = "****"; 
const brokerHostname = "toad.amperka.ru";

const leftEye = require("@amperka/servo").connect(A5); 
const rightEye = require("@amperka/servo").connect(A7).

Каждый может создавать и публиковать свои библиотеки. Мы сделали несколько десятков для наших собственных и других популярных модулей. Все Open Source — заходите, пользуйтесь.

Затем нам понадобятся библиотеки для работы с Wi-Fi и MQTT-брокерами.

const wifi = require("Wifi");
const mqtt = require("tinyMQTT").create(brokerHostname); 

Здесь нет ОС, поэтому даже подключение к Wi-Fi — ручная операция. Забота о подключении лежит на вас, но это не так уж сложно. Чтобы подключиться к сети просто вызываем метод «connect», который в случае успеха или неудачи вызовет «callback» и сообщит об итогах операции.

wifi.connect(ssid, { password: password }, function(e) {
    if (e) {
        console.log("Error connecting:", e);
        wifi.disconnect();
    } else {
        console.log("Wi-Fi OK, connecting to broker ...");
        mqtt.connect();
    }
}); 

Если все хорошо — подключимся к MQTT-брокеру.

При успешном подключении подпишемся на интересные топики, то есть на все, что начинается с toad/eye.

mqtt.on("connected", function() {
    mqtt.subscribe("toad/eye/*");
        console.log("Connected to broker", brokerHostname);
}); 

Когда получаем какое-либо сообщение — разбираемся, куда оно пришло. Это очень похоже на простой разбор URL. В зависимости от топика решаем, на какой глаз будем влиять. Если получили что-то осознанное, то на этот глаз пишем то, что пришло в «payload» в микросекундах.

mqtt.on("message", function(msg) {
    const eye =
        (msg.topic === "toad/eye/left") ? leftEye :
        (msg.topic === "toad/eye/right") ? rightEye : null;
    if (eye) {
        eye.write(Number(msg.message), "us");
    }
}); 

Вот и вся магия лягушки.

Ограничения Espruino JS


Мы пробежались по платформе Espruino. Крутить глаза лягушкам — не единственная ее опция. Но у платформы есть свои косяки, которые заставляют рассматривать другие варианты.

«Всего» 1–4 Mb RAM. Есть ограничение по оперативной памяти. На код и data одновременно дается всего несколько мегабайт. Может показаться, что в эру, когда оперативка измеряется гигами, это мало. Но для группы небольших устройств этого достаточно. На 2 Mb можно делать шикарные вещи — на фонтан хватит.

Не всё так просто с NPM. Эта проблема относится к Espruino IDE. Если пишем «require», Espruino смотрит в одном месте, в другом, и если не нашла — производит fallback на NPM. В этот момент может тормозить. Espruino не всегда может разобраться со сложными пакетами. Ее мощь в этом смысле гораздо ниже, чем у того же Webpack или Parcel. Это боль, но если вы настроите toolchain самостоятельно, с пониманием того, что происходит внутри железа, то проблемы нет.

Ассортимент железа. Не каждая железка, которая называется платформой для разработки, потянет Espruino. По меркам мира микроконтроллеров, Espruino прожорлива — ей нужно от 500 Kb оперативной памяти. Такую память даст не любая железка. У канонических Arduino Uno или Arduino Nano всего 2 Kb RAM — поэтому там нельзя. Работать с Espruino возможно на железе, которое делаем мы и на официальном железе от Espruino.



Espruino — это компания одного человека, который часто выходит на Kickstarter с новыми продуктами и всегда успешно проводит сбор. Если хотите поддержать платформу — покупайте официально.

Есть третий вариант — взять достаточно мощный, но сторонний devboard. Например, у компании ST, которая производит «Nuclear» и «Discovery». Железо с надписью STM32, скорее всего, потянет Espruino.

Пробежимся по двум альтернативным вариантам. Первый — Raspberry Pi.

Raspberry Pi


Это полноценный компьютер с Linux размером с визитку.




Дополнительно есть пины ввода/вывода. Если у вас в распоряжении Raspberry Pi — используйте следующую топологию устройства.



Здесь облако опционально — мобильное устройство может подключаться непосредственно к устройству через hostname, API или систему автоматического определения устройству в сети. У меня дома есть умный телевизор и винчестер для бэкапа. Они доступны со всех других устройств просто потому, что они находятся в том же LAN.

Принцип тот же самый. Ставите устройство на Raspberry Pi в сеть и строите клиент так, что он через HTTP или WebSocket непосредственно соединяется с ним, минуя посредников. Облако для своих целей использует само приложение на Raspberry Pi: протоколирует сенсоры или транслирует прогноз погоды.

Следующая возможная топология с полноценным бэкендом.



В облаке поднимается сервер. Его клиент — то же мобильное приложение, которое держит соединение через HTTP или WebSocket. С другой стороны соединение держится уже через Raspberry Pi, используя HTTP или тот же самый MQTT.

В таком подходе преимущество в полном контроле: валидация, авторизация, отказ клиентам. При этом всемирная доступность — устройство в Москве, а коммуникация с ним доступна из Владивостока.

Ограничения Raspberry Pi


Длинная загрузка. Raspberry Pi — полноценный компьютер и на старт требуется время. В качестве винчестера используется SD-карта. Даже самая быстрая карта все равно проигрывает обычным HDD, не говоря о SSD. По времени загрузка приближается к минуте.

Следующий недостаток — это энергопотребление. В плане энергоэффективности, рассуждайте о Raspberry Pi как о мобильном устройстве. От аккумулятора она протянет недолго — счет идет на часы. Чтобы устройство работало полгода или год, требуется грамотная программа для микроконтроллера и комплект батареек.

Бедный GPIO — general-purpose input/output. Фичи подачи волн, считывание волн, работа с аналоговыми сигналами у Raspberry Pi гораздо слабее, чем у микроконтроллеров, для которых это основная задача. Например, из коробки на Raspberry Pi не удастся управлять сервоприводом в аппаратном режиме.



Ограничение условно, потому что его можно обойти расширением. Например, железкой Troyka Cap, которая берет все низкоуровневое управление работы с сигналами на себя. Raspberry Pi общается с ней с помощью высокоуровневого пакета. Отдает команды покрутить сервопривод и он работает. С помощью таких плат расширения можно подключать все, что угодно.

Arduino


Можно взять обычную Arduino, которая всем надоела. Но JavaScript придется перенести куда-нибудь, что может крутить Node JS — старый компьютер или та же Raspberry Pi.



Соединяем старый ненужный компьютер с Arduino через USB-кабель. В Arduino один раз заливаем стандартную прошивку Firmata. Она превращает Arduino в зомби, который выполняет указания мастера — старого компьютера. Мастер говорит передать на такой-то пин такой-то сигнал, и Arduino передает.

Для коммуникации используются библиотеки с NPM — SerialPort и Firmata с понятным API. Так вы опять в мире JS, пишете высокоуровневую программу, которую отправляете на свой сервер. Работу с сигналами доверяете Arduino, и она ее исполняет.

Не всегда с Firmata получится управлять всем. Она способна обеспечить взаимодействие с тем железом, для которого предназначена: сервоприводы, светодиодные ленты, медиа-контроллеры. Но если возможность считывать вполне конкретный гироскоп или акселерометр не заложена — не подойдет. Тогда придётся на обычную Arduino писать программу на C.

Но это еще не все — если писать на C нет желания, воспользуйтесь Open Source инструментом XOD.io.



Это визуальная среда программирования, где граф, который вы строите, превращается в сишный код, и уже его можно компилировать и заливать. XOD.io позволяет людям, которые слабо знакомы с тонкостями и нюансами микроконтроллерного программирования, быстро создавать простые программы. Если вы выросли из Firmata, но пока не чувствуете сил писать на низком уровне — используйте XOD.io.

Следующая конференция FrontendConf пройдет в октябре. Знаете о JavaScript такое, чего не знает большинство фронтенд-разработчиков, разрабатываете интерфейсы, которыми удобно пользоваться или нашли грааль производительности и готовы рассказать, где он находится, — ждем заявки на доклады.

Интересные доклады в программе, новости конференции, видео и статьи собираем в регулярную рассылку — подписывайтесь.

+33
10.3k 108
Comments 11