Einführung in die Bluetooth-Technologie – Teil 1

Bluetooth-Technologie: Auf blauen Pfaden

Bluetooth-Technologie: Auf blauen Pfaden

Einführung in die Bluetooth-Technologie – Teil 1

Bluetooth-Technologie: Auf blauen Pfaden


Die Entwicklung von Bluetooth ist ein guter Indikator für die Alterung der lesenden Wetware. Der Autor dieser Zeilen kann sich noch gut daran erinnern, als er einst bei seinem damals schweineteuren Palm Tungsten|T erstmals das Bluetooth-Radio aktivierte. Traurigerweise fand er damals in seinem gesamten Umkreis keine Gegenstelle vor. Eine Wegbegleiterin kaufte dann für ihren Palm m515 eine Bluetooth-Karte, um .pcr-Dateien auszutauschen. Dass all der Affentanz über die Infrarotschnittstelle und/oder den SD-Slot genauso bequem von der Stange gegangen wäre, wollen wir an dieser Stelle verschweigen.

In den gut zwanzig Jahren seit dem Erscheinen von Tungsten|T und Co. hat sich die Bluetooth-Technologie radikal weiterentwickelt, und trotz der für kleine Hardwarehersteller lästigen Lizenzbedingungen auch Marktdurchdringung erreicht. In den folgenden Heften wollen wir uns Bluetooth näher ansehen – und zwar nach Maßgabe der Möglichkeiten sowohl unter Android als auch unter anderen Java-Plattformen.

Machete im Bluetooth-Wald

In der Anfangszeit realisierte Bluetooth (denken Sie an Palm m515 und Tungsten|T) eine Punkt-zu-Punkt-Verbindung, wo an jeder Gegenstelle nur ein Gerät zu finden war. Diese Technologie wird von der Bluetooth Special Interest Group (SIG) auch heute noch unterstützt: Man spricht in diesem Zusammenhang vom Namen Bluetooth BR/EDR (Basic Rate/Enhanced Data Rate).

Hintergedanke dieses Standards war der Ersatz von Kabeln – ein Kabel hat normalerweise nur zwei Gegenstellen. Bluetooth LE (LE: Low Energy) wurde einst als niedrige Energieverbrauchsvariante von Bluetooth konzipiert, die sich für Embedded-Systeme eignen sollte.

Im Rahmen von Spezifikation und Standardisierung beseitigte die Bluetooth SIG nicht nur einige Probleme der Basisversion, sondern führte auch neue Betriebsmodi ein. Neben dem nach wie vor möglichen Point-to-Point-Betrieb erlaubt Bluetooth LE sowohl Broadcast- als auch Mesh-Szenarien. Im Fall eines Broadcast-Szenarios sendet ein meist als Beacon bezeichnetes Gerät „Statusmeldungen“ in den Äther, die dann von beliebig vielen Gegenstellen entgegengenommen werden können. Im Mesh-Betrieb „vernetzen“ sich die einzelnen Geräte dynamisch, was unter anderem für höhere Reichweiten des resultierenden Netzes sorgt.

Gemein ist all diesen Systemen vor allem das zugrunde liegende Radio-System. Bluetooth funkt im 2,4 GHz-Band und nutzt eine als Frequency Hopping bezeichnete und in Abbildung 1 illustrierte Technologie. Dahinter steht der Gedanke, dass Störungen im geteilten Übertragungsband dadurch überwunden werden, dass die nächste Übertragung in einem anderen Bandteil stattfindet.

hanna_bluetooth_1.tif_fmt1.jpgAbb. 1: Frequency Hopping erhöht die Störfestigkeit der Verbindungen (Bildquelle: [1])

Gewöhnliches Bluetooth, zur ersten

Ganz analog zur historischen Entwicklung des Bluetooth-Standards wollen wir auch in diesem Tutorial mit einem gewöhnlichen, auf Bluetooth EDR basierenden Programm beginnen. Der eigentliche Bluetooth-Stack ist dabei – ganz analog zum OSI-Modell – in einer Schichtenstruktur aufgebaut.

LMP und L2CAP kümmern sich dabei um die „logische“ Aufrechterhaltung des Datenverkehrs – also darum, dass Pakete von A nach B wandern. Sofern Sie nicht einen Bluetooth-Stack von Hand realisieren (eine Sisyphus-Aufgabe), spielen sie in der täglichen Arbeit allerdings keine große Rolle. Wichtiger bei der Arbeit mit Bluetooth BER/EDR ist das Service Discovery Protocol (SDP).

Die erste Generation der Bluetooth-Spezifikation war (siehe oben) vor allem als Ersatz für verkabelte Verbindungen vorgesehen und war eine Affäre für finanzstarke Unternehmen. Daraus folgte, dass die Bluetooth SIG die vom Bluetooth-Standard zu erfüllenden Anforderungen im Allgemeinen gut zentralisiert verwalten konnte – die verschiedenen Kommunikationsarten bzw. an Bluetooth gestellten Anforderungen wurden deshalb in Form von Profilen zusammengefasst.

Die im Allgemeinen aufnahmefreundliche Wikipedia listet unter dem URL [2] die in Abbildung 2 gezeigten insgesamt 37 bekannten Profile – einige davon sind mittlerweile übrigens deprecated.

hanna_bluetooth_2.tif_fmt1.jpgAbb 2: Die Profilliste von Bluetooth EDR

In der Praxis spielte die Mehrzahl dieser Protokolle nur eine untergeordnete Rolle, die meisten Unternehmen griffen auf das Serial-Port-Profil zurück, das (analog zu RS232) das Austauschen von mehr oder weniger beliebigen Payloads erlaubte. Das Obex-Protokoll war in der Praxis ebenfalls nicht unwichtig, ermöglichte es doch das Übertragen diverser kleiner Dateien wie die in der Einleitung genannten .pcr-Dateien. Manchmal kam auch das FTP-Protokoll zum Einsatz, das Bluetooth-Geräten das Exponieren von an FTP-Servern erinnernden Speichern erlaubte. Analog zu dem in der Frühphase manchmal verwendeten LAN-Protokoll (der Belkin Bluetooth Access Point ist unvergessen) galt allerdings auch hier, dass die vergleichsweise geringe Datenrate diese Protokolle in der praktischen Nutzung bald durch WLAN und Co. ersetzte. Bluetooth LE umging die Probleme mit den rigide definierten Profilen übrigens durch Einführung eines KV-Speichers – ein Thema, dem wir uns allerdings erst in einem der nächsten Artikel zuwenden wollen.

Auf der Schulter des Riesen

Komplexe Systeme lassen sich oft am leichtesten durch Betrachtung ihrer Bausteine verstehen. Google (das Unternehmen ist logischerweise Mitglied der Bluetooth SIG) bietet unter [3] ein schlüsselfertiges Chat-Sample an, das die Chatkommunikation zwischen zwei per Bluetooth verbundenen Geräten realisiert. Interessanterweise bietet Google dieses Beispiel in der in Android Studio integrierten Beispielsammlung nicht mehr an – offensichtlich sieht man im Hause Big G Bluetooth LE als wichtigeren Kandidaten.

Das Nichterscheinen des grünen Codeknopfs im Browser-GUI von GitHub informiert uns darüber, dass dieses Beispiel auch nicht direkt herunterladbar ist. Öffnen Sie stattdessen [4], um das Archiv herunterzuladen – extrahieren Sie die komplette Connectivity-Beispielsammlung (etwa 5 MB) danach in einen bequem zugänglichen Ordner im Dateisystem und importieren Sie das Chatbeispiel wie jedes andere Projekt in Android Studio.

Von den Systemanforderungen her ist das Beispiel durchaus fußgeherisch – der Autor wird in den folgenden Schritten eine Canary-Version von Android Studio verwenden, ältere Versionen der Android-Studio-IDE sollten allerdings ebenso problemlos funktionieren.

Die erste interessante Eigenschaft des Projektskeletts findet sich in der Manifestdatei, wo Google zwei Permissions deklariert:

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />

Im Interesse feingranularer Berechtigungsverwaltung spezifiziert Android ein gutes halbes Dutzend Permissions, die Google unter [5] in Detail auflistet. Die grundlegende Permission Bluetooth erlaubt dabei den Verbindungsaufbau zu Geräten, die den als Pairing bezeichneten Vertrauensbeziehungsaufbau durchlaufen haben. Durch Bluetooth_Admin darf die Applikation ebendiesen Prozess anstoßen, der eine permanente Verbindung zwischen zwei Bluetooth-Endstellen herstellt.

Beachten Sie, dass Android 12 im Bereich der Bluetooth Permissions massive Änderungen durchführt. Diese betreffen unsere Beispielapplikation nicht, weil ihre Target-SDK-Version 28 lautet. Google bietet unter [6] allerdings umfangreiche weitere Informationen zum Thema an.

Erste Versuche mit der Applikation

Nach der Klärung der sicherheitstechnischen Aspekte bietet es sich an, die Applikation im ersten Schritt auf ein Gerät zu deployen. Das Anklicken des oben rechts eingeblendeten Bluetooth-Dialogs führt dann zum Aufscheinen des in Abbildung 3 gezeigten Fensters.

hanna_bluetooth_3.tif_fmt1.jpgAbb. 3: Chat Sample listet alle dem Programm bekannten Gegenstellen auf

Da der Autor dieser Zeilen das Programm auf seinem (gut benutzten) BlackBerry PRIV zur Ausführung bringt, sehen wir ein gutes halbes Dutzend schon gepaarte Geräte. Das Anklicken von Scan führt dann zu einem neuen Scanlauf, der immer etwa 15 Sekunden dauert und noch nicht gepaarte Geräte, die als „sichtbar“ gekennzeichnet sind, erkennt.

Die hier teilweise zensiert abgedruckten Strings erinnern dabei an MACs: Jeder Bluetooth-Chip bekommt im Allgemeinen eine weltweit einzigartige ID eingeschrieben, die den jeweiligen Chip identifiziert. Die im Rahmen der Impfkampagne lancierten Meldungen über „MACs von geimpften Personen“ lassen sich übrigens anhand der Webseite [7] „resolvieren“ – haben Sie einen MAC, so lässt sich einiges über das zugrunde liegende Bluetooth-Gerät herausfinden.

Beachten Sie in diesem Zusammenhang außerdem, dass MACs auf Hardwareebene leben, Pairing-Beziehungen aber eine reine Softwaregeschichte sind. Haben Sie beispielsweise eine Dual Boot-Workstation mit einem Bluetooth-Modul, kann dies zu Kollisionen führen, wenn Sie einen Partitionswechsel durchführen.

Wenn Sie unser Programmbeispiel (dies ist immer empfehlenswert) mit ausgeschaltetem Bluetooth-Transmitter starten, sehen Sie im ersten Schritt eine Message, die Sie zur Einschaltung des Bluetooth-Zentimeters auffordert. Zum Verständnis dessen wollen wir uns zuerst den Programmstart ansehen. Google setzt in ChatSample übrigens konsequent auf Fragmente.

War zu Zeiten von Palm OS und Co. der direkte Zugriff auf die Energieversorgungseinstellungen des Bluetooth-Transmitters gang und gäbe, so muss der Android-Entwickler einen weniger direkten Weg gehen. Systemglobale Einstellungen wie die Anpassung bzw. das Einschalten des Bluetooth-Transmitters darf nur das Betriebssystem erledigen, das zu dieser Handlung durch Abschicken eines Intents aufgefordert werden möchte. Der dazu notwendige Code sieht aus wie in Listing 1 dargestellt.

Listing 1

public class BluetoothChatFragment extends Fragment {
  private static final String TAG = "BluetoothChatFragment";
  @Override
  public void onStart() {
    . . .
    if (!mBluetoothAdapter.isEnabled()) {
      Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
      startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    }
    . . . 
  }

Die Klasse BluetoothAdapter dient dabei als Schnittstelle zwischen Ihrer Programmlogik und den verschiedenen Funktionen des Bluetooth-Stacks. Besonders wichtig ist die im Rahmen von onCreate erfolgende Bevölkerung durch Aufruf der Methode BluetoothAdapter.getDefaultAdapter() (Listing 2).

Listing 2

@Override
public void onCreate(Bundle savedInstanceState) {
  mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  if (mBluetoothAdapter == null && activity != null) {
    Toast.makeText(activity, "Bluetooth is not available", Toast.LENGTH_LONG).show();
  }
}

Obwohl nach Erfahrung des Autors keine Android-Geräte mit mehr als einem Bluetooth-Transmitter im Markt verfügbar sind, wählt Google (übrigens analog zu Microsofts Vorgehensweise im Kinect-API) den Vollausbau, der in der Theorie auch Geräte mit mehreren Adaptern erlaubt. Der Aufruf von BluetoothAdapter.getDefaultAdapter() liefert jedenfalls eine BluetoothAdapter-Instanz zurück, die mit dem Standardtransmitter der zugrunde liegenden Hardware verbunden ist.

Obwohl der Gutteil der im Markt verfügbaren Android-Hardware heute einen Bluetooth-Transmitter mitbringt, ist es trotzdem empfehlenswert, den zurückgegebenen Wert auf null zu prüfen und eventuelle Sondergeräte schon an dieser Stelle abzuweisen.

Angemerkt sei in diesem Zusammenhang, dass der Play Store auf Wunsch auch zur automatischen Abweisung inkompatibler Hardware animiert werden darf. Erlaubt ist dabei die genaue Selektierung von Hardware, die entweder Bluetooth oder Bluetooth LE unterstützen muss:

<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

Nach diesem kleinen Exkurs können wir wieder zur eigentlichen Scanning-Logik zurückkehren, die in der Datei DeviceListActivity.java unterkommt. Für die Kommunikation zwischen Entwicklerapplikation und vom Bluetooth-Stack generierten Informationen setzt Google dabei konsequent auf die Technik des BroadcastReceiver. Im Beispiel in Listing 3 hört der betreffende Kandidat auf den Namen mReceiver und ist am Ende der Datei als Inline-Variable angelegt.

Listing 3

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (BluetoothDevice.ACTION_FOUND.equals(action)) {

Wann immer der Bluetooth-Stack in der Umgebung ein sichtbares Gerät findet, sendet er einen Intent vom Typ BluetoothDevice.ACTION_FOUND. In unserem vorliegenden Beispiel beschaffen wir im ersten Schritt ein BluetoothDevice-Objekt, das uns weitere Informationen über das Zielgerät anliefert. Durch Aufrufen der Methode getBondState überprüfen wir dann, ob das Gerät schon eine Pairing-Beziehung zum vorliegenden Telefon unterhält. Ist das der Fall, müssen wir es hier nicht mehr aufnehmen:

BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null && device.getBondState() != BluetoothDevice.BOND_BONDED) {
  mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}

Wichtig ist in diesem Zusammenhang außerdem noch das Ereignis BluetoothAdapter.ACTION_DISCOVERY_FINISHED, mit dem der Bluetooth-Stack unsere Applikation über den erfolgreichen Abschluss des kompletten Suchlaufs informiert (Listing 4).

Listing 4

    } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
      setProgressBarIndeterminateVisibility(false);
      setTitle(R.string.select_device);
      if (mNewDevicesArrayAdapter.getCount() == 0) {
        String noDevices = getResources().getText(R.string.none_found).toString();
        mNewDevicesArrayAdapter.add(noDevices);
      }
    }
  }
};

Das eigentliche Anstoßen des Scanprozesses erfolgt dann über die von weiter oben bekannte BluetoothAdapter-Klasse, die über die Methode startDiscovery das direkte Aktivieren erlaubt (Listing 5).

Listing 5

private void doDiscovery() {
  . . .
  if (mBtAdapter.isDiscovering()) {
    mBtAdapter.cancelDiscovery();
  }
  mBtAdapter.startDiscovery();
}

Damit können wir uns der Initialisierung des für den Suchprozess zuständigen Benutzerinterface zuwenden. Neben diversen GUI-Konfigurationshandlungen, die uns an dieser Stelle nicht weiter interessieren, finden wir hier auch die Erzeugung von zwei Intentfiltern. Diese ermöglichen Google bzw. der Applikation das Verbinden des als „innere Variable“ angelegten Broadcast Receivers mit den diversen vom Bluetooth-Stack emittierten Informationen (Listing 6).

Listing 6

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  . . .
  IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
  this.registerReceiver(mReceiver, filter);
  filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
  this.registerReceiver(mReceiver, filter);

Nächste Amtshandlung der vorliegenden Routine ist abermals das Aufrufen von getDefaultAdapter, um die lokal vorliegende BluetoothAdapter-Membervariable zu bevölkern. Mehrfache Aufrufe von getDefaultAdapter führen in 99,9 % der Fälle dazu, dass auf dasselbe Endgerät verweisende BluetoothAdapter-Instanzen entstehen. Es ist also nicht notwendig, die Instanzen auf komplizierten bzw. verschlungenen Wegen zwischen den verschiedenen Teilen Ihrer Android-Applikation hin- und herzuschieben:

mBtAdapter = BluetoothAdapter.getDefaultAdapter();

Interessant ist im nächsten Schritt die Bevölkerung der in Abbildung 3 gezeigten oberen Liste. Sie nimmt nur die bereits gepaarten Geräte auf – Informationen, die der Bluetooth-Stack ohne Interaktion mit dem Radio bereitstellen kann. Dies erfolgt durch den in Listing 7 gezeigten Code, der über den von mBtAdapter.getBondedDevices() zurückgelieferten Speicher iteriert.

Listing 7

  Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
  if (pairedDevices.size() > 0) {
    findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
    for (BluetoothDevice device : pairedDevices) {
      pairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
  } else {
    String noDevices = getResources().getText(R.string.none_paired).toString();
    pairedDevicesArrayAdapter.add(noDevices);
  }
}

Betrachtung der Kommunikation, zur ersten

An dieser Stelle können wir in die Datei BluetoothChatFragment.java wechseln, die für die eigentliche Kommunikationsinfrastruktur, das Einsammeln der zu sendenden Daten und das Anzeigen von eingehenden Chatnachrichten verantwortlich ist. Die Klasse BluetoothChatFragment enthält dabei einige Dutzend Member-Variablen – wirklich relevant sind für uns die beiden folgenden:

public class BluetoothChatFragment extends Fragment {
  private BluetoothAdapter mBluetoothAdapter = null;
  private BluetoothChatService mChatService = null;

Neben der mittlerweile hinreichend bekannten Geräteinstanz BluetoothAdapter finden wir hier auch eine Instanz der Klasse BluetoothChatService. Google realisiert das Chatbeispiel unter konsequenter Nutzung der Service-Architektur, die Klasse BluetoothChatService ist ein System-Service, der die eigentliche Kommunikation und Datenübertragung enkapsuliert. Interessant ist an der vorliegenden Datei die Methode ensureDiscoverable. Sie hat die Aufgabe, den Bluetooth-Transmitter des Telefons sichtbar zu machen (Listing 8).

Listing 8

private void ensureDiscoverable() {
  if (mBluetoothAdapter.getScanMode() !=
      BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
    Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
    startActivity(discoverableIntent);
  }
}

Analog zur weiter oben besprochenen Einschaltung des Transmitters erfolgt auch diese Handlung durch das Abfeuern eines Intents, der danach vom Betriebssystem und dem in ihm enthaltenen GUI-Stack verarbeitet wird. Interessant ist in diesem Zusammenhang der Aufruf von putExtra, die die Flag BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION und den Wert 300 entgegennimmt.

Discoverability von Bluetooth-Endgeräten ist ein aus Stromverbrauchssicht durchaus teurer Prozess, der sich insbesondere auf die Stand-by-Laufzeit von Telefon und/oder Tablet auswirken kann. Das Freischalten der Sichtbarkeit von Bluetooth erfolgt in Android deshalb im Allgemeinen mit einem Ablaufdatum, nach dem das Gerät wieder in den normalen „Ansprechbar“-Modus zurückfällt. Die Erfahrung mit älteren Betriebssystemen lehrt nämlich, dass insbesondere technisch herausgeforderte Benutzer nur allzu häufig auf das Ausschalten dieser für sie im Allgemeinen unnützen oder sogar aus sicherheitstechnischer Sicht kritischen Funktion vergessen.

Beachten Sie in diesem Zusammenhang, dass die Sichtbarkeit in vielen Fällen keine Vorteile bringt. Ein bereits gepaartes Bluetooth-Endgerät kann sich nämlich auch dann verbinden, wenn der Transmitter Zielsystem nur eingeschaltet, nicht aber discoverable ist.

Der letzte für die Kommunikationsinfrastruktur wichtige Programmteil ist dann der folgende Handler, der sich um die Verarbeitung der vom Service eingehenden Nachrichten kümmert (Listing 9).

Listing 9

private final Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    FragmentActivity activity = getActivity();
    switch (msg.what) {
      case Constants.MESSAGE_STATE_CHANGE:
        switch (msg.arg1) {

Im Prinzip handelt es sich dabei um gewöhnlichen GUI-Code. Im Fall der Konstante Constants.MESSAGE_STATE_CHANGE meldet der Service dem Benutzerinterface beispielsweise eine Änderung des Programmzustands, deren Auswertung nach dem Schema in Listing 10 erfolgt.

Listing 10

case Constants.MESSAGE_WRITE:
  byte[] writeBuf = (byte[]) msg.obj;
  String writeMessage = new String(writeBuf);
  mConversationArrayAdapter.add("Me:  " + writeMessage);
  break;
case Constants.MESSAGE_READ:
  byte[] readBuf = (byte[]) msg.obj;
  String readMessage = new String(readBuf, 0, msg.arg1);
  mConversationArrayAdapter.add(mConnectedDeviceName + ":  " + readMessage);
  break;

Wichtig ist, dass die Übertragung von Daten über die Bluetooth-Schnittstelle ausschließlich auf Basis von Byteströmen erfolgt – Encodierung und Co. ist hier ob der Nutzung von byte[] die alleinige Verantwortung Ihrer Applikation.

Fazit und Ausblick

Nach diesem ersten Artikel haben wir eine grundlegende Working Proficiency im Bereich Bluetooth erreicht – wir verstehen nun, was die verschiedenen Arten von Bluetooth sind und wie man unter Android eine Geräte-Discovery auslöst.

Im nächsten Artikel dieser Serie werden wir unsere Experimente mit Bluetooth insofern vertiefen, als wir uns nun dem eigentlichen Datenaustausch zuwenden – und zwar sowohl unter Android als auch mit anderen Zielsystemen. Bleiben Sie also bei uns, denn die Arbeit mit Bluetooth-Funk bleibt spannend.

hanna_tam_sw.tif_fmt1.jpgTam Hanna befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenewsdienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.

Tam Hanna

Tam Hanna befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenewsdienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.