1 Seminar zum Programmierprojekt SS 2008 Uni Tübingen, Arbeitsbereich Technische Informatik Ausgabe: 20. Mai 2008 Anleitung C5 Einführung in die BT-Server-Programmierung mit JavaSE Allgemeines Mit Aufgabe 5 beginnen Sie die Implementierungsphase des Praktikums. Auf den Praktikumsrechnern 2 (3, 4) ist Java2SE und Eclipse installiert. Sie müssen für diese Aufgabe den Bluetooth-Dongle in einen USB-Port einstecken. Sie lernen in Aufgabe 5 die Grundstruktur eines BT-Servers kennen. JAVA Bluetooth-Programmierung im HOST In dieser Programmieraufgabe sollen die Grundlagen der Bluetooth-Programmierung erlernt werden. Als Ziel für den gesamten Aufgabenblock C5 bis C7 sei die Programmierung eines Servers im Host gesetzt, der selbst drei Dienste anbietet, nämlich den Dienst "TelefonDienst", der auf Anfrage eine Telefonnummer (z.B. des Sekretariats der TI), den "Gebäudeplan-Dienst", der einen Gebäudeplan an das anfragende Gerät schickt und den „Quiz-Dienst“: ein kleines Frage- und Antwort-Spiel. Zusätzlich soll ein Dienst "Send Services" als Antwort auf eine Dienstanfrage eines entfernten Gerätes die im Host registrierten Dienste an das anfragende Gerät zurückgeben. In dieser Aufgabe wird zunächst das Serverframework programmiert, das die Dienste im Host registriert, eine „Device-Discovery“, also Suche nach entfernten Geräten durchführt und eine Dienstanfrage beantwortet. Gefundene BT-Geräte sollen im Serverprogramm in einer Liste ausgegeben werden. Das Handling der Clientverbindungen sei Thema der nächsten Aufgaben (C6 und C7). Für die Programmierung soll die Java BT Implementierung von Benhui Bluecove (für Windows) sowie das Java J2SE Development Kit und die J2SE Runtime Environment 5.0 Update 10 oder höher verwendet werden. Als Entwicklungsumgebung bietet sich eclipse an. Hier muss ein neues Java-Projekt angelegt werden. Es muss die Benhui Bluecove Bibliothek ins Projekt eingebunden werden (über Rechte Maustaste auf Projekt -> BuildPath -> Configure Build Path -> Add External Jars). Das Gegenstück zu dem hier zu entwickelnden Hostprogramm ist das Clientprogramm der Gruppe B. Sie müssen sich mit der Gruppe B absprechen, wenn Sie ihr Programm testen. Grundstruktur eines Servers Ein BT-Server legt für jedes Gerät, mit dem er eine Verbindung eingeht, einen Thread an. Die Programmierschritte im Server sind: • Initialisierung des BT-Stacks • Antworte auf eine Dienste-Anfrage • Warte auf Client-Verbindungsanfragen und aktzeptiere diese und bearbeite ClientAnforderungen 2 • Schließe den Dienst ab Es existieren einige Tutorials von SUN im Netz, die recht gut sind. Im Tutorial 1 sind diese Programmierschritte beschrieben, Tutorial 2 geht mehr auf die Programmierung im MT ein. Zu finden sind die Tutorials unter: • Tutorial 1 (http://developers.sun.com/techtopics/mobility/apis/articles/bluetoothcore/) • Tutorial 2 (http://developers.sun.com/techtopics/mobility/midp/articles/bluetooth2/) Außerdem zu empfehlen ist die Homepage von Benhui, auf der Beispielprogramme und viele nützliche Informationen sowie ein Diskussionsforum zu finden sind. Initialisierung des Servers Um einen Dienst im Server zu registrieren, ist zunächst die Initialisierung des lokalen Bluetooth-Stacks und des lokalen BT-Gerätes nötig, d.h. der angeschlossene BT-Dongle muss über die JavaAPI initialisiert werden. Ein Aufruf von LocalDevice localDevice = getLocalDevice(); aus dem javax.bluetooth.*-Paket erledigt dies. Zusätzlich muss ein StreamConnectionNotifier-Objekt angelegt werden, welche ähnlich wie ein Server-Socket eingehende Verbindung akzeptiert. Die Initialisierung des StreamConnectionNotifiers geschieht über folgenden Aufruf: StreamConnectionNotifier serverconn = (StreamConnectionNotifier) Connector.open(url); Die Connector-Klasse aus javax.microedition.io.Connector öffnet eine Verbindung und gibt ein Connection- Objekt zurück, welches in die entsprechende Verbindungsart gecastet werden kann (hier also StreamConnectionNotifier). Der url-Parameterstring muss dem URLFormat aus RFC 2396 entsprechen. Er hat die allgemeine Form wie folgt: {scheme}:[{target}][{params}] • {Scheme}: Name des Protkolls, z.B. btspp oder http. (Bei uns: btspp) • {target}: Eine Netzwerkadresse, für den lokalen Host "localhost:1", für entfernte Geräte eine BT-Adresse • [{params}]: Weitere Parameter der Form ";x=Y", z.B. ";app-name=HostNode" Hinter der {target}-Netzwerkadresse wird die PSM (Protocol Service Muliplexer, wichtigster Parameter der L2CAP-Verbindung, siehe [1]) angegeben, um einen speziellen Dienst im Server anzusprechen. PSM 1 (genauer 0x0001) entspricht z.B. dem ServiceDiscoveryProtocol, das wir allerdings nicht verwenden. Auf diese Weise kann später mittels der UUIDs für die Serverdienste ein Dienst gezielt ausgewählt werden (eine PSM entspricht letztlich einer UUID, da die 16- bzw. 32-Bit-PSM intern in eine 128-Bit UUID umgewandelt wird (siehe BluetoothAPI:UUID für eine Beschreibung der UUID-Klasse). Obwohl wir das Service Discovery Protokoll (SDP) nicht verwenden, hier noch einige Bemerkungen dazu: Die UUIDs für die Serverdienste müssen angelegt und im ServiceRecord des Hosts abgelegt werden. Damit sind die Services im ServiceRecord der localDevice regist- 3 riert und können bei einer ServiceDiscovery durch ein entferntes Gerät gefunden werden. Eine Beschreibung des SDP findet sich in der Bluetooth Core Specification v2.0. Akzeptieren einer Client Verbindung In dieser Aufgabe geht es darum, eingehende Clientverbindungen zu verarbeiten. Die Dienstauswahl geschieht hier vereinfacht über den Austausch von Strings, die im Host geparst werden. Das Mobiltelefon (MT) schickt zuerst einen String, der in der Spezifikation definiert ist, an den Host. Im Host soll dies erkannt werden und die registrierten Dienste ebenfalls als String zurückgeschickt werden (siehe Spezifikation). Dieser String kann vom MT wiederum erkannt werden, welches dann die einzelnen Dienste zur Auswahl freigibt. Das Handling der angebotenen Dienste wird in den folgenden Aufgaben durchgeführt. Hier soll zunächst nur die Dienst-Antwort auf die Dienstanfrage des Client implementiert werden. Akzeptieren einer Clientverbindung wird durch StreamConnection clientconn = serverconn.acceptAndOpen(); ausgeführt. Dadurch wird ein StreamConnection Objekt erstellt, über das verschiedene Schreib- und Leseoperationen durchgeführt werden können Dienstanfrage: „Service Discovery“ Für die Service Discovery sendet in diesem Projekt das Mobiltelefon einen String (über das „Serial Port Profile“ SPP) " <SendServices>" an den Server. Dieser parst den String und schickt seinerseits einen String mit der Liste der angebotenen Dienste (also "Telefon-Service, Gebäudeplan-Service, Quiz-Service-String") zurück. Falsche (unverständliche) Anfragen vom Handy sollten mit einer Fehlermeldung beantwortet werden. Benutzen Sie für Input und Output über Bluetooth die Funktionen writeUTF(String s) und readUTF(). Sie müssen zunächst ein DataInputStream Objekt für die eingehende Anfrage erzeugen: DataInputStream in = clientconn.openDataInputStream();, bevor sie mit String s = in.readUTF(); den eingehenden String lesen können. Analog läuft das Schreiben auf den DataOutputStream ab: DataOutputStream out = clientconn.openDataOutputStream(); out.writeUTF("ihreantwort"); Da es sich bei der geöffneten Verbindung um eine Verbindung mit Puffer handelt, müssen Sie diesen leeren und somit die Nachricht senden: outStream.flush(); Schließen Sie danach den OutputSream, nicht jedoch ihre Stream Connection: outStream.close(); 4 Editieren, Kompilieren und Ausführen Ihres Programms Für das Editieren Ihres Programms wird eclipse empfohlen. Für das Kompilieren gibt es zwei Möglichkeiten: • Sie können ebenfalls auf eclipse mit dem Java2SE-plugin kompilieren. Dafür müssen Sie folgende Bibliothek einbinden: BlueCoveJSR82-Patched-By-Benhui-net.jar Den Link zu dieser Bibliothek finden Sie oben. • Die andere Möglichkeit ist das Kompilieren in der DOS-Eingabeaufforderung Ihres Windows-Rechners. Sie können dafür ein kleines BAT-Programm anlegen wie folgt: javac -d bin -Xlint:unchecked -cp BlueCoveJSR82-Patched-By-Benhui-net.jar src\*.java • Gehen Sie in der Eingabeaufforderung in ihr Entwicklungsverzeichnis und geben führen Sie die obige BAT-Datei aus. • Für das Ausführen Ihres Programms wir ebenfalls eine kleine BAT-Datei empfohlen, wie folgt: javac -cp BlueCoveJSR82-Patched-By-Benhui-net.jar;bin StartC8 Der folgende Coderahmen für den Server hilft Ihnen ein wenig beim Einstieg in die Codierung. ---------------------------------------------------------------------------------------------------------------import java.io.FileWriter; import java.io.IOException; import java.util.Enumeration; import java.util.Vector; import javax.bluetooth.*; import javax.microedition.io.*; /** * Praktikum Eingebettete Systeme WS 07/08 * Aufgabe C5 * Datum: … * @author: Gruppe C: ...Namen * @version: … * * Service discovery is done via SPP * The Services found are displayed to StdOut * Server waits for Client connections * * Handles the client connections: * parsing the services: * 1)Send Services * 2)Map Service * 3)Phone Service */ /* Starte den Server */ public class StartC5 { public static void main(String[] args) { C5Server server = new C5Server(); 5 server.start(); } } public class C5Server extends Thread { private StreamConnectionNotifier serverconn; // the server side connection object private LocalDevice localDevice; // Das Lokale Gerät (Ihr PC mit BT-Dongle) private FileWriter logfile; private static Vector<RemoteDevice> devices = new Vector<RemoteDevice>(); private static Vector<DeviceClass> deviceClasses = new Vector<DeviceClass>(); public C5Server() { // Constructor Funktion. Eventuell nicht nötig } public synchronized void run() { try { // init the stack, get the local device // ....... fügen Sie hier Ihren Init-Code ein ........ // Machen Sie Ihr eigenes Gerät allgemein sichtbar: // ....... fügen Sie hier Ihren set discoverable -Code ein…… // mit GIAC(General/Unlimited Inquiry Access Code)-Parameter // siehe Tutorial 1 // Empfehlung: legen Sie ein log an: // z.B. log("localDevice set discoverable GIAC",0); //Do the device discovery: eventuell in einer eigenen Anwendung! //....... fügen Sie hier Ihren device Discovery-Code ein .... // Eventuell ist es günstig, eine eigene // "Discovery Agent (da)" Klasse zu schreiben // diese zu instanziieren und zu starten ...run() // Starte die Rundum-Abfrage: z.B.: // da.startInquiry(......GIAC, new Listener()); // Implementierung eines "Discovery Listeners" Interface, der Device // Discovery durchführt // ....... fügen Sie hier Ihren Device-Listener-Code ein .......... // z.B.: public class Listener implements DiscoveryListener // Legen Sie einen Device Vektor an und greifen Sie auf diesen über eine // Methode zu. // Fügen Sie als Elemente die Devices in einen Device-Vektor ein // Error handling, falls die Inquiry nicht klappt // // Eventuell müssen Sie hier einige Sekunden warten, bis die Device // Discovery abgeschlossen ist // etwa mit wait(20000); // Schließen Sie die Inquiry // loggen Sie die erfolgreiche D-Discovery! // und zeigen Sie die Geräte an. 6 // // Empfohlen: Eine ClientHandler-Klasse (runnable) anlegen, die die // Verbindung mir dem Client (den Clients) abarbeitet // ....... fügen Sie hier Ihren ClientHandler-Code ein .......... // Für jeden Client wird ein ClientHandler (sozusagen als Thread) // aufgerufen // ClientHandler ch = new // ClientHandler(serverconn.acceptAndOpen(), this, ...); // Nehemen Sie die Verbindungen von entfernten Geräten an (remote devi // ces) // RemoteDevice rdev = RemoteDevice.getRemoteDevice(clientconn); // clientconn.openDataInputStream(); // String s = in.readUTF(); // Wenn s = Diensteanfrage, // dann: this.wait(100); und antworte mit "Dienste-String" // antwort = "Dienste-String" // out.writeUTF(antwort); // out.flush(); // // Falls s = eine Dienstanforderung, dann führe den Dienst aus // } } } ---------------------------------------------------------------------------------------------------------------- Literatur [1] Sauter, Martin: Grundkurs Mobile Kommunikationssysteme, Kapitel 5.