天天看点

手机蓝牙编程简介

手机蓝牙编程简介

一、什么是蓝牙技术

蓝牙是一种低成本、短距离的无线通信技术。对于那些希望创建个人局域网(PANs )的人们来说,蓝牙技术已经越来越流行了。每个个人局域网都在独立设备的周围被动态地创建,并且为蜂窝 式电话和PDA 等设备提供了自动连接和即时共享数据的能力。为了在Java 平台上开发支持蓝牙技术的软件,JCP 定义了JSR82 标准--Java 蓝牙无线技术APIs(JABWT) 。

当蓝牙设备互相连接时,他们将组成一个微微网(piconet ), 即以一个主设备和最大7 个从设备的形式动态创建网络。蓝牙也支持piconet 网之间的连接:当一个piconet 中的 主设备成为另一个piconet 的从设备时,piconet 与piconet 间将形成桥接。

二、蓝牙协 议栈

蓝牙协议栈允许采用 多种方法,包括 RFCOMM 和 Object Exchange (OBEX ), 在设备之间发送和接收文件。如果想发送和接收流数据(而且想采用传统的串口应用程序,并给它加上蓝牙支持),那么 RFCOMM 更好。反过来,如果想发送对象数据以及关于负载的上下文和元数据,则 OBEX 最好。图 1 显示了协议栈的细节。

图1 蓝牙协议栈 ,如下 :

手机蓝牙编程简介
  • 栈的最底层是 HCI ,即主机控制器接口 (Host Controller Interface )。这一层顾名思义就是主机(计算机)和控制器(蓝牙设备)之间的接口。可以看到,其 他所有的层都要经过 HCI 。
  • HCI 上面的一层是L2CAP , 即逻辑链接控制器适配协议(Logical Link Controller Adaptation Protocol )。这一层充当其他所有层的数据多路复用器。
  • 接下来一层是 BNEP ,即蓝牙网络封装 协议(Bluetooth Network Encapsulation Protocol )。使用 BNEP , 可以在蓝牙上运行其他网络协议,例如 IP 、TCP 和 UDP 。
  • RFCOMM 称作虚拟串口协议(virtual serial port protocol ) ,因为它允许蓝牙设备模拟串口的功能。
  • OBEX 协议层是在RFCOMM 层 上面实现的,如果想把数据以对象(例如文件)的形式传输,那么OBEX 很有用。
  • SDP 是服务发现协议(Service Discovery Protocol )层,用于在远程蓝牙设备上寻找服务。
  • 最后两层是 AVCTP 和 AVDTP ,用于蓝牙上音频和视频的控制 和 发布 。AVCTP 和 AVDTP 是蓝牙协议中增加的相对较新的层;如果想控制媒体播放器的功能或者想以立体声播放音频流,则要使用它们。

三、蓝牙规范用例

初始化-- 所有具备蓝牙功能的应用程序必须先要初始化蓝牙 栈。

服 务器:建立一个服务,然后等待一个客户端来连接。

客 户端:搜索服务,然后尝试与服务器建立连接。

图2 一个蓝牙规范用例图,如下:

手机蓝牙编程简介

四、用例中参与活动的图表

图3 蓝牙应用程序活动图,如下:

手机蓝牙编程简介

五、JSR-82 API 简介

JSR-82 是用于蓝牙无线技术的官方Java API 。可使用这个API 创建可执行以下功能 的应用程序:

  • 判断和检测自己的蓝牙设备的属性
  • 发现设备通信范围内的蓝牙设备
  • 在远程蓝牙设备上搜索服务
  • 创建可以与远程蓝牙服务器通信的蓝牙客户机应用程序
  • 创建能够为蓝牙客户机的请求提供服务的蓝牙服务器应用程序

JSR-82 包含两个包,即javax.bluetooth 和javax.obex 。

图4 显示了在MIDlet 中一个典型蓝牙功能应用程序中的 一些元素,如下:

1 、本地设备类: 

手机蓝牙编程简介

图5 :LocalDevice 类

本地设备提供了方法来返回关于本地设备的信息,并且能够进入 Bluetooth manager :

    .getBluetoothAddress() 返回蓝牙设备地址。

    .getDeviceClass() 返回设备类。

    .getFriendlyName() 返回设备友好名称,蓝牙设备名通常是用户在蓝牙控制中心为其设置的我们将会在后面看到。

    .getRecord() 返回一个指定蓝牙连接的服务记录。

    .updateRecord() 方法用来为指定的ServiceRecord 更 新SDDB 服务记录。

    .getDiscoverable() 返回设备的可发现状态。

    .setDiscoverable() 设置设备的可发现状态。

    .getDiscoveryAgent() 返回一个参考给发现代理。

    .getProperty() 返回一个设备的蓝牙属性

通过 调用getProperty() 方法你可以得到的属性包括:

    .bluetooth.api.version ,蓝牙API 版本

    .bluetooth.sd.attr.retrievable.max ,一次性能够被获得的服务记录属性的最大值

    .bluetooth.connected.devices.max ,支持的连接设备的最大值

    .bluetooth.sd.trans.max ,同时发生的服务发现处理的最大值

    .bluetooth.l2cap.receiveMTU.max ,L2CAP 最 大发射单元

你可以在Javadoc 文档中或是规范中学习更多的有 关蓝牙属性的内容。

2 、远端设备类:

手机蓝牙编程简介

图6 :RemoteDevice 类

远端设备(RemoteDevice ) 提供的方法中,有些很类似于本地设备 (LocalDevice )里提供的方法:

    .getBluetoothAddress() 返回蓝牙地址。

    .getFriendlyName() 返回蓝牙设备名。

    .getRemoteDevice() 返回相应的被指定蓝牙连接的远端设备。

    .authenticate() 尝试识别验证远端设备。

    .authorize() 为指定的蓝牙连接去尝试批准远端设备访问本地设备。

    .encrypt() 尝试为指定的蓝牙连接开启或关闭加密。

    .isAuthenticated() 测试是否远端设备可以被验证。

    .isAuthorized() 测试是否远端设备已经被蓝牙控制中心授权访问本地设备以进行蓝牙连接。

    .isEncrypted() 测试是否本地设备和远端设备之间的通信被加密。

    .isTrustedDevice() 测试是否远端设备被蓝牙控制中心指定为可信任的。

3 、DeviceClass 类

    一个DeviceClass 对象代表一个设备的设备类(CoD ), 例如一个打印机或者一部电话。CoD 包括一个主类,一个辅的类,和服务类型或服务类。 DeviceClass 提供了如下方法:

    .getMajorDeviceClass() 方法获取设备的主类。

    .getMinorDeviceClass() 方法获取设备的辅类。

    .getServiceClasses() 获取设备的服务类。

当 一个设备被发现,同时他的类也会被发现;当发现代理调用deviceDiscovered() 时, 其中一个参数就是DeviceClass 。你可以通过它 的getDeviceClass() 方 法找到本地设备的CoD 。

4 、DiscoveryAgent 类是个有帮助的类,它让您可以发现附近的远程蓝牙设备,并为 区域内的每个蓝牙设备返回一个 RemoteDevice 。也可以使用javax.bluetooth.DiscoveryAgent 在已经发现的远程设备上搜索服务。如果想在发生发现事件的时候得到通知,则需要实现 DiscoveryListener 接口的方 法。见图7 DiscoveryAgent 类和DiscoveryListener 接 口,如下:

手机蓝牙编程简介

5 、设备发现API

你使用DiscoveryAgent 类的" 设备发现" 方法来开始和取消设备发现:

.retrieveDevices() 重新获得已经发现或者附近的已知设备

.startInquiry() 启动发现附近设备,也叫inquiry

.cancelInquiry() 取消当前进行的任何请求

蓝牙发现代理在请求阶段的不同时候会分别调用DiscoveryListener (发 现监听器)不同的回调方法:

.deviceDiscovered() 指出是否有设备被发现。

.inquiryCompleted() 指出是否请求已经成功、触发一个错误或已被取消。

在图8 中的状态图表阐明了设备发现的状态改变结束于相应的回调方法的返回。

手机蓝牙编程简介

图 8: 设备发现状态表

设备发现以调用startInquiry() 函数开始。在请求进行时,蓝牙发现代理会在适当的时候调用回调方法DeviceDiscovered() 和 inquiryCompleted() 。

6 、服务发现API

你可以使用发现代理的服务发现方法来开始或取消服务发现:

. selectService() 启动服务发现搜索。(根据API 手册应为尝试定 位一个服务)

. searchServices() 启动服务发现搜索。

. cancelServiceSearch() 取消在正在进行中的任何的服务发现搜索操作。蓝牙发现代理在服务发现阶段的不同时候会分别 调用 DiscoveryListener 的服务发现回调方法:

. servicesDiscovered() 表示是否服务已被发现。

. serviceSearchCompleted() 表示服务发现是否已经完成。

图9 阐明了服务发现的状态改变结束于DiscoveryListener 的 回调方法的返回。

手机蓝牙编程简介

图 9: 服务发现状态图表

服务发现开始于对searchServices() 的调用。当服务搜索进行时,蓝牙发现代理会在适当的时候回调servicesDiscovered() 和 serviceSearchCompleted() 方法。

除了DiscoveryAgent 和DiscoveryListener 了, 你在服务发现过程中还要使用到的类有UUID ,ServiceRecord 以 及DataElement 等。

7 、UUID 类

    在蓝牙中,每个服务和服 务属性都唯一地由" 全球唯一标识符" (UUID )来校验。正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。UUID 类可表现为短整形(16 或32 位)和长整形(128 位)UUID 。他提供了分别利用String 和16 位或32 位数值来创建类的构造函数,提供了一个可以 比较两个UUID (如果两个都是128 位) 的方法,还有一个可以转换一个UUID 为一个字符串的方法。UUID 实 例是不可改变的(immutable ),只有被UUID 标 示的服务可以被发现。

    在Linux 下你用一个命令uuidgen -t 可以生成一个UUID 值;在Windows 下 则执行命令uuidgen 。UUID 看起来 就像如下的这个形式:2d266186-01fb-47c2-8d9f-10b8ec891363 。 当使用生成的UUID 去创建一个 UUID 对 象,你可以去掉连字符。

8 、SDDB 和ServiceRecord 接口

    在服务发现的中心是服务发现数据库(SDDB) 和服务发现协议(SDP )。SDDB 由蓝牙实现负责维护的数据库。它包含了服务记录(service records ),后者代表了对客户端有效的服务。SDP 对于基于JABWT (Java 蓝 牙无线技术APIs )应用程序来说是透明的;可以这么说,SDP 是用于服务发现的。为重新获取服务纪录,一个本地设备SDP 客 户端会向一个远端设备上SDP 服务器发出请求。

手机蓝牙编程简介

                      图 10: SDDB

每一笔服务记录都会由一个ServiceRecord 的 实例来表现。这个记录包含了描述服务细节的属性。这个类提供了几种有用的方法:

    .getAttributeIDs() 和 getAttributeValue() 方法返回服务记录的属性。

    .getConnectionURL() 方法获取链接的URL 地址给服务器 主机来收集服务记录。

    .getHostDevice() 方法获取提供服务的远端设备。

    .populateRecord() 和 setAttributeValue() 方法用来设置设备记录的属性。

    .setDeviceServiceClasses() 方法设置服务的类。

图11 显示了蓝牙本地设备和远端设备,以及SDDB 还 有服务记录之间的关系:

手机蓝牙编程简介

       图 11: 使 用远端设备,SDDB 和服务记录进行服务发现

    为使服务端可以被客户端来使用,服务应用程序要通过如下方法建立一个 服务记录,首先要创建一个连接通知器(connection notifier ),然后由调用连接 通知器的acceptAndWait() 方法来向SDDB 中 插入记录。服务端程序能够在适当的时候获得记录和更新。客户端应用程序向远端SDDB 请求可以使用 的服务,会发现服务记录。

六、如何创建一个蓝牙服务端

具 体代码步骤:

1、 得到本地设备。

try {

       this . localDevice = LocalDevice.getLocalDevice ();

       this . localDevice .setDiscoverable(DiscoveryAgent. GIAC );

    } catch (BluetoothStateException ex) {

       ex.printStackTrace();

    }

2、 生成客户端连接通告。

try {

   String url = "btspp://localhost:F0E0D0C0B0A000908070605040302010;name=BTServer;authorize=false";

       notifier = (StreamConnectionNotifier) Connector.open ( url );

    } catch (IOException ex1) {

       ex1.printStackTrace();

    }

3、 获得服务记录。

serviceRecord = localDevice.getRecord(notifier);

4、 从通告获得远端蓝牙设备的连接。

streamConnection = notifier.acceptAndOpen();

    dataOutputStream = streamConnection.openDataOutputStream();

    dataInputStream = streamConnection.openDataInputStream();

然 后下面就是等待客户端来连接了。

七、如何创建一个蓝牙客户端

1 、搜索本地设备

try {

      localDevice = LocalDevice.getLocalDevice ();

      // 获得发 现代理

      discoverAgent = localDevice .getDiscoveryAgent();

      discoverAgent .startInquiry(DiscoveryAgent. GIAC , this );

    }

    catch (BluetoothStateException ex){

      ex.printStackTrace();

}

  // 设备发现过程中的回调(用来查找每一个可用的设备)

  public void deviceDiscovered (RemoteDevice remoteDevice,DeviceClass deviceClass){

    if ( device .indexOf(remoteDevice) == -1){

      device .addElement(remoteDevice);

    }

}

    // 设备发 现完成回调(用来标识设备查找是否完成)

  public void inquiryCompleted( int _int){

    synchronized ( this ){

      notify();

    }

  }

2 、 搜索远端设备提供的服务

uuid = new UUID[2];

    uuid [0] = new UUID(0x1101);

    uuid [1] = new UUID( "F0E0D0C0B0A000908070605040302010" , false );

    for ( int i = 0; i< device .size(); i++){ // 取出已经搜到的远端设备

      remoteDevice = (RemoteDevice) device .elementAt(i);

      try { // 搜索该设备上的服务

        discoverAgent .searchServices( null , uuid , remoteDevice , this );

      } catch (BluetoothStateException ex){

          ex.printStackTrace();

      }

}

  // 服务发现回调(用来发现远端设备上的可用服务)

  public void servicesDiscovered ( int _int, ServiceRecord[] serviceRecordArray){

    for ( int i = 0; i < serviceRecordArray. length ; i++){

      service .addElement(serviceRecordArray[i]);

      serviceRecord = serviceRecordArray[i];

    }

}

  // 服务发现回调(用来标识服务搜索是否完成)

  public void serviceSearchCompleted( int _int, int _int1){

    synchronized ( this ){

      notify();

    }

  }

3 、与远端设备建立连接

String url = serviceRecord .getConnectionURL(ServiceRecord. NOAUTHENTICATE_NOENCRYPT , false );

    try {

      streamConnection = (StreamConnection)Connector.open ( url );

      dataOutputStream = streamConnection .openDataOutputStream();

      dataInputStream = streamConnection .openDataInputStream();

    } catch (IOException ex1) {

        ex1.printStackTrace();

}

经过以上步骤,服务器端和客户端就建立起了连接,相互之间就可以互相发送数据了。