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

Dominik Bednarski porównuje STM32MP1 i OpenSTLinux oraz Raspberry Pi 5
Technologie End of Life i bezpieczeństwo sieci – wyzwania Europy związane z tzw. długiem technologicznym
Najczęstsze błędy firm przy wyborze dostawcy energii i jak ich uniknąć 



