Serwer WWW – odczyt danych z procesu obsługi modułu żyroskopu
Na obecnym etapie realizacji projektu wiemy już jak nawiązać prostą komunikację pomiędzy serwerem a klientem. Do pełnej realizacji celu brakuje wciąż informacji w jaki sposób „poinformować” serwer o aktualnych danych pomiarowych, które będą zwracane przez podłączony do komputera za pomocą magistrali I2C moduł żyroskopu. Do najprostszych rozwiązań tego problemu możemy zaliczyć np. bezpośrednią implementacją obsługi żyroskopu w kodzie serwera – z wykorzystaniem operacji na plikach lub gotowych modułów Node.js, instalowanych poprzez menadżer pakietów npm.
Przykładem takiego modułu może być pakiet i2c, instalowany poleceniem:
1 |
npm install i2c |
Udostępnia on proste API do realizacji niskopoziomowych operacji zapisu/odczytu danych na magistrali, np.:
1 2 3 4 5 6 7 8 9 |
var i2c = require('i2c'); var wire = new i2c(address, {device: '/dev/i2c-1'}); wire.writeByte(byte, function(err) {}); wire.writeBytes(command, [byte0, byte1], function(err) {}); wire.readByte(function(err, res) {}); |
Pomimo tego, że API modułu i2c jest bardzo czytelne, a ewentualna reimplementacja istniejących kawałków kodu lub bibliotek z języka C nie powinna być problematyczna, do realizacji projektu użyjemy alternatywnego podejścia. Całość obsługi modułu żyroskopu zostanie przygotowana w języku C i skompilowana do postaci pliku wykonywalnego gyro-i2c (patrz ramka poniżej). Dlaczego? Po pierwsze, większość programistów związanych z niskopoziomowymi systemami wbudowanymi nie zaryzykuje implementacji obsługi sprzętu w JavaScript (język C jest tutaj bardziej naturalnym wyborem), a po drugie – metoda ta może być przydatna, gdy nie posiadamy dostępu do kodów źródłowych aplikacji obsługujących sprzęt – wówczas jedyną możliwością jest odczyt danych ze standardowego wyjścia procesu.
<pomiarX><spacja><pomiarY><spacja><pomiarZ>
Przykładowa implementacja:
1 2 3 4 5 6 7 |
/* … */ while (1) { sleep (1); printf ("%d %d %d\n", rand()%360, rand()%360, rand()%360); fflush (stdout); } /* … */ |
1 |
gcc main.c -o /tmp/gyro-i2c |
Do uruchomienia i komunikacji z procesem gyro-i2c z poziomu Node.js wykorzystamy wbudowany moduł child_process. Za pomocą metody spawn() utworzymy nowy proces potomny oraz zdefiniujemy dla niego funkcje zwrotną obsługi standardowego wyjścia (wywoływaną w chwili gdy program gyro-i2c zwróci kolejną porcję danych z wynikami pomiarów).
Analogicznie jak w poprzednich podpunktach, do realizacji tego etapu wykorzystamy pliki z listingu 4 oraz listingu 5. Edycję rozpoczniemy od skryptu main.js w którym zaimportujemy wbudowany moduł child_process:
1 |
var spawn = require('child_process').spawn; |
W kolejnym kroku, za pomocą wywołania spawn() utworzymy nowy proces potomny realizujący kod programu gyro-i2c (skopiowany uprzednio do folderu /tmp):
1 |
var child = spawn ('/tmp/gyro-i2c'); |
Ostatnią modyfikacją w skrypcie main.js jest dodanie funkcji zwrotnych do obsługi kanałów stdout (funkcja przesyła odczytane dane do przeglądarki w postaci komunikatu xyz) oraz stderr (funkcja wypisuje w konsoli dane odczytane ze standardowego strumienia błędów):
1 2 3 4 5 6 7 |
child.stdout.on ('data', function (data) { io.emit ('xyz', {message: data.toString().split('\n')[0]}); }); child.stderr.on ('data', function (data) { console.log ('stderr: ' + data); }); |
Warto również zaimplementować obsługę zdarzenia close, która poinformuje nas o zakończeniu procesu potomnego i zwróconym przez niego kodzie wyjścia:
1 2 3 |
child.on ('close', function (code) { console.log ('exit: ' + code); }); |
Pełna zawartość pliku main.js została przedstawiona na listingu 6.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var http = require ('http'); var fs = require ('fs'); <strong>var spawn = require('child_process').spawn;</strong> var index = fs.readFileSync (__dirname + '/index.html'); var PORT = 8080; var server = http.createServer (function handler (request, response) { response.writeHead (200, {'Content-Type': 'text/html'}); response.end (index); }); var io = require ('socket.io').listen(server); io.on ('connection', function (socket) { console.log ('We have new connection!'); }); <strong>var child = spawn ('/tmp/gyro-i2c'); child.stdout.on ('data', function (data) { io.emit ('xyz', {message: data.toString().split('\n')[0]}); }); child.stderr.on ('data', function (data) { console.log ('stderr: ' + data); }); child.on ('close', function (code) { console.log ('exit: ' + code); });</strong> server.listen (PORT); |
Listing 6. Skrypt main.js z zaimplementowaną obsługą procesu potomnego
Przystosujmy również plik index.html do nowych wymagań projektu, tj. wyświetlenia wartości trzech pomiarów dla osi X, Y oraz Z. W tym celu w sekcji <body> utwórzmy prostą tabelę zawierającą identyfikatory pól x_val, y_val oraz z_val – listing 7.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<table> <tr> <th>X [deg]</th> <td><p id="x_val">---</p></td> </tr> <tr> <th>Y [deg]</th> <td><p id="y_val">---</p></td> </tr> <tr> <th>Z [deg]</th> <td><p id="z_val">---</p></td> </tr> </table> |
Listing 7. Tabela z wynikami pomiarów żyroskopowych – plik index.html
W sekcji <head> zmodyfikujmy kod obsługi wiadomości xyz. Odczytana linia danych zostanie podzielona względem separatora ‘ ‘ (spacja), a wyniki pomiarów przypisane do poszczególnych identyfikatorów pól – listing 8.
1 2 3 4 5 6 7 8 9 10 11 12 |
<script> var socket = io(); socket.on ('xyz', function (data) { var arr = data.message.split(" "); document.getElementById("x_val").innerHTML = arr[0]; document.getElementById("y_val").innerHTML = arr[1]; document.getElementById("z_val").innerHTML = arr[2]; }); </script> |
Listing 8. Parsowanie otrzymanych danych i przypisanie do identyfikatorów pól
Dla poprawienia estetyki utworzonej strony, w sekcji head dodajmy wpis formatujący wygląd tabeli. Pełna zawartość pliku index.html została przedstawiona na listingu 9.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<!DOCTYPE html> <html> <head> <style> table, th, td { border: 1px solid black; } th, td { border: 1px solid black; padding: 15px; } </style> <script src='/socket.io/socket.io.js'></script> <script> var socket = io(); socket.on ('xyz', function (data) { var arr = data.message.split(" "); document.getElementById("x_val").innerHTML = arr[0]; document.getElementById("y_val").innerHTML = arr[1]; document.getElementById("z_val").innerHTML = arr[2]; }); </script> </head> <body> <h1>Gyroscope I2C</h1> <table> <tr> <th>X [deg]</th> <td><p id="x_val">---</p></td> </tr> <tr> <th>Y [deg]</th> <td><p id="y_val">---</p></td> </tr> <tr> <th>Z [deg]</th> <td><p id="z_val">---</p></td> </tr> </table> </body> </html> |
Listing 9. Wyświetlanie wyników pomiarów postaci tabeli – plik index.html
Po ponownym uruchomieniu serwera oraz odświeżeniu zawartości strony internetowej, generowane przez aplikację gyro-i2c, losowe wyniki pomiarów powinny zostać wyświetlone i na bieżąco aktualizowane – Rysunek 3.
Rys. 3. Prezentacja wyników pomiarów w oknie przeglądarki internetowej
Łukasz Skalski