Czujniki internetu

Użyj interfejsu Ogólny 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żliwić takie działanie jak wciągająca gra, monitorowanie aktywności fizycznej czy rzeczywistość rozszerzona lub wirtualna. Czy nie byłoby wspaniale połączyć aplikacje internetowe i na konkretne platformy? Wpisz interfejs Ogólny Sensor API do użytku w internecie.

Czym jest ogólny interfejs Sensor API?

Ogólny interfejs Sensor API to zestaw interfejsów, które udostępniają platformie internetowej czujniki. Interfejs API składa się z podstawowego interfejsu Sensor i zbioru konkretnych klas czujników. Posiadanie podstawowego interfejsu upraszcza implementację i proces specyfikacji konkretnych klas czujników. Spójrzmy na przykład na klasę Gyroscope. Jest bardzo mały! Podstawową funkcjonalność określa interfejs podstawowy, a Gyroscope jedynie rozszerza ją o 3 atrybuty reprezentujące prędkość kątową.

Niektóre klasy czujników łączą się z rzeczywistymi czujnikami sprzętowymi, takimi jak np. klasy akcelerometru czy żyroskopu. Są to tzw. czujniki niskiego poziomu. Inne czujniki, nazywane czujnikami fuzji fuzji, scalają dane z kilku czujników niskiego poziomu, aby udostępnić informacje, które w innym przypadku musiałby zostać obliczony przez skrypt. Na przykład czujnik AbsoluteOrientation zawiera gotową do użycia macierz 4 na 4 obrotów wygenerowaną na podstawie danych z akcelerometru, żyroskopu i magnetometru.

Może się wydawać, że platforma internetowa już dostarcza dane z czujników – masz absolutną rację. Na przykład zdarzenia DeviceMotion i DeviceOrientation udostępniają dane z czujnika ruchu. Dlaczego potrzebujemy nowego interfejsu API?

W porównaniu z dotychczasowymi interfejsami interfejs Ogólny Sensor API ma wiele zalet:

  • Ogólny interfejs Sensor API to platforma czujników, którą można łatwo rozszerzyć o nowe klasy czujników, a każda z nich zachowa interfejs ogólny. Kod klienta napisany dla jednego typu czujnika można wykorzystać ponownie w innym, z niewielką liczbą modyfikacji.
  • Możesz skonfigurować czujnik. Możesz na przykład ustawić częstotliwość próbkowania odpowiednią do swoich potrzeb.
  • Możesz sprawdzić, czy czujnik jest dostępny na platformie.
  • Odczyty z czujników mają bardzo precyzyjne sygnatury czasowe, co pozwala na lepszą synchronizację z innymi działaniami w aplikacji.
  • Modele danych z czujników i układy współrzędnych są jasno zdefiniowane, dzięki czemu dostawcy przeglądarek mogą wdrażać współdziałające rozwiązania.
  • Interfejsy oparte na ogólnym czujniku nie są powiązane z DOM (co oznacza, że nie są one obiektami navigator ani window). Stwarza to w przyszłości możliwość korzystania z interfejsu API w mechanizmach Service Worker lub implementacji go w środowiskach wykonawczych JavaScript bez interfejsu graficznego, takich jak urządzenia osadzone.
  • Aspekty bezpieczeństwa i prywatności mają najwyższy priorytet w ogólnym interfejsie API Sensor i zapewniają znacznie większe bezpieczeństwo w porównaniu ze starszymi interfejsami API do obsługi czujników. Istnieje również integracja z interfejsem Permissions API.
  • Automatyczna synchronizacja ze współrzędnymi ekranu jest dostępna dla Accelerometer, Gyroscope, LinearAccelerationSensor, AbsoluteOrientationSensor, RelativeOrientationSensor i Magnetometer.

Dostępne ogólne interfejsy API czujników

W chwili pisania masz do dyspozycji 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 funkcji

Wykrywanie funkcji sprzętowych interfejsów API jest trudne, ponieważ trzeba sprawdzić, czy przeglądarka obsługuje dany interfejs, oraz czy urządzenie ma odpowiedni czujnik. Sprawdź w prosty sposób, czy przeglądarka obsługuje dany interfejs. (Zastąp Accelerometer dowolnym z innych interfejsów wspomnianych powyżej).

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

Aby uzyskać konkretny wynik wykrywania cech, musisz też 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;
  }
}

Włókno poliestrowe

W przeglądarkach, które nie obsługują interfejsu Ogólny Sensor API, jest dostępny polyfill. Kod polyfill umożliwia wczytywanie tylko odpowiednich implementacji 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 });

Co to za czujniki? Jak mogę ich użyć?

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 omówimy szczegółowo każdy obsługiwany czujnik.

Akcelerometr i czujnik przyspieszenia liniowego

Pomiary czujnika akcelerometru

Czujnik Accelerometer mierzy przyspieszenie urządzenia, które hostuje czujnik, w 3 osiach (X, Y i Z). Ten czujnik jest czujnikiem bezwładnym, co oznacza, że w przypadku liniowego spadania całkowite przyspieszenie wynosiłoby 0 m/s2, a gdy urządzenie leży płasko na stole, przyspieszenie w górnej osi Z jest równe grawitacji Ziemi, czyli g g ≈ +9,8 m/s. Jeśli pchniesz urządzenie w prawo, przyspieszenie na osi X będzie dodatnie lub ujemne, jeśli nastąpi przyspieszenie od prawej do lewej.

Akcelerometry można używać do liczenia kroków, wykrywania ruchu lub do prostego ustalania orientacji urządzenia. Pomiary z akcelerometru są często łączone z danymi z innych źródeł, aby utworzyć czujniki termoizolacji, np. czujniki orientacji.

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

Czujnik grawitacji

Użytkownicy mogą już ręcznie określać odczyty w pobliżu czujnika grawitacji, sprawdzając odczyty Accelerometer i LinearAccelerometer, ale może to być kłopotliwe i zależy od dokładności wartości dostarczanych przez te czujniki. Platformy takie jak Android mogą w ramach systemu operacyjnego umożliwiać odczyt grawitacji. Powinno to być tańsze pod względem obliczeń, dokładniejsze wartości w zależności od sprzętu użytkownika i łatwiejsze w obsłudze pod względem ergonomii interfejsu API. GravitySensor zwraca efekt przyspieszenia wzdłuż osi X, Y i Z urządzenia wynikającej z grawitacji.

Żyroskop

Pomiary za pomocą czujnika żyroskopu

Czujnik Gyroscope mierzy prędkość kątową w radianach na sekundę wokół lokalnej osi X, Y i Z urządzenia. Większość urządzeń konsumenckich jest wyposażona w żyroskopy mechaniczne (MEMS), czyli czujniki bezwładne, które mierzą szybkość obrotu na podstawie siły bezwładnej Coriolisa. Żyroskopy MEMS są podatne na dryf, co jest spowodowane czułością grawitacyjną czujnika, co odkształca jego wewnętrzny układ mechaniczny. Żyroskopy oscylują z wysoką częstotliwością, na przykład Częstotliwość 10 kHz może zużywać więcej energii w porównaniu z innymi czujnikami.

Czujniki orientacji

Pomiary czujnika orientacji bezwzględnej

AbsoluteOrientationSensor to czujnik termojądrowy, który mierzy obrót urządzenia względem układu współrzędnych Ziemi, natomiast RelativeOrientationSensor dostarcza dane reprezentujące obrót urządzenia obsługującego czujniki ruchu w stosunku do nieruchomego referencyjnego układu współrzędnych.

Wszystkie nowoczesne platformy JavaScript 3D obsługują kwaternony i macierze rotacji do reprezentowania rotacji. Jeśli jednak użyjesz bezpośrednio WebGL, OrientationSensor ma wygodnie zarówno właściwość quaternion, jak i 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, np. wciągające gry, rzeczywistość rozszerzoną i wirtualną.

Więcej informacji o czujnikach ruchu, zaawansowanych przypadkach użycia i wymaganiach znajdziesz w objaśnieniu dotyczącym czujników ruchu.

Synchronizacja przy użyciu współrzędnych ekranu

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

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

Jednak w wielu przypadkach użycia takich jak gry czy rzeczywistość rozszerzona i wirtualna odczyty z czujników są dokonywane w układzie współrzędnych, który jest powiązany z orientacją ekranu.

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

Wcześniej ponowne mapowanie odczytów z czujników na współrzędne ekranu musiało być zaimplementowane w JavaScript. Takie podejście jest nieefektywne i znacznie zwiększa złożoność kodu aplikacji internetowej. Aplikacja internetowa musi obserwować zmiany orientacji ekranu i wykonywać transformacje współrzędnych na potrzeby odczytu z czujników, co nie jest łatwe w przypadku kątów czy kwaternionów Eulera.

Ogólny Sensor API jest o wiele prostszym i niezawodnym rozwiązaniem. Lokalny układ współrzędnych można skonfigurować dla wszystkich zdefiniowanych klas czujników przestrzennych: Accelerometer, Gyroscope, LinearAccelerationSensor, AbsoluteOrientationSensor, RelativeOrientationSensor i Magnetometer. Przekazując opcję referenceFrame do konstruktora obiektu czujnika, użytkownik określa, czy zwracane odczyty mają być rozpoznawane za pomocą 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 Ogólny Sensor API jest bardzo prosty i łatwy w obsłudze. Interfejs czujnika zawiera metody start() i stop() do kontrolowania stanu czujnika oraz kilka modułów obsługi zdarzeń do odbierania powiadomień o aktywacji czujnika, błędach i nowo dostępnych odczytach. Konkretne klasy czujników zwykle dodają do klasy podstawowej określone atrybuty odczytu.

Środowisko programistyczne

W trakcie tworzenia aplikacji będzie można używać czujników za pomocą aplikacji localhost. Jeśli programujesz aplikacje na urządzenia mobilne, skonfiguruj przekierowywanie portów na swój serwer lokalny i gotowe.

Gdy kod będzie gotowy, wdróż go na serwerze obsługującym HTTPS. Strony GitHub są wyświetlane przy użyciu protokołu HTTPS, więc stanowią świetne miejsce do udostępniania prezentacji.

Obrót modelu 3D

W tym prostym przykładzie używamy danych z czujnika orientacji bezwzględnej, aby zmienić kwartion obrotu modelu 3D. model to instancja klasy Object3D z właściwością quaternion. Poniższy fragment kodu z telefonu w orientacji poziomej 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 podczas obracania model w 3D w obrębie sceny WebGL.

Czujnik aktualizuje orientację modelu 3D
Czujnik aktualizuje orientację modelu 3D

Piętnik

Poniższy fragment kodu jest wyodrębniony z wersji demonstracyjnej punchometru i pokazuje, jak można użyć czujnika przyspieszenia liniowego do obliczenia maksymalnej prędkości urządzenia przy założeniu, że początkowo leży ono w bezruchu.

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

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

Demonstracja aplikacji internetowej do pomiaru szybkości wpychania
Pomiar szybkości ciosów

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

W niektórych przypadkach do korzystania z interfejsu Ogólny Sensor API nie jest potrzebne urządzenie fizyczne. Narzędzia deweloperskie w Chrome doskonale obsługują symulację orientacji urządzenia.

Narzędzia deweloperskie w Chrome używane do zastępowania danych dotyczących niestandardowej orientacji telefonu wirtualnego
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 złośliwych stron internetowych. Implementacje interfejsów General Sensor API egzekwują pewne ograniczenia, by zmniejszyć potencjalne zagrożenia dla bezpieczeństwa i prywatności. Deweloperzy, którzy zamierzają korzystać z tego interfejsu API, muszą wziąć pod uwagę te ograniczenia.

Tylko HTTPS

Interfejs General Sensor API to zaawansowana funkcja, która pozwala na korzystanie z niej tylko w bezpiecznych kontekstach. W praktyce oznacza to, że korzystanie z interfejsu Ogólny Sensor API wymaga dostępu do strony przez HTTPS. W trakcie programowania możesz to zrobić przez http://localhost, ale w środowisku produkcyjnym musisz umieścić na serwerze HTTPS. Sprawdzone metody i wytyczne znajdziesz w kolekcji Bezpieczeństwo.

Integracja zasad uprawnień

Integracja z zasadą uprawnień w ogólnym interfejsie Sensor API kontroluje dostęp do danych z czujników ramki.

Domyślnie obiekty Sensor można tworzyć tylko w ramce głównej lub ramkach podrzędnych z tej samej domeny. Zapobiega to niesankcjom odczytu danych z czujników przez elementy iframe z innych domen. To domyślne działanie można zmodyfikować przez jawne włączenie lub wyłączenie odpowiednich funkcji kontrolowanych przez zasady.

Fragment kodu poniżej pokazuje przyznawanie dostępu do danych z akcelerometru do elementu iframe z innych domen. Oznacza to, że teraz można tworzyć w nim obiekty Accelerometer i LinearAccelerationSensor.

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

Dostarczanie odczytów z czujników można zawiesić

Odczyty z czujników są dostępne tylko dla widocznej strony internetowej, tj. wtedy, gdy użytkownik faktycznie jej używa. Co więcej, dane z czujników nie zostaną przekazane do ramki nadrzędnej, jeśli fokus użytkownika zmieni się na ramkę podrzędną z innych domen. Zapobiega to sytuacji, w której ramka nadrzędna może wywnioskować dane wejściowe użytkownika.

Co dalej?

W najbliższej przyszłości wprowadzimy zestaw określonych klas czujników, np. czujnik światła otoczenia lub czujnik zbliżeniowy. Jednak dzięki ogromnej rozszerzalności struktury czujnika ogólnego możemy przewidzieć pojawienie się kolejnych klas reprezentujących różne rodzaje czujników.

Kolejną ważną kwestią w przyszłości jest ulepszenie samego interfejsu Ogólny Sensor API. Ogólna specyfikacja czujnika jest obecnie rekomendacją dla kandydatów, co oznacza, że nadal mamy czas na wprowadzenie poprawek i wprowadzenie nowych funkcji, których potrzebują deweloperzy.

Możesz mi pomóc!

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

Możesz zgłaszać problemy ze specyfikacjami i bugs związane z implementacją Chrome.

Zasoby

Podziękowania

Ten artykuł napisali Joe Medley i Kayce Basques. Baner powitalny autorstwa: Misko, dostępny na Wikimedia Commons.