Czujniki w internecie

Używaj interfejsu Generic Sensor API, aby uzyskać dostęp do czujników na urządzeniu, takich jak akcelerometry, żyroskopy i magnetometry.

Alex Shalamov
Alex Shalamov
Mikhail Pozdnyakov
Mikhail Pozdnyakov

Obecnie dane z czujników są używane w wielu aplikacjach na poszczególnych platformach, aby umożliwiać takie zastosowania jak wciągające gry, monitorowanie aktywności czy rzeczywistość rozszerzona lub wirtualna. Czy nie byłoby fajnie połączyć aplikacji na poszczególne platformy z aplikacjami internetowymi? Poznaj Generic Sensor API w internecie.

Czym jest interfejs Generic Sensor API?

Interfejs Generic Sensor API to zestaw interfejsów, które udostępniają urządzenia czujnikowe platformie internetowej. Interfejs API składa się z interfejsu bazowego Sensor i zestawu konkretnych klas czujników, które na nim bazują. Dzięki interfejsowi bazowemu upraszczamy proces wdrażania i specyfikacji konkretnych klas czujników. Przyjrzyjmy się na przykład klasie Gyroscope. Jest bardzo mały. Podstawową funkcjonalność określa interfejs bazowy, a Gyroscope rozszerza go o 3 atrybuty reprezentujące prędkość kątową.

Niektóre klasy czujników łączą się z rzeczywistymi czujnikami sprzętowymi, np. z akcelerometrem lub żyroskopem. Są one nazywane czujnikami niskiego poziomu. Inne czujniki, zwane czujnikami fuzji, łączą dane z kilku czujników niskiego poziomu, aby udostępniać informacje, które w inny sposób musiałby obliczyć skrypt. Na przykład czujnik AbsoluteOrientation udostępnia gotową macierz rotacji 4x4 na podstawie danych uzyskanych z akcelerometru, żyroskopu i magnetometru.

Możesz pomyśleć, że platforma internetowa już udostępnia dane z czujników i będziesz mieć rację. Na przykład zdarzenia DeviceMotionDeviceOrientation udostępniają dane z czujnika ruchu. Dlaczego więc potrzebujemy nowego interfejsu API?

W porównaniu z dotychczasowymi interfejsami Generic Sensor API ma wiele zalet:

  • Generic Sensor API to platforma czujników, którą można łatwo rozszerzać o nowe klasy czujników. Każda z tych klas zachowuje interfejs ogólny. Kod klienta napisany dla jednego typu czujnika można ponownie wykorzystać w przypadku innego czujnika po wprowadzeniu niewielkich zmian.
  • Możesz skonfigurować czujnik. Możesz na przykład ustawić częstotliwość próbkowania odpowiednią do potrzeb Twojej aplikacji.
  • Możesz sprawdzić, czy czujnik jest dostępny na platformie.
  • Odczyty z czujników mają sygnatury czasowe o wysokiej precyzji, co umożliwia lepszą synchronizację z innymi działaniami w aplikacji.
  • Modele danych z czujników i systemy współrzędnych są jasno zdefiniowane, co umożliwia dostawcom przeglądarek wdrażanie rozwiązań interoperacyjnych.
  • Interfejsy oparte na Generic Sensor nie są powiązane z DOM (co oznacza, że nie są obiektami navigator ani window), co otwiera w przyszłości możliwości korzystania z interfejsu API w usługach Service Worker lub implementowania go w środowiskach wykonawczych JavaScript bez interfejsu, takich jak urządzenia wbudowane.
  • Bezpieczeństwo i prywatność są najważniejsze w przypadku interfejsu Generic Sensor API i zapewniają znacznie lepsze zabezpieczenia w porównaniu ze starszymi interfejsami Sensor API. Jest on zintegrowany z interfejsem Permissions API.
  • Automatyczna synchronizacja ze współrzędnymi ekranu jest dostępna w przypadku urządzeń Accelerometer, Gyroscope, LinearAccelerationSensor, AbsoluteOrientationSensor, RelativeOrientationSensorMagnetometer.

Dostępne interfejsy API czujników ogólnych

W momencie pisania tego artykułu dostępnych jest kilka czujników, z którymi możesz eksperymentować.

Czujniki ruchu:

  • Accelerometer
  • Gyroscope
  • LinearAccelerationSensor
  • AbsoluteOrientationSensor
  • RelativeOrientationSensor
  • GravitySensor

Czujniki środowiskowe:

  • AmbientLightSensor (Za flagą #enable-generic-sensor-extra-classes w Chromium).
  • Magnetometer (Za flagą #enable-generic-sensor-extra-classes w Chromium).

Wykrywanie cech

Wykrywanie funkcji interfejsów API sprzętu jest trudne, ponieważ musisz sprawdzić, czy przeglądarka obsługuje dany interfejs i czy urządzenie ma odpowiedni czujnik. Sprawdzenie, czy przeglądarka obsługuje interfejs, jest proste. (Zastąp Accelerometer dowolnym z innych interfejsów wymienionych powyżej).

if ('Accelerometer' in window) {
  // The `Accelerometer` interface is supported by the browser.
  // Does the device have an accelerometer, though?
}

Aby uzyskać wiarygodny wynik wykrywania funkcji, musisz też spróbować połączyć się z czujnikiem. Ten przykład pokazuje, jak to zrobić.

let accelerometer = null;
try {
  accelerometer = new Accelerometer({ frequency: 10 });
  accelerometer.onerror = (event) => {
    // Handle runtime errors.
    if (event.error.name === 'NotAllowedError') {
      console.log('Permission to access sensor was denied.');
    } else if (event.error.name === 'NotReadableError') {
      console.log('Cannot connect to the sensor.');
    }
  };
  accelerometer.onreading = (e) => {
    console.log(e);
  };
  accelerometer.start();
} catch (error) {
  // Handle construction errors.
  if (error.name === 'SecurityError') {
    console.log('Sensor construction was blocked by the Permissions Policy.');
  } else if (error.name === 'ReferenceError') {
    console.log('Sensor is not supported by the User Agent.');
  } else {
    throw error;
  }
}

Wypełnienie

W przypadku przeglądarek, które nie obsługują interfejsu Generic Sensor API, dostępny jest polyfill. Polyfill umożliwia wczytywanie tylko implementacji odpowiednich czujników.

// Import the objects you need.
import { Gyroscope, AbsoluteOrientationSensor } from './src/motion-sensors.js';

// And they're ready for use!
const gyroscope = new Gyroscope({ frequency: 15 });
const orientation = new AbsoluteOrientationSensor({ frequency: 60 });

Do czego służą te wszystkie czujniki? Jak mogę z nich korzystać?

Czujniki to obszar, który może wymagać krótkiego wprowadzenia. Jeśli znasz się na czujnikach, możesz od razu przejść do sekcji z praktycznym kodowaniem. W przeciwnym razie przyjrzyjmy się szczegółowo każdemu obsługiwanemu czujnikowi.

Akcelerometr i czujnik przyspieszenia liniowego

Pomiary z czujnika akcelerometru

Czujnik Accelerometer mierzy przyspieszenie urządzenia, na którym się znajduje, w 3 osiach (X, Y i Z). Ten czujnik jest czujnikiem bezwładnościowym, co oznacza, że gdy urządzenie spada swobodnie w linii prostej, całkowite zmierzone przyspieszenie wynosi 0 m/s2, a gdy urządzenie leży płasko na stole, przyspieszenie w kierunku do góry (oś Z) będzie równe przyspieszeniu ziemskiemu, czyli g ≈ +9,8 m/s2, ponieważ mierzy siłę, z jaką stół wypycha urządzenie do góry. Jeśli przesuniesz urządzenie w prawo, przyspieszenie na osi X będzie dodatnie, a jeśli przesuniesz je z prawej strony w lewo – ujemne.

Akcelerometry mogą być używane do takich funkcji jak liczenie kroków, wykrywanie ruchu czy proste określanie orientacji urządzenia. Pomiary akcelerometru są często łączone z danymi z innych źródeł, aby tworzyć czujniki fuzji, np. czujniki orientacji.

LinearAccelerationSensor Mierzy przyspieszenie, które jest stosowane do urządzenia, na którym znajduje się czujnik, z wyłączeniem wpływu grawitacji. Gdy urządzenie jest w spoczynku, np. leży płasko na stole, czujnik mierzy przyspieszenie ≈ 0 m/s2 na 3 osiach.

Czujnik grawitacyjny

Użytkownicy mogą już ręcznie uzyskiwać odczyty zbliżone do odczytów czujnika grawitacyjnego, sprawdzając ręcznie odczyty AccelerometerLinearAccelerometer, ale może to być uciążliwe i zależeć od dokładności wartości podawanych przez te czujniki. Platformy takie jak Android mogą udostępniać odczyty grawitacji w ramach systemu operacyjnego. Powinno to być tańsze pod względem obliczeń, zapewniać dokładniejsze wartości w zależności od sprzętu użytkownika i być łatwiejsze w użyciu pod względem ergonomii interfejsu API. GravitySensor zwraca efekt przyspieszenia wzdłuż osi X, Y i Z urządzenia spowodowanego grawitacją.

Żyroskop

Pomiary z czujnika żyroskopu

Czujnik Gyroscope mierzy prędkość kątową w radianach na sekundę wokół lokalnych osi X, Y i Z urządzenia. Większość urządzeń konsumenckich ma mechaniczne żyroskopy (MEMS), które są czujnikami bezwładnościowymi mierzącymi szybkość obrotu na podstawie bezwładnościowej siły Coriolisa. Żyroskopy MEMS są podatne na dryfowanie spowodowane wrażliwością czujnika na grawitację, która odkształca wewnętrzny układ mechaniczny czujnika. Żyroskopy oscylują z względnie wysoką częstotliwością, np. 10 kHz, dlatego może zużywać więcej energii niż inne czujniki.

Czujniki orientacji

Pomiary z czujnika orientacji bezwzględnej

AbsoluteOrientationSensor to czujnik fuzji, który mierzy obrót urządzenia względem układu współrzędnych Ziemi, a RelativeOrientationSensor dostarcza dane reprezentujące obrót urządzenia z czujnikami ruchu względem stacjonarnego układu współrzędnych odniesienia.

Wszystkie nowoczesne platformy JavaScript 3D obsługują kwaternionymacierze rotacji do reprezentowania rotacji. Jeśli jednak używasz WebGL bezpośrednio, OrientationSensor ma zarówno właściwość quaternion, jak i metodę populateMatrix(). Oto kilka fragmentów:

three.js

let torusGeometry = new THREE.TorusGeometry(7, 1.6, 4, 3, 6.3);
let material = new THREE.MeshBasicMaterial({ color: 0x0071c5 });
let torus = new THREE.Mesh(torusGeometry, material);
scene.add(torus);

// Update mesh rotation using quaternion.
const sensorAbs = new AbsoluteOrientationSensor();
sensorAbs.onreading = () => torus.quaternion.fromArray(sensorAbs.quaternion);
sensorAbs.start();

// Update mesh rotation using rotation matrix.
const sensorRel = new RelativeOrientationSensor();
let rotationMatrix = new Float32Array(16);
sensor_rel.onreading = () => {
  sensorRel.populateMatrix(rotationMatrix);
  torus.matrix.fromArray(rotationMatrix);
};
sensorRel.start();

BABYLON

const mesh = new BABYLON.Mesh.CreateCylinder('mesh', 0.9, 0.3, 0.6, 9, 1, scene);
const sensorRel = new RelativeOrientationSensor({ frequency: 30 });
sensorRel.onreading = () => mesh.rotationQuaternion.FromArray(sensorRel.quaternion);
sensorRel.start();

WebGL

// Initialize sensor and update model matrix when new reading is available.
let modMatrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
const sensorAbs = new AbsoluteOrientationSensor({ frequency: 60 });
sensorAbs.onreading = () => sensorAbs.populateMatrix(modMatrix);
sensorAbs.start();

// Somewhere in rendering code, update vertex shader attribute for the model
gl.uniformMatrix4fv(modMatrixAttr, false, modMatrix);

Czujniki orientacji umożliwiają różne zastosowania, takie jak wciągające gry, rzeczywistość rozszerzona i wirtualna.

Więcej informacji o czujnikach ruchu, zaawansowanych przypadkach użycia i wymaganiach znajdziesz w dokumencie wyjaśniającym działanie czujników ruchu.

Synchronizacja ze współrzędnymi ekranu

Domyślnie odczyty czujników przestrzennych są rozwiązywane w lokalnym układzie współrzędnych powiązanym z urządzeniem, który nie uwzględnia orientacji ekranu.

Układ współrzędnych urządzenia
Układ współrzędnych urządzenia

Wiele przypadków użycia, takich jak gry czy rzeczywistość rozszerzona i wirtualna, wymaga jednak, aby odczyty z czujników były rozwiązywane w układzie współrzędnych powiązanym z orientacją ekranu.

Układ współrzędnych ekranu
Układ współrzędnych ekranu

Wcześniej ponowne mapowanie odczytów czujników na współrzędne ekranu musiało być implementowane w JavaScript. Takie podejście jest nieefektywne i znacznie zwiększa złożoność kodu aplikacji internetowej. Musi ona śledzić zmiany orientacji ekranu i wykonywać przekształcenia współrzędnych dla odczytów z czujników, co nie jest łatwe w przypadku kątów Eulera ani kwaternionów.

Interfejs Generic Sensor API to znacznie prostsze i bardziej niezawodne rozwiązanie. Lokalny układ współrzędnych można skonfigurować dla wszystkich zdefiniowanych klas czujników przestrzennych: Accelerometer, Gyroscope, LinearAccelerationSensor, AbsoluteOrientationSensor, RelativeOrientationSensorMagnetometer. Przekazując opcję referenceFrame do konstruktora obiektu czujnika, użytkownik określa, czy zwracane odczyty będą rozstrzygane we współrzędnych urządzenia czy ekranu.

// Sensor readings are resolved in the Device coordinate system by default.
// Alternatively, could be RelativeOrientationSensor({referenceFrame: "device"}).
const sensorRelDevice = new RelativeOrientationSensor();

// Sensor readings are resolved in the Screen coordinate system. No manual remapping is required!
const sensorRelScreen = new RelativeOrientationSensor({ referenceFrame: 'screen' });

Kodujmy!

Interfejs Generic Sensor API jest bardzo prosty i łatwy w użyciu. Interfejs czujnika ma metody start()stop() do kontrolowania stanu czujnika oraz kilka procedur obsługi zdarzeń do otrzymywania powiadomień o aktywacji czujnika, błędach i nowo dostępnych odczytach. Konkretne klasy czujników zwykle dodają do klasy bazowej swoje atrybuty odczytu.

Środowisko programistyczne

Podczas tworzenia aplikacji możesz używać czujników za pomocą localhost. Jeśli tworzysz aplikację na urządzenia mobilne, skonfiguruj przekierowanie portów na serwerze lokalnym i gotowe.

Gdy kod będzie gotowy, wdróż go na serwerze obsługującym HTTPS. Strony GitHub są wyświetlane przez HTTPS, co sprawia, że są świetnym miejscem do udostępniania wersji demonstracyjnych.

Obracanie modelu 3D

W tym prostym przykładzie używamy danych z czujnika orientacji bezwzględnej do modyfikowania kwaternionu rotacji modelu 3D. model to instancja klasy Object3D three.js, która ma właściwość quaternion. Poniższy fragment kodu z wersji demonstracyjnej orientacji telefonu pokazuje, jak można użyć czujnika orientacji bezwzględnej do obracania modelu 3D.

function initSensor() {
  sensor = new AbsoluteOrientationSensor({ frequency: 60 });
  sensor.onreading = () => model.quaternion.fromArray(sensor.quaternion);
  sensor.onerror = (event) => {
    if (event.error.name == 'NotReadableError') {
      console.log('Sensor is not available.');
    }
  };
  sensor.start();
}

Orientacja urządzenia zostanie odzwierciedlona w obrocie 3D model w scenie WebGL.

Czujniki aktualizują orientację modelu 3D
Czujnik aktualizuje orientację modelu 3D

Punchmeter

Poniższy fragment kodu pochodzi z demonstracji punchmeter i pokazuje, jak za pomocą czujnika przyspieszenia liniowego można obliczyć maksymalną prędkość urządzenia przy założeniu, że początkowo jest ono nieruchome.

this.maxSpeed = 0;
this.vx = 0;
this.ax = 0;
this.t = 0;

/* … */

this.accel.onreading = () => {
  let dt = (this.accel.timestamp - this.t) * 0.001; // In seconds.
  this.vx += ((this.accel.x + this.ax) / 2) * dt;

  let speed = Math.abs(this.vx);

  if (this.maxSpeed < speed) {
    this.maxSpeed = speed;
  }

  this.t = this.accel.timestamp;
  this.ax = this.accel.x;
};

Bieżąca prędkość jest obliczana jako przybliżenie całki funkcji przyspieszenia.

Demonstracyjna aplikacja internetowa do pomiaru szybkości uderzeń
Pomiar szybkości uderzenia

Debugowanie i zastępowanie czujników za pomocą Narzędzi deweloperskich w Chrome

W niektórych przypadkach nie potrzebujesz fizycznego urządzenia, aby korzystać z interfejsu Generic Sensor API. Narzędzia deweloperskie w Chrome doskonale obsługują symulowanie orientacji urządzenia.

Narzędzia deweloperskie w Chrome używane do zastępowania niestandardowych danych o orientacji wirtualnego telefonu
Symulowanie orientacji urządzenia za pomocą Narzędzi deweloperskich w Chrome

Prywatność i bezpieczeństwo

Odczyty z czujników to dane wrażliwe, które mogą być narażone na różne ataki ze strony złośliwych stron internetowych. Implementacje interfejsów Generic Sensor API wymuszają kilka ograniczeń, aby zmniejszyć potencjalne zagrożenia dla bezpieczeństwa i prywatności. Deweloperzy, którzy zamierzają korzystać z interfejsu API, muszą wziąć pod uwagę te ograniczenia. Wymieńmy je pokrótce.

Tylko HTTPS

Interfejs Generic Sensor API to zaawansowana funkcja, dlatego przeglądarka zezwala na jej używanie tylko w bezpiecznych kontekstach. W praktyce oznacza to, że aby korzystać z interfejsu Generic Sensor API, musisz mieć dostęp do strony przez HTTPS. Podczas tworzenia możesz to zrobić za pomocą adresu http://localhost, ale w przypadku wersji produkcyjnej musisz mieć protokół HTTPS na serwerze. Więcej informacji znajdziesz w kolekcji Bezpieczeństwo, w której znajdziesz sprawdzone metody i wytyczne.

Integracja zasad dotyczących uprawnień

Integracja zasad dotyczących uprawnień w interfejsie Generic Sensor API kontroluje dostęp do danych z czujników w ramach.

Domyślnie obiekty Sensor można tworzyć tylko w głównej ramce lub w podrzędnych ramkach z tej samej domeny, co uniemożliwia nieautoryzowane odczytywanie danych z czujników przez ramki iframe z innych domen. To domyślne zachowanie można zmienić, włączając lub wyłączając odpowiednie funkcje kontrolowane przez zasady.

Poniższy fragment kodu ilustruje przyznawanie dostępu do danych z akcelerometru ramce iframe z innej domeny, co oznacza, że można w niej teraz tworzyć obiekty Accelerometer lub LinearAccelerationSensor.

<iframe src="https://third-party.com" allow="accelerometer" />

Dostarczanie odczytów z czujników może zostać zawieszone

Odczyty z czujników są dostępne tylko dla widocznej strony internetowej, czyli wtedy, gdy użytkownik wchodzi z nią w interakcję. Ponadto dane z czujników nie będą przekazywane do ramki nadrzędnej, jeśli użytkownik przełączy fokus na ramkę podrzędną z innej domeny. Zapobiega to wywnioskowaniu przez ramkę nadrzędną danych wejściowych użytkownika.

Co dalej?

W najbliższej przyszłości zostanie wdrożony zestaw określonych już klas czujników, takich jak czujnik światła otoczenia czy czujnik zbliżeniowy. Dzięki dużej rozszerzalności platformy Generic Sensor możemy jednak oczekiwać pojawienia się jeszcze większej liczby nowych klas reprezentujących różne typy czujników.

Kolejnym ważnym obszarem przyszłych prac jest ulepszenie samego interfejsu Generic Sensor API. Specyfikacja Generic Sensor jest obecnie rekomendacją kandydacką, co oznacza, że wciąż jest czas na wprowadzenie poprawek i nowych funkcji, których potrzebują deweloperzy.

Możesz pomóc.

Specyfikacje czujników osiągnęły poziom dojrzałości rekomendacji, dlatego bardzo cenimy sobie opinie deweloperów stron internetowych i przeglądarek. Daj nam znać, jakie funkcje warto dodać lub co chcesz zmodyfikować w obecnym interfejsie API.

Możesz zgłaszać problemy ze specyfikacją oraz błędy w implementacji Chrome.

Zasoby

Podziękowania

Ten artykuł został sprawdzony przez Joego Medleya i Kayce Basques.