JavaScript i IoT: wprowadzenie do Johnny-Five
- Opublikowano • 8 min
Na tej stronie
Wprowadzenie
Niejeden programista frontend ma zakurzone na półce Raspberry albo Arduino. Niejeden ma czasem dość wirtualnego wyniku pracy, który ląduje na wirtualnym śmietniku. Połączenie JavaScript i elektroniki może być odskocznią. Z pomocą przychodzi Johnny-Five, framework łączący Javascript ze światem IoT.
W roku 2023 natrafiłem na kurs Hardware with Arduino & JavaScript autorstwa Steve’a Kinney’a. Zafascynowała mnie możliwość kontrolowania elektroniki przy pomocy JS. Postanowiłem zgłębić temat i przez kolejne miesiące poznawałem podstawy elektroniki, mikrokontrolerów oraz pisania oprogramowania w C++. Jednak to właśnie Johnny-Five i możliwość prototypowania w języku JavaScript najdłużej przykuł moją uwagę.
Po trzech latach postanowiłem sprawdzić stan projektu. Okazuje się, że ostatni commit został dodany w 2023 roku. Jednak opracowane wcześniej przykłady na potrzeby wewnętrznych prezentacji w Xebia Poland dalej działały. Postanowiłem je uporządkować, poprawić i udostępnić. A efektem są treść mini-warsztatów oraz dwa publiczne repozytoria:
- JavaScript spotyka iOT: szybkie wprowadzenie do Johnny-Five oraz 5 przykładów
- JavaScript spotyka iOT: repozytorium
- Przykłady do warsztatu
Ten artykuł to moje dotychczasowe doświadczenie w temacie JavaScript oraz IoT. W artykule znajdziesz teorię a także praktyczny przykład. Przeprowadzę cię krok po kroku.
Jak to działa
Firmata to protokół do komunikowania się z mikrokontrolerami z oprogramowania na komputerze / smartfonie / tablecie. Protokół może być zaimplementowany jako firmware na dowolnej architekturze mikrokontrolera, jak również w dowolnym oprogramowaniu klienta.
Po jednej stronie mamy firmware (Firmata) skompilowany na urządzeniu, po drugiej: skrypt JavaScript napisany przy pomocy frameworka Johnny-Five (dalej zwanego także jako J5).
Firmata jest skompilowana na mikrokontrolerze (np. Arduino UNO). Nasłuchuje na komunikacji szeregowej i tłumaczy ją na rzeczywiste polecenia Arduino API (np. takie jak digitalWrite()). Firmata jest odpowiedzialna za niskopoziome instrukcje I/O.
Skrypt, który piszemy w języku JavaScript korzysta z wysokopoziomowego frameworka Johnny-Five. Framework J5 zapewnia lepszy developer experience, o czym przekonacie się za chwilę. Pod spodem, J5 wysyła polecenia (np. led.on(): zapal diodę) po porcie szeregowym (np. USB) bądź po WiFi do urzędzenia.
Zobaczmy przykładowy skrypt.
Przykładowy skrypt: zapalenie diody LED
W tej chwili pomijamy instalację i setup. Chcę byś zobaczył, jak łatwo można pisać skrypty przy pomocy Johnny-Five.
const { Board, Led } = require("johnny-five");
const board = new Board();
board.on("ready", () => {
const led = new Led(13);
led.on();
board.on("exit", () => led.off());
});
Jeżeli jesteś zainteresowany to komplet informacji znajdziesz tutaj. Niżej załączam jedynie schemat: aczkolwiek nie jest to niezbędna wiedza na tym etapie.
led.on(); odpowiada za włączenie diody. Jeżeli zajrzymy głębiej, led.on(); wywołuje:
this.io.digitalWrite(this.pin, value); // this.pin => 13, value => 1 (stan wysoki)
Kontekst:
write: {
writable: true,
value(value) {
const state = priv.get(this);
if (state.mode === this.io.MODES.OUTPUT) {
this.io.digitalWrite(this.pin, value);
}
if (state.mode === this.io.MODES.PWM) {
this.io.analogWrite(this.pin, value);
}
}
}
Źródło: rwaldron/johnny-five/blob/main/lib/led/led.js
Teraz na niższym poziomie
Dla porównania zobaczmy, jak wygląda obsługa zapalenia diody przy pomocy firmata-io oraz serialport.
const { SerialPort } = require("serialport");
const Firmata = require("firmata-io")(SerialPort);
Firmata.requestPort((error, port) => {
if (error) {
console.log(error);
return;
}
const LED_PIN = 13;
const transport = new SerialPort({
path: "/dev/cu.usbmodem14701",
baudRate: 57600,
});
const board = new Firmata(transport);
board.on("ready", () => {
console.log(`Connected to ${board.firmware.name}`); // Connected to StandardFirmataPlus.ino
board.pinMode(LED_PIN, board.MODES.OUTPUT);
board.digitalWrite(LED_PIN, board.HIGH);
process.on("SIGINT", () => {
board.digitalWrite(LED_PIN, board.LOW);
console.log("Bye!");
process.exit();
});
});
});
Przy założeniu zależności z package.json:
{
"dependencies": {
"firmata-io": "^2.3.0",
"serialport": "^12.0.0"
}
}
Johnny-Five dodaje nowy poziom abstrakcji i ułatwia komunikację z urządzeniami. Po prostu warstwa transportu staje się dla nas transparenta. Z J5 nie musimy się martwić o przygotowanie danych do komunikacji po porcie szeregowym; nie musimy korzystać z board.digitalWrite by wysłać stan niski/wysoki, bo w przypadku diody wystarczy nam led.on();.
Przygotowałem diagram pokazujący, jak przebiega komunikacja pomiędzy Johnny-Five a urządzeniem.
Summa summarum dzięki Johnny-Five jest łatwiej i mamy lepszy DX. Mamy jaśniejszy obraz tego, co się dzieje pomiędzy uruchomieniem polecenia led.on() a fizycznym zapaleniem diody. Przejdę do przedstawienia mocnych i słabych stron J5.
Mocne i słabe strony
Tabela dla niecierpliwych. W tekście znajduje się objaśnienie dlaczego minus niekoniecznie jest minusem.
| Plusy | Minusy | ||
|---|---|---|---|
| Niski próg wejścia dla znających JavaScript | Nie nadaje się do komercyjnych rozwiązań | ||
| Dobry DX | Obecnie brak aktywnego developmentu | ||
| Do prototypowania / wprowadzenia w świat elektroniki | Wymaga środowiska uruchomieniowego z node | ||
| Daje efekt ŁAŁ | Może nie obsługiwać niektórych sensorów/komponentów elektronicznych |
Dla osób znających JavaScript ogromnym plusem jest niski próg wejścia. Biorąc pod uwagę, że J5 zapewnia dobry DX oraz wysoki poziom abstrakcji, osoba, która wcześniej nie miała styczności z elektroniką może zacząć zabawę. Sam byłem osobą, która była na bakier z elektroniką. Dla mnie J5 był odkryciem, że mogą budować na tym, co znam najlepiej, Javascript.
Napisałem „zacząć zabawę”. Myślę, że trzeba traktować framework Johnny-Five w kategoriach rozwiązania do prototypowania i zabawy. J5 daje efekt ŁAŁ. Nadaje się idealnie na warsztaty z dziećmi (także tych baardzo dużych dzieci). Czym innym jest pokazać animację podnoszącej się temperatury na wirtualnym termometrze a czym innym przyłożenie palucha i doświadczenie, że temperatura urośnie jak ogrzeje się czujnik.
Minusy tak, jak i plusy, które podałem wyżej, traktuj jako bardzo subiektywne, przez pryzmat nie rozwiązania „na produkcję”, ale eksperymentu. Obecnie dla mnie niejasny jest status projektu. Od trzech lat nie dzieje się (przynajmniej publicznie) nic, a szkoda. J5 wymaga node’a. A to oznacza, że żeby połączyć się ze swoim Arduino potrzebujesz uruchomić skrypt w środowisku, które posiada zainstalowanego node’a.
Możesz:
- Połączyć się po kablu USB z Maca
- Możesz połączyć się z urządzeniem po WiFi korzystając z
StandardFirmataWiFi - Twoje Raspberry może nawet dostarczyć środowisko uruchomieniowe node
ALE, jeżeli odłączysz kabel, zakończysz proces, komputer się zawiesi, to po ponowym uruchomieniu na twoim urządzeniu zostanie tylko Firmata.
Pamiętaj, że Firmata to firmware na twoim Arduino, a skrypt napisany w Johnny-Five działa o ile wyślesz go na urządzenie przy pomocy node’a.
Patrząc przez pryzmat rozwiązania do prototypowania, zależność node może być zaletą. Pomyśl w ten sposób: jeżeli skrypt rzuca wyjątek albo po prostu zawiera błąd, poprawiasz go i od razu testujesz; szybciej iterujesz. Jeżeli błąd znajduje się w firmware napisanym w C++ musisz ponownie skompilować kod i wgrać go na urządzenie.
Nie jest to może duży problem, ale warto o tym wspomnieć. J5 może mieć problemy z obsługą niektórych sensorów. Osobiście doświadczyłem tego z termometrem DHT11, tanim i popularnym rozwiązaniem. Do poprawnego odczytu z sensora, J5 potrzebuje dodatkowej płytki, tzw. backpack. Trochę karkołomne to jest. Przykład znajdziesz na stronie projektu: Multi - DHT11_I2C_NANO_BACKPACK.
Nie musisz jednak ograniczać się do DHT11. Do odczytu samej temperatury można skorzystać z jeszcze tańszego termometru LM35. W tym przypadku nie trzeba już podłączyć backpacka. Tutaj przykład: Thermometer - LM35.
Podsumowując, Johnny-Five ma więcej plusów dodatkich a te, które są ujemne, można potraktować jak szklanka do połowy napełniona wodą.
Wymagania - sprzęt oraz software
W tej cześci przedstawię minimum sprzętowe, które pozwolą ci na wykonanie prostych przykładów. Na początek polecam Arduino UNO R3. ESP8266 wymaga dodatkowego przygotowania, choć niewielkiego to dla uproszczenia przygotowałem ćwiczenie pod Arduino UNO.
Sprzęt
- Arduino UNO R3 (~100 PLN) albo ESP8266 (~30-40 PLN)
- Płytka stykowa: od kilku złotych do ~20 PLN w zależności od wielkości płytki
- Zestaw różnych rezystorów: groszowe sprawy
- Zestaw diod: parę złotych
- Kable połączeniowe męsko-męskie: parę złotych
- Termometr LM35DZ: ~10 PLN
- Kabel USB ale zakładam, że masz ich sporo
Myślę, że można zamknąć się w 50 złotych.
Sugestie kierowane są do zupełnych laików. Sam byłem w tym miejscu. Osoby, które są bardziej obyte z elektroniką na pewno mają swoje preferencje. Jeżeli planujesz użyć czegoś innego niż Arduino UNO albo ESP8266 sprawdź czy J5 jest wspierane na stronie Platform Support. Arduino UNO R3 oraz ESP8266 mam przetestowane. Kilka lat temu kupiłem tego sporo i żal nie skorzystać jeżeli działa. Moje Arduino UNO jest bez WiFi; kupiłem kiedyś mikrokontroler Jolly ale nie udało mi się go zgrać z Firmatą oraz J5.
Na początku kup części oryginalne. Dotyczy to w szczególności Arduino UNO.
Ja kupuję oryginały. Słyszałem różne opinie na temat zamienników / klonów. Osobiście mi szkoda czasu na szukanie po sieci dlaczego coś, co powinno działać, nie działa.
Software
Przeczytaj Garść podstawowych informacji a następnie upewnij się, że masz…
- Node w wersji 18. Nie testowałem na najnowszych.
W zasadzie tyle!
Witaj, świecie! Krok po kroku
W części Przykładowy skrypt: zapalenie diody LED podałem gotowy przykład. W tej części przeprowadzę cię krok po kroku.
Przygotowanie Firmaty dla Arduino
Potrzebujemy wgrać Firmatę na Arduino UNO.
- Zainstaluj Arduino IDE
- Z Przykładów wybierz Firmata / StandardFirmataPlus
- Podepnij Arduino do portu USB
- Wybierz Arduino UNO w Menadżerze płytek
- Zweryfikuj/skompiluj kod
- Wgraj na urzędzenie
- Jeżeli zobaczysz Kompilacja zakończona, jesteś w domu
Po tym kroku mamy przygotowane Arduino UNO. Teraz pora na podłączenie diody:
- Przygotuj diodę (czerwoną, żółtą, nie ma znaczenia :)
- Dłuższą nóżkę diody (anodę) podłącz do pinu
13a krótszą doGND.
Schemat znajdziesz w części Przykładowy skrypt: zapalenie diody LED.
Projekt w Johnny-Five
Jeżeli na jakimkolwiek etapie pojawią się problemy przy instalacji, sprawdź czy strona Troubleshooting nie opisuje problemu.
mkdir -p ~/johnny-five/hello-world
cd ~/johnny-five/hello-world
Na OS X potrzebujesz jeszcze node-gyp:
npm install -g node-gyp
Teraz już standardowo:
nvm use 18.15.0
npm init -y
npm install firmata johnny-five
touch example.js
Jeżeli poprawnie wgrałeś Firmatę na Arduino UNO po wykonaniu npx @serialport/list powinienieś widzieć urządzenie:
johnny-five/hello-world » npx @serialport/list
/dev/tty.URT1
/dev/tty.BLTH
/dev/tty.usbmodem14701 Arduino (www.arduino.cc)
/dev/tty.Bluetooth-Incoming-Port
U ciebie będzie oczywiście inaczej. Nas interesuje port, pod którym zostało wykryte Arduino (www.arduino.cc).
W pliku example.js wklej:
const Five = require("johnny-five");
const board = new Five.Board({
port: "/dev/tty.usbmodem14701", // Podaj wykryty port
});
function onReady() {
const led = new Five.Led("13");
led.blink(500);
this.repl.inject({
led: led,
});
}
board.on("ready", onReady);
Zostaje nam jedynie uruchomienie skryptu:
johnny-five/hello-world » node ./example.js
1783432169960 Connected /dev/tty.usbmodem14701
1783432170047 Repl Initialized
Dioda powinna migać co 500ms. Gratulacje! Właśnie dokonałeś pierwszego out-of-virtual-experience®. Żartuję.
Psst… masz dostęp do REPL
Zwróć uwagę na Repl Initialized. Masz teraz dostęp do REPL (Read-Eval-Print-Loop) w terminalu:
johnny-five/hello-world » node ./example.js
1783432169960 Connected /dev/tty.usbmodem14701
1783432170047 Repl InitializedW terminalu wpisz led.tab. Dostaniesz podpowiedź:
1783432170047 Repl Initialized
>> led.
led.__proto__ led.hasOwnProperty led.isPrototypeOf
led.propertyIsEnumerable led.toLocaleString led.toString
led.valueOf
led.blink led.brightness led.constructor
led.fade led.fadeIn led.fadeOut
led.intensity led.off led.on
led.pulse led.stop led.strobe
led.toggleMiłego odkrywania!
Podsumowanie
Za nami kawał teorii i szczypta praktyki. Mam nadzieję, że masz teraz solidne podstawy. W kolejnej części przedstawię kilka ciekawych przykładów.