Suche
Wir müssen über Bluetooth LE reden

Bluetooth Low Energy in JavaScript und Node.js

Philip Ackermann

© Shutterstock / Jacob Lund

Bluetooth Low Energy wird im Bereich des Internet of Things immer interessanter. Sei es, um vom Smartphone den Fitness-Tracker auszulesen oder um von einem Raspberry Pi auf die Messwerte von verschiedenen Sensoren zuzugreifen. Über die Node.js-Module bleno und noble lässt sich die Kommunikation zwischen BLE-Geräten auch unter JavaScript nutzen.

Zunächst sollen ein paar grundlegende Begriffe geklärt werden, die im Zusammenhang mit BLE verwendet werden. Nehmen wir dazu das Beispiel einer Smartphone-App, die sich mit verschiedenen E-Health-Geräten, wie Blutdruckmessgeräten oder Pulsmessgeräten verbinden kann. Diese E-Health-Geräte werden im Zusammenhang mit BLE als Peripheral-Devices bezeichnet (kurz: Peripherals), die Smartphone-App dagegen als Central-Device (kurz: Central). Beide Rollen sind im so genannten GAP-Protokoll (Generic Access Profile) festgelegt, über das entschieden wird, ob zwei BLE-Geräte miteinander kommunizieren dürfen oder nicht.

W-JAX
Ivo Wessel

QWERTZ war gestern! Interfaces von morgen

mit Ivo Wessel (IN BEST HANDS UG)

Dominik Obermaier

Securing MQTT

mit Dominik Obermaier (dc-square GmbH)

Um auf sich aufmerksam zu machen, senden Peripherals kontinuierlich oder in einer bestimmten Frequenz per Broadcast ein so genanntes Advertising-Signal (Abb. 1). Die Informationen, die dabei mitgesendet werden, enthalten in der Regel den Namen des Peripherals, eine ID und einige weitere Eigenschaften. Anhand dieser Informationen kann ein Central dann entscheiden, ob es sich mit dem Peripheral verbinden möchte. Dabei kann ein Central durchaus zu mehreren Peripherals eine Verbindung herstellen. Ein Smartphone kann sich also mit mehreren E-Health-Geräten verbinden. Ein Peripheral dagegen kann immer nur mit einem einzigen Central verbunden sein, ein E-Health-Gerät beispielsweise also nicht gleichzeitig mit mehreren Smartphones. Das ist übrigens auch einer der Gründe, warum Sie mit Ihrer Bluetoothtastatur nicht gleichzeitig zwei Rechner steuern können.

ackermann_ble_1

Abb. 1: Das Prinzip des Generic Access Profile

Wie zwei Geräte via BLE miteinander kommunizieren, wird über das so genannte Generic Attribute Profile (GATT) definiert (Abb. 2). Auf den Peripherals läuft dazu jeweils ein GATT-Server, auf dem Central ein entsprechender GATT-Client. Sprich: Wenn sich ein Smartphone mit mehreren E-Health-Geräten verbindet, läuft auf jedem dieser Peripherals ein GATT-Server, auf dem Smartphone dagegen jeweils ein GATT-Client.

ackermann_ble_2

Abb. 2: Das Prinzip des Generic Attribute Profile

Welche Daten von einem GATT-Server bereitgestellt werden, wird über so genannte Profile definiert. Dabei handelt es sich um eine Zusammenstellung verschiedener Services, die von dem jeweiligen Peripheral zur Verfügung gestellt werden (Abb. 3). Die Bluetooth Special Interest Group (SIG) hat diesbezüglich bereits einige Profile und Services definiert, beispielsweise für Blutdruck- oder Herzfrequenzmessgeräte, siehe hier und hier. Prinzipiell kann man Profile und Services aber auch selbst definieren und implementieren.

Services wiederum enthalten eine oder mehrere Characteristics, auf die lesend und/oder schreibend zugegriffen werden kann, beispielsweise um Messwerte auszulesen oder das Peripheral in einen bestimmten Zustand zu versetzen. Auch hierfür gibt es unter bereits einige vordefinierte Characteristics. Aber auch hier kann man als Entwickler wieder seinen Fantasien quasi freien Lauf lassen. Jede Characteristic wiederum enthält einen Wert sowie optional einen oder mehrere so genannter Descriptors, sprich textuelle Beschreibungen der Characteristic. Darüber hinaus besteht die Möglichkeit, ähnlich dem Observer- oder Publish-/Subscribe-Entwurfsmuster, vom Central über Änderungen einer Characteristic auf einem Peripheral benachrichtigt zu werden.

ackermann_ble_3

Abb. 3: Der Zusammenhang von Profiles, Services und Characteristics

„bleno“ und „noble“: die Node.js-Module im Einsatz

Im folgenden Tutorial sollen nun ein Peripheral und ein Central mit Node.js und mit den Modulen bleno und noble implementiert werden. Das Peripheral wird dabei einen Service mit drei Characteristics bereitstellen, auf die dann vom Central in bestimmter Reihenfolge lesend und schreibend zugegriffen wird. Außerdem wird sich das Central an einer der Characteristics als Listener registrieren.

Benötigt werden für das Tutorial zwei Rechner: Einer, der die Rolle des Centrals einnimmt und einer, der die Rolle des Peripherals einnimmt. Hier bieten sich beispielsweise zwei Raspberry Pis an, die praktischerweise seit Version 3 BLE direkt mit an Bord haben. Steht die Hardware, muss anschließend auf den beteiligten Raspberry Pis die JavaScript-Plattform Node.js installiert werden. Dies geschieht relativ einfach über folgende Befehle, die nacheinander die Packages von www.nodesource.com zum Package Repository hinzufügen und anschließend die Installation von Node.js übernehmen:

  sudo curl –sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
sudo apt-get install -y nodejs

Die erfolgreiche Installation von Node.js können Sie anschließend überprüfen, indem Sie sich beispielsweise über den Befehl node -v die Versionsnummer ausgeben lassen. Mehr Details zu der Installation auf verschiedenen Betriebssystemen finden sich unter. Für die Implementierung von Peripherals steht für Node.js das Modul bleno zur Verfügung, für die Implementierung von Centrals das Modul noble vom gleichen Entwickler.

Sowohl bleno als auch noble setzen beim Einsatz auf einem Raspberry Pi verschiedene Pakete voraus, die zunächst über folgenden Befehl für jeden der beiden Rechner installiert werden müssen:

sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev

Die beiden Node.js-Module werden anschließend wie für Node.js-Module gewohnt über den Node.js Package Manger (NPM) installiert und gegebenenfalls über den Parameter –save als Abhängigkeit in die Konfigurationsdatei package.json des jeweiligen Projekts geschrieben. Auf dem Rechner, der als Peripheral dienen soll, führt man also den Befehl npm install bleno –save aus, auf dem Rechner, der als Central dienen soll, dagegen den Befehl npm install noble –save.

Als Basis für das Tutorial dient das Hello-World-Pizza-Beispiel von der Entwicklerseite, wobei dieses aber deutlich abgeändert wurde: Zum einen wurde der dortige ES5-Code vollständig in ES2015-Code migriert, zum anderen wurde das Beispiel aber auch ‑ fast schon wichtiger ‑ in eine der Jahreszeit entsprechenden Domäne verfrachtet: Statt eines Pizzaservice haben wir es dementsprechend mit einem Eiscremeservice zu tun. Der Use Case: Das Central wird über BLE am Peripheral ein Eis bestellen, das Peripheral anschließend das Central benachrichtigen, sobald das Eis fertig ist. Praxisnäher geht es fast nicht.

Das Peripheral soll dabei, wie in Abbildung 4 zu sehen, einen Service bereitstellen (IceCreamService), der wiederum drei Characteristics enthält: eine Characteristic (IceCreamContainerCharacteristic), über die sich der Eisbehälter definieren lässt (sprich Waffel oder Becher), eine zweite, über die sich die Eissorten definieren lassen (IceCreamTypesCharacteristic) und eine dritte, die darüber informiert, sobald das Eis fertig ist (IceCreamStatusCharacteristic).

ackermann_ble_4

Abb. 4: Peripheral-Service zum Eisbestellen

Das Peripheral implementieren

Den Quelltext für das Peripheral zeigt Listing 1 (Datei peripheral.js). Über den Befehl require() werden zunächst die benötigten Abhängigkeiten importiert: das Modul bleno, die „Klasse“ IceCream, die das Objektmodell repräsentiert und der IceCreamService. Anschließend werden über die Methode on() zwei Event-Listener registriert: einer für das Event stateChange und einer für das Event advertisingStart. Übrigens: „Klasse“ deswegen in Anführungszeichen, weil JavaScript bekanntermaßen auch trotz der in ES2015 neu eingeführten Klassensyntax keine echten Klassen kennt.

Das Event stateChange wird ausgelöst, wenn sich der Zustand der BLE-Schnittstelle des Peripherals ändert. Erst, wenn der Zustand den Wert poweredOn hat, kann auf die BLE-Schnittstelle zugegriffen werden. Der Aufruf der Methode startAdvertisting() sorgt dann dafür, dass kontinuierlich ein entsprechendes Advertising-Signal gesendet wird, um auf sich aufmerksam zu machen. Als erster Parameter wird der Name übergeben, unter dem das Gerät sichtbar sein soll. Als zweiter Parameter ein Array der UUIDs der BLE-Services, die das Gerät bereitstellt. Und als dritter Parameter ein Callback-Handler, der aufgerufen wird, sollte irgendein Fehler auftreten. Ist der Zustand der BLE-Schnittstelle dagegen nicht auf poweredOn, sorgt der Aufruf von stopAdvertisting() dafür, dass das Peripheral mit dem Senden des Advertising-Signals aufhört. Sobald der Advertising-Modus erfolgreich gestartet wurde, wird das advertisingStart-Event ausgelöst. Im entsprechenden Event-Listener werden im Beispiel nun die Services initialisiert, die durch das Peripheral zur Verfügung gestellt werden sollen. Da das Peripheral in unserem Beispiel nur einen Service bereitstellen soll, enthält das übergebene Array entsprechend nur einen einzigen Eintrag.

'use strict';
// Importe
const bleno = require('bleno');
const IceCream = require('./lib/om/IceCream');
const IceCreamService = require('./lib/services/IceCreamService');

const peripheralName = 'Philips Ice Cream';
const iceCream = new IceCream();
const iceCreamService = new IceCreamService(iceCream);

bleno.on('stateChange', (state) => {
  if (state === 'poweredOn') {
    bleno.startAdvertising(
      peripheralName,
      [iceCreamService.uuid],
      (error) => {
        if (error) {
          console.error(error);
        }
      }
    );
  }
  else {
    bleno.stopAdvertising();
  }
});

bleno.on('advertisingStart', (error) => {
  if (!error) {
    console.log('Starte Advertising...');
    bleno.setServices([
      iceCreamService
    ]);
  }
});

Bevor wir die Datei peripheral.js starten, sei im Folgenden zunächst noch ein Blick auf den Code für den Service, die Characteristics und das zugrundeliegende Objektmodell geworfen. Der Code für den Service ist in Listing 2 zu sehen. Die Klasse IceCreamService leitet dabei von PrimaryService ab, einer Basisklasse, die von bleno zur Verfügung gestellt wird und bereits einige Basisfunktionalität mit sich bringt. Im Konstruktor wird die UUID des Service und die zur Verfügung gestellten Characteristics in Form eines Konfigurationsobjekts an den Superkonstruktor übergeben, konkret also Instanzen von IceCreamContainerCharacteristic, IceCreamTypesCharacteristic und IceCreamStatusCharacteristic.

All den entsprechenden Konstruktoraufrufen und auch dem Konstruktor von IceCreamService wird darüber hinaus eine Instanz des Objektmodells übergeben, sprich eine Instanz der Klasse IceCream. Diese Kopplung könnte man sicherlich noch aufheben, beispielsweise über Dependency Injection oder indem zwischen den Klassen über Events kommuniziert wird, für das Beispiel wollen wir uns mit dieser Abhängigkeit aber zufrieden geben.

'use strict';
const util = require('util');
const bleno = require('bleno');
const Constants = require('../constants/Constants');

const IceCreamContainerCharacteristic = 
  require('../characteristics/IceCreamContainerCharacteristic');
const IceCreamTypesCharacteristic = 
  require('../characteristics/IceCreamTypesCharacteristic');
const IceCreamStatusCharacteristic = 
  require('../characteristics/IceCreamStatusCharacteristic');

module.exports = class IceCreamService extends bleno.PrimaryService {
  constructor(currentIceCream) {
    super({
      uuid: Constants.ICE_CREAM_SERVICE_UUID,
      characteristics: [
        new IceCreamContainerCharacteristic(currentIceCream),
        new IceCreamTypesCharacteristic(currentIceCream),
        new IceCreamStatusCharacteristic(currentIceCream)
      ]
    });
    this._currentIceCream = currentIceCream;
  }
  get currentIceCream() {
    return this._currentIceCream;
  }
}

Die Implementierungen der Characteristics sind prinzipiell alle ähnlich aufgebaut, wie exemplarisch anhand der Klassen IceCreamTypesCharacteristic und IceCreamStatusCharacteristic in den Listings 3 und 4 zu sehen. Alle Characteristics-Klassen leiten von der allgemeinen Klasse Characteristics ab und überschreiben je nachdem eine oder beide der folgenden Methoden: onWriteRequest(), um das Verhalten zu definieren, sobald schreibend auf die jeweilige Characteristic zugegriffen wird und onReadRequest(), um analog das Verhalten zu definieren, wenn lesend auf die jeweilige Characteristic zugegriffen wird.

Doch alles der Reihe nach: Zunächst muss natürlich innerhalb der Konstruktoren die jeweilige Characteristic konfiguriert werden. Dies geschieht im Wesentlichen über ein Konfigurationsobjekt, das dem Elternkonstruktor als Parameter übergeben wird. Über die Eigenschaft uuid dieses Objekts wird die UUID der jeweiligen Characteristic definiert, über die Eigenschaft properties Angaben darüber festgelegt, auf welche Art und Weise auf die Characteristic zugegriffen werden kann read für lesend, write für schreibend und notify, wenn das Central von der Characteristic bei Änderungen benachrichtigt werden soll. Optional lässt sich über die Eigenschaft descriptors zudem ein Array von Descriptor-Instanzen übergeben, über die sich die Characteristic beschreiben lässt. Diese Information kann dann beispielsweise vom Central ausgelesen werden.

'use strict';
const util = require('util');
const bleno = require('bleno');
const Characteristic = bleno.Characteristic;
const Constants = require('../constants/Constants');

module.exports = class IceCreamTypesCharacteristic extends Characteristic {
  constructor(currentIceCream) {
    super({
      uuid: Constants.ICE_CREAM_TYPES_CHARACTERISTIC_UUID,
      properties: ['read', 'write'],
      descriptors: [
        new bleno.Descriptor({
          uuid: '2901',
          value: 'Gets or sets the ice cream types.'
        })
      ]
    });
    this._currentIceCream = currentIceCream;
  }
  get currentIceCream() {
    return this._currentIceCream;
  }
  onWriteRequest(data, offset, withoutResponse, callback) {
    if (offset) {
      return callback(Characteristic.RESULT_ATTR_NOT_LONG);
    }
    else if (data.length !== 2) {
      return callback(Characteristic.RESULT_INVALID_ATTRIBUTE_LENGTH);
    }
    else {
      this.currentIceCream.types = data.readUInt16BE(0);
      return callback(Characteristic.RESULT_SUCCESS);
    }
  }
  onReadRequest(offset, callback) {
    if (offset) {
      return callback(Characteristic.RESULT_ATTR_NOT_LONG, null);
    }
    else {
      let data = new Buffer(2);
      data.writeUInt16BE(this.currentIceCream.types, 0);
      return callback(Characteristic.RESULT_SUCCESS, data);
    }
  };
}
'use strict';
const util = require('util');
const bleno = require('bleno');
const Characteristic = bleno.Characteristic;
const Constants = require('../constants/Constants');

module.exports = class IceCreamStatusCharacteristic extends Characteristic {
  constructor(currentIceCream) {
    super({
      uuid: Constants.ICE_CREAM_STATUS_CHARACTERISTIC_UUID,
      properties: ['notify', 'write'],
      descriptors: [
        new bleno.Descriptor({
          uuid: '2901',
          value: 'Creates the ice cream and sends a notification when done.'
        })
      ]
    });
    this._currentIceCream = currentIceCream;
  }
  get currentIceCream() {
    return this._currentIceCream;
  }
  onWriteRequest(data, offset, withoutResponse, callback) {
    if (offset) {
      return callback(Characteristic.RESULT_ATTR_NOT_LONG);
    }
    else if (data.length !== 2) {
      return callback(Characteristic.RESULT_INVALID_ATTRIBUTE_LENGTH);
    }
    else {
      this.currentIceCream.once('ready', (result) => {
        if (this.updateValueCallback) {
          let data = new Buffer(1);
          data.writeUInt8(result, 0);
          this.updateValueCallback(data);
        }
      });
      this.currentIceCream.prepare();
      return callback(Characteristic.RESULT_SUCCESS);
    }
  }
}

Das den BLE-Klassen zugrundeliegende Objektmodell ist in Listing 5 zu sehen. Im Wesentlichen handelt es sich hierbei um eine Klasse mit zwei Eigenschaften: eine Eigenschaft für den Eisbehälter (Eigenschaft container) sowie eine Eigenschaft für die Eissorten (Eigenschaft types). Um der BLE-Kommunikation Rechnung zu tragen, enthalten beide Eigenschaften als Werte Binärwerte. Die möglichen Werte werden dabei über die statischen Eigenschaften CONTAINER, TYPES und STATUS zur Verfügung gestellt.

'use strict';
const util = require('util');
const events = require('events');

const IceCream = class IceCream extends events.EventEmitter {
  constructor() {
    super();
    this._container = IceCream.CONTAINER.CUP;
    this._types = IceCream.TYPES.NONE;
  }
  prepare() {
    console.log('Bereite Eis vor.');
    setTimeout(() => {
      let result = IceCream.status.READY;
      this.emit('ready', result);
    }, 5000);
  }
  set container(value) {
    this._container = value;
  }
  get container() {
    return this._container;
  }
  set types(value) {
    this._types = value;
  }
  get types() {
    return this._types;
  }
}
// Globale Eigenschaften
IceCream.CONTAINER = {
  CUP: 0,
  CONE: 1
}
IceCream.TYPES = {
  NONE:         0,
  CHOCOLATE:    1 << 0,
  VANILLA:      1 << 1
}
IceCream.STATUS = {
  ORDERED:    0,
  PREPARED:   1,
  READY:      2
}
module.exports = IceCream;

Das Central Module implementieren

Für das Implementieren des Centrals wird das Node.js-Modul noble benötigt. Den entsprechenden Quelltext zeigt Listing 6 (Datei central.js). Nach Importieren der Abhängigkeiten werden über das noble-Objekt zwei Event-Listener registriert: zum einen wie auch bei der Implementierung des Peripherals ein Event-Listener für das Event stateChange, zum anderen ein Event-Listener für das Event discover. Ersteres wird wie schon bei bleno ausgelöst, sobald die BLE-Schnittstelle genutzt werden kann, Letzteres immer dann, wenn ein neues Peripheral gefunden wurde.

Um nach BLE-Geräten zu suchen, muss allerdings zuvor innerhalb des stateChange-Event-Listeners die Methode startScanning() aufgerufen werden. Dieser Methode kann optional ein Array von Service-UUIDs übergeben werden. Das schränkt die Suche auf solche Peripherals ein, die einen Service mit entsprechender UUID anbieten. Analog zu startScanning() kann über die Methode stopScanning() das Scannen nach Geräten zu einem späteren Zeitpunkt auch wieder gestoppt werden. Dies wird im Listing an zwei Stellen gemacht: Zum einen dann, wenn die BLE-Schnittstelle nicht zur Verfügung steht, zum anderen, sobald ein Peripheral mit einem Service für die angegebene UUID gefunden wurde.

Der Event-Listener für das discover-Event erhält als Parameter zudem ein Objekt, welches das Peripheral repräsentiert. Über die Methode connect() lässt sich anschließend eine Verbindung zu dem Gerät herstellen. Dabei wird man über eine Callback-Funktion benachrichtigt, wenn die Verbindung hergestellt wurde oder ein Fehler auftritt. Danach werden über den Aufruf von discoverServices() die optional auf bestimmte UUIDs begrenzten Services des verbundenen Geräts ausgelesen und über den Aufruf der Methode discoverCharacteristics() die jeweils von jedem Service zur Verfügung gestellten Characteristics. Übergibt man letzterer Methode ein leeres Array, werden übrigens alle Characteristics aufgelistet. Die drei Characteristics des IceCreamService werden dabei in den drei Variablen iceCreamContainerCharacteristic, iceCreamTypesCharacteristics und iceCreamStatusCharacteristics gespeichert.

Die eigentliche Logik des Use Cases befindet sich in der Funktion orderIceCream(). Hier wird zunächst über die iceCreamContainerCharacteristic der Eisbehälter definiert, anschließend über iceCreamTypesCharacteristic die Eissorten und zu guter Letzt ein Listener für das read-Event an der iceCreamStatusCharacteristic registriert.

'use strict';
const noble = require('noble');
const IceCream = require('./lib/om/IceCream');
const Constants = require('./lib/constants/Constants');

let iceCreamContainerCharacteristic = null;
let iceCreamTypesCharacteristic = null;
let iceCreamStatusCharacteristic = null;

noble.on('stateChange', (state) => {
  if (state === 'poweredOn') {
    console.log('Starte Scanning...');
    noble.startScanning([Constants.ICE_CREAM_SERVICE_UUID], false);
  }
  else {
    noble.stopScanning();
  }
})

noble.on('discover', (peripheral) => {
  noble.stopScanning();
  console.log('Peripheral gefunden: ' + peripheral.localName);
  peripheral.connect((error) => {
    peripheral.discoverServices(
      [Constants.ICE_CREAM_SERVICE_UUID], 
      (error, services) => {
        services.forEach((service) => {
          console.log('Service gefunden:', service.uuid);
          service.discoverCharacteristics([], (error, characteristics) => {
            characteristics.forEach((characteristic) => {
              console.log('Charakteristic gefunden:', characteristic.uuid);
              if (Constants.ICE_CREAM_CONTAINER_CHARACTERISTIC_UUID == 
                  characteristic.uuid) {
                iceCreamContainerCharacteristic = characteristic;
              }
              else if (Constants.ICE_CREAM_TYPES_CHARACTERISTIC_UUID == 
                       characteristic.uuid) {
                iceCreamTypesCharacteristic = characteristic;
              }
              else if (Constants.ICE_CREAM_STATUS_CHARACTERISTIC_UUID == 
                       characteristic.uuid) {
                iceCreamStatusCharacteristic = characteristic;
              }
            });
            if (
              iceCreamContainerCharacteristic &&
              iceCreamTypesCharacteristic &&
              iceCreamStatusCharacteristic
            ) {
              orderIceCream();
            }
            else {
              console.error('Nicht alle Charakteristiken gefunden.');
            }
        });
      });
    });
  });
})

function orderIceCream() {
  let container = new Buffer(1);
  container.writeUInt8(IceCream.container.CONE, 0);
  iceCreamContainerCharacteristic.write(container, false, (error) => {
    if (!error)
    {
      let types = new Buffer(2);
      types.writeUInt16BE(
        IceCream.TYPES.CHOCOLATE |
        IceCream.TYPES.VANILLA |
        0
      );
      iceCreamTypesCharacteristic.write(types, false, (error) => {
      if (!error)
      {
        iceCreamStatusCharacteristic.on('read', (data, isNotification) => {
          console.log('Das Eis ist fertig!');
        });
        iceCreamStatusCharacteristic.subscribe((error) => {
         console.error(error);
        }
        else {
          console.error('Fehler bei Auswahl der Eissorten');
        }
      });
    }
    else {
      console.error('Fehler bei Auswahl des Behälters');
    }
  })
}

Das Programm, das den Code für das Peripheral enthält, kann nun über den Befehl sudo node peripheral.js gestartet werden. Das Programm, das den Code für das Central enthält, startet analog über den Befehl sudo node central-module.js. Die Ausgabe für das Peripheral lautet dann wie folgt:

$ Starte Advertising...
$ Bereite Eis vor.

Auf Seiten des Centrals wird folgende Ausgabe erzeugt:

$ Starte Scanning...
$ Peripheral gefunden: raspberrypi
$ Service gefunden: 13333333333333333333333333333337
$ Charakteristic gefunden: 13333333333333333333333333330001
$ Charakteristic gefunden: 13333333333333333333333333330002
$ Charakteristic gefunden: 13333333333333333333333333330003
$ Das Eis ist fertig!
$ Ergebnis:  Fertig

Fazit

Bluetooth Low Energy ist eine Funktechnik, über die Geräte relativ stromsparend miteinander kommunizieren können. Nach einer kurzen Einführung in die Begrifflichkeiten rund um BLE haben Sie in diesem Tutorial die beiden Node.js-Module bleno und noble kennengelernt: Ersteres dient dazu, Peripheral-Devices zu implementieren, Letzteres dazu, Central-Devices zu implementieren. Raspberry Pis eignen sich besonders für den Einsatz dieser beiden Module, weil zum einen Node.js ohne Probleme auf dem Minirechner installiert werden kann und zum anderen BLE seit Version 3 bereits mit an Bord ist.

Weitere Anwendungsbeispiele, in denen JavaScript zum Einsatz kommt, finden Sie in meinem aktuellen Buch „JavaScript – Das umfassende Handbuch“ [1].

Geschrieben von
Philip Ackermann
Philip Ackermann
Philip Ackermann arbeitet als Softwareentwickler beim Fraunhofer Institut für Angewandte Informationstechnik FIT, ist Autor dreier Bücher sowie zahlreicher Artikel über Java und JavaScript.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.