LinkedIn YouTube Facebook
Szukaj

Wstecz
SoM / SBC

Serwer WWW z elementami grafiki 3D – praktyczne wykorzystanie pakietów Node.js oraz Three.js w systemach wbudowanych (część 1)

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:

npm install i2c

Udostępnia on proste API do realizacji niskopoziomowych operacji zapisu/odczytu danych na magistrali, np.:

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.

Jak wspomniano na wstępie artykułu, szczegóły związane z przygotowaniem obsługi modułu żyroskopu w języku C zostaną przedstawione w kolejnej części artykułu. Na potrzeby aktualnego etapu, przygotujmy najprostszą aplikację generującą losowe wyniki dla trzech osi (X, Y, Z). Dane będą wypisywane na standardowe wyjście z interwałem 1 sekundy w formacie:
<pomiarX><spacja><pomiarY><spacja><pomiarZ>
Przykładowa implementacja:

/* … */
while (1) {
sleep (1);
printf ("%d %d %d\n", rand()%360, rand()%360, rand()%360);
fflush (stdout);
}
/* … */

Kompilacja:

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:

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):

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):

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:

child.on ('close', function (code) {
console.log ('exit: ' + code);
});

Pełna zawartość pliku main.js została przedstawiona na listingu 6.

var http = require ('http');
var fs = require ('fs');
var spawn = require('child_process').spawn;

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!');
});

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);
});

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.

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

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

<!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

contact@lukasz-skalski.com

Łukasz Skalski - absolwent Politechniki Gdańskiej, miłośnik FLOSS, autor książki "Linux. Podstawy i aplikacje dla systemów embedded" oraz szeregu artykułów dotyczących programowania systemów wbudowanych. Zawodowo związany z firmą Samsung. Wszystkie wolne chwile poświęca projektowaniu i programowaniu urządzeń wyposażonych w mikroprocesory 8-/16- i 32-bitowe. Szczególnym zainteresowaniem obejmuje tematykę systemu Linux w aplikacjach na urządzenia embedded.