WarsawJS #4

We talk about JavaScript. Each month in Warsaw, Poland.

Zastosowanie JavaScript w Robotyce

Marcin Borkowski | LinkedIn

Next generation robotics framework with support for 33 different platforms

Cylon.js to obecnie najszybciej rozwijający się i najlepiej udokumentowany framework JavaScript do komunikacji z urządzeniami typu „Open Hardware“.

Tak jest! Cylon.js to naprawdę najszybciej rozwijający się framework tego typu. Jeśli nie obsługuje jeszcze któregoś z popularnych urządzeń open-hardware, z całą pewnością za chwilę będzie.

- tempo rozwoju

W Marcu 2014 Cylon.js obsługiwał 6 platform.
Niemal 10 m-cy później Cylon.js potrafi obsłużyć w sumie aż 33 platformy (wzrost o 550%)!

Niektóre z nich to: Arduino, ARDrone, Leap Motion, Spark, Sphero, Neurosky, Pebble, Raspberry Pi, Philips Hue, Bluetooth SMART

- Architektura

Zanim napiszemy pierwszy kod, kilka słów o architekturze. Pozwoli nam to lepiej zrozumieć, czym jest i jak działa Cylon.js

- Architektura

MCP (Master Control Program) - jest odpowiedzialny za zarządzanie cyklem życia programu, jak definiowanie „robotów“, zatrzymywanie/wznawianie ich pracy, obsługa API.

- Architektura

REST API - Cylon.js oferuje wbudowany interfejs RESTful API, który możemy wykorzystać do obsługi wszelkich zdarzeń związanych zarówno z samym programem, jak i zdefiniowanymi robotami. Wywołania API kierowane są bezpośrednio do MCP.

- Architektura

Robots - Robotami w ujęciu Cylon.js nazywamy zbiór (1 lub więcej) urządzeń (devices) oraz połączeń (connections), zdolnych do wzajemnej komunikacji. Definiowanie robotów odbywa się na poziomie MCP.

- Architektura

Connections - definicja połączeń dla wykorzystywanych urządzeń, np. określenie portu USB, do którego podłączono dane urządzenie.

Devices - definicja urządzeń, nad którymi chcemy mieć kontrolę. W przypadku np. Arduino, urządzeniami będą wszystkie elementy zewnętrzne, które do niego podłączono, np. dioda led, czujnik temperatury, servo itp.

- Architektura

Drivers, Adaptors - zapewniają komunikację z urządzeniami i poszczególnymi usługami. Adapterem jest np. cylon-firmata, który instalujemy jako submoduł do komunikacji z Arduino. Sterownikiem (driver) jest za to część programu służąca do obsługi komunikacji z danym urządzeniem (device).

- Architektura

- rozpoczynamy prace

Cylon.js instalujemy jako moduł Node.js:
            $ npm install cylon
        

Instalowany jest core frameworku wraz z pakietami cylon-gpio i cylon-i2c. Praca z poszczególnymi platformami wymaga doinstalowania właściwych dla tych platform pakietów, dostępnych w ramach frameworka.

- rozmówki z Arduino

W naszych pierwszych przykładach będziemy wykorzystywać połączenie z Arduino. Komunikacje z nim zapewni nam pakiet cylon-firmata, którego instalacja jest równie szybka i bezbolesna:
            $ npm install cylon-firmata
        

- rozmówki z Arduino

Aktualnie zainstalowane pakiety listujemy za pomocą komendy:
            $ npm -ls --depth 0
        
co daje przykładowy wynik:
            
        

- rozmówki z Arduino

Standardowo, aby Arduino mogło wykonywać dla nas określone czynności, niezbędne jest zuploadowanie do jego niewielkiej pamięci (wersja UNO mieści ~32 kb kodu) wcześniej przygotowanego kodu.

Takie rozwiązanie, poza oczywistymi plusami (wszystko w jednym - brak konieczności podlączania dodatkowego komputera) posiada również pewne wady, mogące uprzykrzyć nasze pierwsze kroki z robotyką...

- rozmówki z Arduino

Największe problemy z Arduino dla osób stawiających pierwsze kroki w robotyce:
  1. Upload kodu trwa nieadekwatnie długo, przez co testowanie i zmiany w czasie mocno się rozwlekają.
  2. Natywny sposób programowania Arduino (oparty na środowisku Wiring i zasadniczo na języku C/C++) pozostawia wiele do życzenia (np. duże ryzyko "śmieciowego" kodu, słabe abstrakcje i nazewnictwo, zagmatwana obsługa przerwań).

- rozmówki z Arduino

Cylon.js swoją komunikację z Arduino opiera na protokole Firmata. Dzięki temu:

- rozmówki z Arduino

Aby nasze programy napisane z użyciem Cylon.js były poprawnie interpretowane przez Arduino, niezbędne jest wgranie do jego pamięci sketcha protokołu Firmata (jest to jednorazowa operacja wykonywana na danym urządzeniu).

Szczegółowy opis jak tego dokonać znajduje się tutaj oraz (aktualizacja) tutaj.

- sterowanie diodą LED

W tym przykładzie:
  1. zaimportujemy bibliotekę Cylon.js,
  2. zdefiniujemy robota,
  3. zdefiniujemy sposób połączenia z Arduino,
  4. zdefiniujemy diodę LED jako "urządzenie" peryferyjne podłączone do Arduino,
  5. zaprogramujemy proste zadanie do wykonania - zapalanie i gaszenie przez Arduino diody w 1-sekundowy interwale,
  6. uruchomimy robota.

- sterowanie diodą LED

Importujemy bibliotekę Cylon.js do zmiennej Cylon:
            var Cylon = require('cylon');
        
Definiujemy robota:
            Cylon.robot({
        

- sterowanie diodą LED

W "ciele" Robota definiujemy połączenie z Arduino wykorzystując zainstalowany wcześniej adapter cylon-firmata.
            connections: {
              arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' }
            },
        

- sterowanie diodą LED

Definiujemy urządzenie peryferyjne podłączone do Arduino.
W naszym przykładzie będzie to dioda LED, którą podłączymy pod pin=13 (pin cyfrowy).
            devices: {
              led: { driver: 'led', pin: 13 }
            },
        

- sterowanie diodą LED

Definiujemy proste zadanie do wykonania przez Arduino - zapalanie i wygaszanie diody z 1-sekundowym interwałem.
            work: function(my) {
              every((1).second(), my.led.toggle);
            }
        
Uruchamiamy naszego robota:
            }).start();
        

- sterowanie diodą LED

Nasz program powinien wyglądać teraz tak:
            var Cylon = require('cylon');
            Cylon.robot({
              connections: {
                  arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' }
              },
              devices: {
                  led: { driver: 'led', pin: 13 }
              },
              work: function(my) {
                  every((1).second(), my.led.toggle);
              }
            }).start();
        

- sterowanie diodą LED

Uruchomienie programu w terminalu:
            $ node led_blink.js
        
gdzie led_blink.js to nazwa pliku, w której zapisaliśmy nasz program.

UWAGA: Wyłączając powyższy program, automatycznie zatrzymujemy wszelkie dotychczas wykonywane przez Arduino zaprogramowane czynności.

- Udostępniamy proste API

Z wcześniejszych slajdów dowiedziałeś się, że Cylon.js udostępnia możliwość definiowania własnych metod API.

W kolejnym przykładzie zróbmy to samo, jednak z tą różnicą, że dioda LED nie będzie zapalana w interwale a poprzez wywołanie odpowiedniej metody z API.

- Udostępniamy proste API

Importujemy bibliotekę Cylon.js do zmiennej Cylon:
            var Cylon = require('cylon');
        

- Udostępniamy proste API

Definiujemy interfejs API uruchamiany na lokalnym hoście, na porcie 8080, z pominięciem SSL:
            Cylon.api({
              host: '0.0.0.0',
              port: 8080,
              ssl: false
            });
        

- Udostępniamy proste API

Definiujemy robota:
            Cylon.robot({
              /* Tym razem dla porządku nadamy mu imię */
              name: 'ledMonster',
              connections: {
                  arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' }
              },
              devices: {
                  led: { driver: 'led', pin: 13 }
              },
        

- Udostępniamy proste API

              /* Definiujemy tym razem puste zadanie */
              work: function() {},
              /* Tworzymy własną funkcję, której każde wywołanie
                 będzie przełączać aktualny stan diody LED (on/off) */
              ledToggle: function() {
                  console.log('Led Toggle');
                  this.led.toggle();
              }}).start();
        

- Udostępniamy proste API

Po uruchomieniu programu diody LED, które w poprzednim przykładzie zapalały się i gasiły w interwale, teraz pozostają wygaszone.

Jednak w tym przykładzie zdefiniowaliśmy interfejs API, dostępny z lokalnej przeglądarki pod adresem http://localhost:8080/.

- Udostępniamy proste API

Wejście do API nie wymaga żadnego logowania, choć Cylon.js umożliwia zaimplementowanie prostego mechanizmu autoryzacji.

W dashboardzie znajdziemy listę zdefiniowanych przez nas robotów:

- Udostępniamy proste API

Po wybraniu naszego robota (LEDMonster) otrzymujemy dostęp do listy poleceń, które za pomocą API możemy wykonać. Znajdują się tu wszystkie funkcje umieszczone w Robocie jako niestandardowe (czyli np. nasz ledToggle).

Kliknięcie przycisku [RUN] spowoduje natychmiastowe wywołanie zdefiniowanej przez nas funkcji, czego rezultatem będzie zmiana aktualnego stanu diody LED (on/off).

- Podłączamy Leap Motion

We wcześniejszych przykładach wykorzystywaliśmy Arduino z wpiętym do niego urządzeniem peryferyjnym - diodą LED.

W tym przykładzie będziemy zapalać i gasić ją za pomocą prostych gestów. Aby było to możliwe, wykorzystamy Leap Motion - kontroler ruchu znajdujący się na liście obsługiwanych przez Cylon.js urządzeń.

- Podłączamy Leap Motion

            var Cylon = require('cylon');
            var state = {
              lightOn: false
            };
            Cylon.robot({
              name: 'LEDMonster',
              connections: {
                  arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' },
                  leapmotion: { adaptor: 'leapmotion' }
              },
              devices: {
                  led: { driver: 'led', pin: 11, connection: 'arduino' },
                  leapmotion: { driver: 'leapmotion', connection: 'leapmotion' }
              },
        

- Podłączamy Leap Motion

              work: function(my) {
                  this.leapmotion.on('hand', function(hand) {
                      if(hand.type === 'left' && state.lightOn) {
                          console.log('Light OFF');
                          my.led.turnOff();
                          state.lightOn = false;
                      } else if(hand.type === 'right' && !state.lightOn) {
                          console.log('Light ON');
                          my.led.turnOn();
                          state.lightOn = true;
                      } else {}
                   });
                   ...
        

- Podłączamy Leap Motion

                   ...
                   /* Zapalaj/Wygaszaj diodę w interwale = 250ms JEŚLI state.lightOn */
                   every(250, function() {
                      if(state.lightOn) {
                          my.led.toggle();
                      } else {
                          my.led.turnOff();
                      }
                   });
              }
            }).start();
        

Co dalej? Spróbuj sam(a)!

Pytania?

Chętnie na nie teraz odpowiem :)
Możesz zadać je również po tej prezentacji (zapraszam w kuluary).

Pozostałe formy kontaktu:
marcin@makerlab.pl | LinkedIn | GoldenLine

Dzięki!

See you next month at WarsawJS

Fork me on Github