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

в 10:44, , рубрики: arduino, diy или сделай сам, espruino, frontendconf, javascript, paspberry pi, Блог компании Конференции Олега Бунина (Онтико), Интернет вещей, микроконтроллеры, программирование микроконтроллеров

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

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

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

Ок, 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 — появится нехитрый веб-интерфейс.

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

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

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

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

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

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

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

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

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

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

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

Он работает на базе популярного чипа 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.

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

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

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

<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, он подписывается на интересующие его топики и принимает данные. Когда они приходят, слот реагирует и заставляет работать моторы.

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

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

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

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

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

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

Raspberry Pi

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Arduino

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

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

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

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

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

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

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

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

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

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

Автор: Глеб Михеев

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js