JavaScript i IoT: wprowadzenie do Johnny-Five

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ę.

Prezentuję Johnny-Five podczas Drzwi Otwartych w Xebia Poland w 2023 roku
Prezentuję Johnny-Five podczas Drzwi Otwartych w Xebia Poland w 2023 roku

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:

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.

Wolne tłumaczenie z dokumentacji projektu Firmata

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.

Schemat podłączenia diody do Arduino UNO
Schemat podłączenia diody do Arduino UNO

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.

Komunikacja za pośrednictwem protokołu Firmata
Komunikacja za pośrednictwem protokołu Firmata

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.

PlusyMinusy
Niski próg wejścia dla znających JavaScriptNie nadaje się do komercyjnych rozwiązań
Dobry DXObecnie brak aktywnego developmentu
Do prototypowania / wprowadzenia w świat elektronikiWymaga ś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:

ALE, jeżeli odłączysz kabel, zakończysz proces, komputer się zawiesi, to po ponowym uruchomieniu na twoim urządzeniu zostanie tylko Firmata.

Tip

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

Sugerowany sprzęt oraz części
Sugerowany sprzęt oraz części
  1. Arduino UNO R3 (~100 PLN) albo ESP8266 (~30-40 PLN)
  2. Płytka stykowa: od kilku złotych do ~20 PLN w zależności od wielkości płytki
  3. Zestaw różnych rezystorów: groszowe sprawy
  4. Zestaw diod: parę złotych
  5. Kable połączeniowe męsko-męskie: parę złotych
  6. Termometr LM35DZ: ~10 PLN
  7. 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.

Uwaga na podróbki!

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…

  1. 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.

  1. Zainstaluj Arduino IDE
  2. Z Przykładów wybierz Firmata / StandardFirmataPlus
  3. Podepnij Arduino do portu USB
  4. Wybierz Arduino UNO w Menadżerze płytek
  5. Zweryfikuj/skompiluj kod
  6. Wgraj na urzędzenie
  7. Jeżeli zobaczysz Kompilacja zakończona, jesteś w domu
Wybór StandardFirmataPlus w Arduino IDE
Wybór StandardFirmataPlus w Arduino IDE
Wybór urządzenia przed kompilacją StandardFirmataPlus
Wybór urządzenia przed kompilacją StandardFirmataPlus
Weryfikacja oraz kompilacja szkicu StandardFirmataPlus
Weryfikacja oraz kompilacja szkicu StandardFirmataPlus
Wgranie oprogramowania do Arduino UNO
Wgranie oprogramowania do Arduino UNO
Kompilacja zakończona!
Kompilacja zakończona!

Po tym kroku mamy przygotowane Arduino UNO. Teraz pora na podłączenie diody:

  1. Przygotuj diodę (czerwoną, żółtą, nie ma znaczenia :)
  2. Dłuższą nóżkę diody (anodę) podłącz do pinu 13 a krótszą do GND.

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ę.

Gratulacje, dioda świeci!
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 Initialized

W 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.toggle

Mił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.

Zasoby