Sterowanie świecą PLAYBULB przez Web Bluetooth

1. O czym to jest?

IMG_19700101_023537~2~2.jpg

Z tego ćwiczenia dowiesz się, jak sterować świecą LED PLAYBULB za pomocą samego JavaScriptu dzięki interfejsowi Web Bluetooth API. Po drodze poznasz też funkcje JavaScript ES2015, takie jak klasy, funkcje strzałkowe, MapPromises.

Czego się nauczysz

  • Jak wchodzić w interakcje z urządzeniem Bluetooth w pobliżu za pomocą JavaScriptu
  • Jak korzystać z klas ES2015, funkcji strzałkowych, map i obietnic

Czego potrzebujesz

2. Odtwórz pierwszy

Zanim zaczniesz wykonywać to ćwiczenie, możesz sprawdzić ostateczną wersję aplikacji, którą zamierzasz utworzyć, na stronie https://googlecodelabs.github.io/candle-bluetooth i pobawić się urządzeniem PLAYBULB Candle Bluetooth, które masz do dyspozycji.

Możesz też zobaczyć, jak zmieniam kolory, na stronie https://www.youtube.com/watch?v=fBCPA9gIxlU

3. Konfiguracja

Pobierz przykładowy kod

Przykładowy kod możesz pobrać w tym pliku ZIP:

lub sklonuj to repozytorium Git:

git clone https://github.com/googlecodelabs/candle-bluetooth.git

Jeśli źródło zostało pobrane jako plik ZIP, po jego rozpakowaniu powinien pojawić się folder główny candle-bluetooth-master.

Instalowanie i weryfikowanie serwera WWW

Możesz używać własnego serwera WWW, ale ten przewodnik został zaprojektowany tak, aby dobrze współpracował z serwerem WWW Chrome. Jeśli nie masz jeszcze tej aplikacji, możesz ją zainstalować z Chrome Web Store.

Po zainstalowaniu aplikacji Web Server for Chrome kliknij skrót Aplikacje na pasku zakładek:

Screen Shot 2016-11-16 at 4.10.42 PM.png

W wyświetlonym oknie kliknij ikonę serwera internetowego:

9f3c21b2cf6cbfb5.png

Następnie zobaczysz to okno dialogowe, w którym możesz skonfigurować lokalny serwer WWW:

Screen Shot 2016-11-16 at 3.40.47 PM.png

Kliknij przycisk Wybierz folder i wybierz poziom główny sklonowanego (lub rozpakowanego) repozytorium. Umożliwi to wyświetlanie pracy w trakcie tworzenia za pomocą adresu URL wyróżnionego w oknie serwera WWW (w sekcji Adresy URL serwera WWW).

W sekcji Opcje zaznacz pole wyboru obok opcji „Automatycznie wyświetlaj plik index.html”, jak pokazano poniżej:

Screen Shot 2016-11-16 at 3.40.56 PM.png

Teraz otwórz witrynę w przeglądarce (klikając wyróżniony adres URL serwera internetowego). Powinna wyświetlić się strona podobna do tej:

Screen Shot 2016-11-16 at 3.20.22 PM.png

Jeśli chcesz zobaczyć, jak ta aplikacja wygląda na telefonie z Androidem, musisz włączyć zdalne debugowanie na Androidzieskonfigurować przekierowanie portów (domyślny numer portu to 8887). Następnie możesz po prostu otworzyć nową kartę Chrome pod adresem http://localhost:8887 na telefonie z Androidem.

Dalsze czynności

Obecnie ta aplikacja internetowa nie ma wielu funkcji. Zacznijmy dodawać obsługę Bluetootha.

4. Odkryj świeczkę

Zaczniemy od napisania biblioteki, która używa klasy JavaScript ES2015 dla urządzenia Bluetooth PLAYBULB Candle.

Zachowaj spokój. Składnia klasy nie wprowadza do JavaScriptu nowego modelu dziedziczenia obiektowego. Zapewnia po prostu znacznie bardziej przejrzystą składnię do tworzenia obiektów i obsługi dziedziczenia, o czym możesz przeczytać poniżej.

Najpierw zdefiniujmy klasę PlaybulbCandleplaybulbCandle.js i utwórzmy instancję playbulbCandle, która będzie później dostępna w pliku app.js.

playbulbCandle.js

(function() {
  'use strict';

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

Aby poprosić o dostęp do pobliskiego urządzenia Bluetooth, musimy wywołać funkcję navigator.bluetooth.requestDevice. Urządzenie PLAYBULB Candle reklamuje się nieustannie (jeśli nie jest jeszcze sparowane), używając stałego identyfikatora UUID usługi Bluetooth GATT, znanego w skrócie jako 0xFF02. Możemy więc po prostu zdefiniować stałą i dodać ją do parametru usług filtrów w nowej publicznej metodzie connect klasy PlaybulbCandle.

Będziemy też wewnętrznie śledzić obiekt BluetoothDevice, aby w razie potrzeby mieć do niego dostęp. Ponieważ funkcja navigator.bluetooth.requestDevice zwraca obietnicę JavaScript ES2015, zrobimy to w metodzie then.

playbulbCandle.js

(function() {
  'use strict';

  const CANDLE_SERVICE_UUID = 0xFF02;

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(function(device) {
        this.device = device;
      }.bind(this)); 
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

Ze względów bezpieczeństwa wykrywanie urządzeń Bluetooth w pobliżu za pomocą navigator.bluetooth.requestDevice musi być wywoływane przez gest użytkownika, np. dotknięcie lub kliknięcie myszą. Dlatego wywołamy metodę connect, gdy użytkownik kliknie przycisk „Połącz” w pliku app.js:

app.js

document.querySelector('#connect').addEventListener('click', function(event) {
  document.querySelector('#state').classList.add('connecting');
  playbulbCandle.connect()
  .then(function() {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
  })
  .catch(function(error) {
    console.error('Argh!', error);
  });
});

Uruchamianie aplikacji

W tym momencie otwórz witrynę w przeglądarce (klikając adres URL serwera WWW wyróżniony w aplikacji serwera WWW) lub po prostu odśwież istniejącą stronę. Kliknij zielony przycisk „Połącz”, wybierz urządzenie w selektorze i otwórz ulubioną konsolę Narzędzi deweloperskich za pomocą skrótu klawiszowego Ctrl + Shift + J. Zwróć uwagę na zalogowany obiekt BluetoothDevice.

Screen Shot 2016-11-16 at 3.27.12 PM.png

Jeśli Bluetooth jest wyłączony lub urządzenie Bluetooth PLAYBULB Candle jest wyłączone, może pojawić się błąd. W takim przypadku włącz je i spróbuj ponownie.

Bonus obowiązkowy

Nie wiem, jak Ty, ale ja widzę w tym kodzie za dużo znaków function() {}. Zamiast tego użyjmy () => {} funkcji strzałkowych JavaScript ES2015. Są one niezwykle przydatne: mają wszystkie zalety funkcji anonimowych, ale nie mają wad związanych z wiązaniem.

playbulbCandle.js

(function() {
  'use strict';

  const CANDLE_SERVICE_UUID = 0xFF02;

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(device => {
        this.device = device;
      }); 
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

Dalsze czynności

- OK... czy mogę porozmawiać z tą świecą?

– Jasne… przejdź do następnego kroku

Najczęstsze pytania

5. Przeczytaj coś

Co więc zrobić, gdy otrzymasz BluetoothDevice w ramach obietnicy navigator.bluetooth.requestDevice? Połączmy się ze zdalnym serwerem GATT Bluetooth, który zawiera definicje usługi i charakterystyki Bluetooth, wywołując funkcję device.gatt.connect():

playbulbCandle.js

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(device => {
        this.device = device;
        return device.gatt.connect();
      });
    }
  }

Odczytaj nazwę urządzenia

W tym momencie jesteśmy połączeni z serwerem GATT urządzenia Bluetooth PLAYBULB Candle. Teraz chcemy uzyskać dostęp do głównej usługi GATT (wcześniej reklamowanej jako 0xFF02) i odczytać charakterystykę nazwy urządzenia (0xFFFF) należącą do tej usługi. Możesz to łatwo osiągnąć, dodając nową metodę getDeviceName do klasy PlaybulbCandle i używając device.gatt.getPrimaryServiceservice.getCharacteristic. Metoda characteristic.readValue zwróci w rzeczywistości wartość DataView, którą po prostu zdekodujemy za pomocą funkcji TextDecoder.

playbulbCandle.js

  const CANDLE_DEVICE_NAME_UUID = 0xFFFF;

  ...

    getDeviceName() {
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_DEVICE_NAME_UUID))
      .then(characteristic => characteristic.readValue())
      .then(data => {
        let decoder = new TextDecoder('utf-8');
        return decoder.decode(data);
      });
    }

Dodajmy to do app.js, wywołując playbulbCandle.getDeviceName po połączeniu i wyświetlając nazwę urządzenia.

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
    return playbulbCandle.getDeviceName().then(handleDeviceName);
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

function handleDeviceName(deviceName) {
  document.querySelector('#deviceName').value = deviceName;
}

W tym momencie otwórz witrynę w przeglądarce (klikając adres URL serwera WWW wyróżniony w aplikacji serwera WWW) lub po prostu odśwież istniejącą stronę. Upewnij się, że lampka PLAYBULB Candle jest włączona, a następnie kliknij przycisk „Połącz” na stronie. Pod selektorem kolorów powinna pojawić się nazwa urządzenia.

Screen Shot 2016-11-16 at 3.29.21 PM.png

Odczytywanie poziomu baterii

Urządzenie Bluetooth PLAYBULB Candle ma też standardową charakterystykę Bluetooth poziomu baterii, która zawiera poziom baterii urządzenia. Oznacza to, że możemy używać standardowych nazw, takich jak battery_service w przypadku identyfikatora UUID usługi Bluetooth GATT i battery_level w przypadku identyfikatora UUID charakterystyki Bluetooth GATT.

Dodajmy do klasy PlaybulbCandle nową metodę getBatteryLevel i odczytajmy poziom baterii w procentach.

playbulbCandle.js

    getBatteryLevel() {
      return this.device.gatt.getPrimaryService('battery_service')
      .then(service => service.getCharacteristic('battery_level'))
      .then(characteristic => characteristic.readValue())
      .then(data => data.getUint8(0));
    }

Musimy też zaktualizować obiekt JavaScript options, aby dodać usługę baterii do klucza optionalServices, ponieważ nie jest ona reklamowana przez urządzenie Bluetooth PLAYBULB Candle, ale jest niezbędna do uzyskania do niej dostępu.

playbulbCandle.js

      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
                     optionalServices: ['battery_service']};
      return navigator.bluetooth.requestDevice(options)

Jak wcześniej, podłączmy to do app.js, wywołując playbulbCandle.getBatteryLevel po uzyskaniu nazwy urządzenia i wyświetlmy poziom baterii.

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
    return playbulbCandle.getDeviceName().then(handleDeviceName)
    .then(() => playbulbCandle.getBatteryLevel().then(handleBatteryLevel));
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

function handleDeviceName(deviceName) {
  document.querySelector('#deviceName').value = deviceName;
}

function handleBatteryLevel(batteryLevel) {
  document.querySelector('#batteryLevel').textContent = batteryLevel + '%';
}

W tym momencie otwórz witrynę w przeglądarce (klikając adres URL serwera WWW wyróżniony w aplikacji serwera WWW) lub po prostu odśwież istniejącą stronę. Kliknij przycisk „Połącz” na stronie. Powinny się wyświetlić nazwa urządzenia i poziom baterii.

Screen Shot 2016-11-16 at 3.29.21 PM.png

Dalsze czynności

– Jak mogę zmienić kolor tej żarówki? Dlatego tu jestem!

– Obiecuję, że jesteś już blisko…

Najczęstsze pytania

6. Zmień kolor

Zmiana koloru jest tak prosta, jak wpisanie określonego zestawu poleceń do charakterystyki Bluetooth (0xFFFC) w podstawowej usłudze GATT reklamowanej jako 0xFF02. Na przykład zmiana koloru świecy PLAYBULB na czerwony wymagałaby wpisania tablicy 8-bitowych liczb całkowitych bez znaku równej [0x00, 255, 0, 0], gdzie 0x00 to nasycenie bieli, a 255, 0, 0 to odpowiednio wartości czerwieni, zieleni i niebieskiego .

Użyjemy characteristic.writeValue, aby zapisać dane w charakterystyce Bluetooth w nowej setColor publicznej metodzie klasy PlaybulbCandle. Gdy obietnica zostanie spełniona, zwrócimy też rzeczywiste wartości czerwonego, zielonego i niebieskiego, abyśmy mogli ich później użyć w app.js:

playbulbCandle.js

  const CANDLE_COLOR_UUID = 0xFFFC;

  ...

    setColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_COLOR_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }

Zaktualizujmy funkcję changeColor w pliku app.js, aby wywoływała funkcję playbulbCandle.setColor, gdy zaznaczony jest przycisk „No Effect” (Brak efektu). Globalne zmienne koloru r, g, b są już ustawione, gdy użytkownik kliknie obszar selektora kolorów.

app.js

function changeColor() {
  var effect = document.querySelector('[name="effectSwitch"]:checked').id;
  if (effect === 'noEffect') {
    playbulbCandle.setColor(r, g, b).then(onColorChanged);
  }
}

W tym momencie otwórz witrynę w przeglądarce (klikając adres URL serwera WWW wyróżniony w aplikacji serwera WWW) lub po prostu odśwież istniejącą stronę. Kliknij przycisk „Połącz” na stronie i kliknij selektor kolorów, aby zmienić kolor lampy PLAYBULB Candle tyle razy, ile chcesz.

Screen Shot 2016-11-16 at 3.31.37 PM.png

Moar candle effects

Jeśli kiedykolwiek zapaliłeś(-aś) świecę, wiesz, że jej światło nie jest statyczne. Na szczęście istnieje inna charakterystyka Bluetooth (0xFFFB) w podstawowej usłudze GATT, która jest reklamowana jako 0xFF02 i umożliwia użytkownikowi ustawienie niektórych efektów świecy.

Aby ustawić „efekt świecy” dla instancji, wpisz [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. Możesz też ustawić „efekt migania” za pomocą [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].

Dodajmy metody setCandleEffectColorsetFlashingColor do klasy PlaybulbCandle.

playbulbCandle.js

  const CANDLE_EFFECT_UUID = 0xFFFB;

  ...

    setCandleEffectColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }
    setFlashingColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }

Zaktualizujmy też funkcję changeColorapp.js, aby wywoływała funkcję playbulbCandle.setCandleEffectColor, gdy jest zaznaczony przycisk „Efekt świecy”, i funkcję playbulbCandle.setFlashingColor, gdy jest zaznaczony przycisk „Miganie”. Tym razem użyjemy atrybutu switch, jeśli nie masz nic przeciwko.

app.js

function changeColor() {
  var effect = document.querySelector('[name="effectSwitch"]:checked').id;
  switch(effect) {
    case 'noEffect':
      playbulbCandle.setColor(r, g, b).then(onColorChanged);
      break;
    case 'candleEffect':
      playbulbCandle.setCandleEffectColor(r, g, b).then(onColorChanged);
      break;
    case 'flashing':
      playbulbCandle.setFlashingColor(r, g, b).then(onColorChanged);
      break;
  }
}

W tym momencie otwórz witrynę w przeglądarce (klikając adres URL serwera WWW wyróżniony w aplikacji serwera WWW) lub po prostu odśwież istniejącą stronę. Kliknij przycisk „Połącz” na stronie i wypróbuj efekty świecy i migania.

Screen Shot 2016-11-16 at 3.33.23 PM.png

Dalsze czynności

– To wszystko? 3 słabe efekty świecy? Is this why I'm here?

– jest ich więcej, ale tym razem musisz je znaleźć samodzielnie.

7. Dołóż dodatkowych starań

I oto jesteśmy. Może Ci się wydawać, że to już koniec, ale aplikacja jeszcze się nie skończyła. Sprawdźmy, czy rozumiesz, co zostało skopiowane i wklejone w tym laboratorium. Oto co możesz teraz zrobić samodzielnie, aby ta aplikacja działała jeszcze lepiej.

Dodawanie brakujących efektów

Oto dane dotyczące brakujących efektów:

  • Puls: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (możesz dostosować tam wartości r, g, b)
  • Tęcza: [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00] (osoby z epilepsją powinny unikać tego efektu)
  • Tęcza: [0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]

Oznacza to dodanie nowych metod setPulseColor, setRainbowsetRainbowFade do klasy PlaybulbCandle i wywołanie ich w changeColor.

Rozwiązywanie problemu „brak efektu”

Jak być może zauważysz, opcja „bez efektu” nie resetuje żadnego trwającego efektu. To drobna, ale jednak istotna kwestia. Naprawmy to. W setColor musisz najpierw sprawdzić, czy efekt jest w toku, za pomocą nowej zmiennej klasy _isEffectSet, a jeśli true, wyłączyć efekt przed ustawieniem nowego koloru za pomocą tych danych: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].

Wpisz nazwę urządzenia

To proste! Wpisanie niestandardowej nazwy urządzenia jest tak proste, jak wpisanie poprzedniej nazwy urządzenia Bluetooth. Zalecam użycie metody TextEncoder encode, aby uzyskać Uint8Array zawierający nazwę urządzenia.

Następnie dodam „input” eventListener do document.querySelector('#deviceName') i nazwę playbulbCandle.setDeviceName, aby uprościć proces.

Moja ma nazwę PLAY💡 CANDLE!

8. To wszystko.

Czego się nauczysz

  • Jak wchodzić w interakcje z urządzeniem Bluetooth w pobliżu za pomocą JavaScriptu
  • Jak korzystać z klas ES2015, funkcji strzałkowych, map i obietnic

Następne kroki