BlackBerry蓝牙编程


1 BlackBerry 蓝牙编程 作者:俞伟 目录 蓝牙概述 ........................................................................................................................ 2 JSR082 的内容与 API 支持的功能 ................................................................................ 2 蓝牙协议 ........................................................................................................................ 3 蓝牙功能简表 ................................................................................................................ 4 蓝牙客户端 .................................................................................................................... 5 搜索蓝牙设备 ................................................................................................................ 5 搜索服务 ........................................................................................................................ 7 获取 URL 建立连接 ....................................................................................................... 9 客户端三种蓝牙连接方式 .......................................................................................... 10 蓝牙服务器端 .............................................................................................................. 13 启用蓝牙 SPP 服务 ...................................................................................................... 14 启用蓝牙 L2CAP 服务 ................................................................................................. 15 启用蓝牙 OBEX 服务 ................................................................................................... 16 2 蓝牙概述 Bluetooth 即我们通常说的蓝牙。蓝牙的构想源于 PAN – Personal Area Network, 个人区域 网,目的是在小范围的区域网络内实现蓝牙设备之间的通信。这个蓝牙设备一般是指带蓝 牙功能的手机或蓝牙手持设备,蓝牙设备之间的通信距离在 5-15 米之间,完全免费。常 见的蓝牙应用包括:蓝牙耳麦,文件传输,数据通信等等。 BlackBerry 平台对蓝牙的支持包括 JSR 082 和 BlackBerry 平台自身提供的 BluetoothSerialPort (蓝牙串口协议)。其中 JSR082 是比较流行,使用比较广泛的蓝牙接口标准,由 J2ME 延续 而来。本文主要说明 JSR082 蓝牙接口的使用。 JSR082 规定的内容包括: 1. Data Transmissions Only - 仅数据传输 2. 通讯协议:  L2CAP (长连接)  RFCOMM  SDP  Object Exchange Protocol 3. 功能简表:  Generic Access Profile (GAP)  Service Discovery Application Profile (SDAP)  Serial Port Profile (SPP)  Generic Object Exchange Profile (GOEP) JSR 082 API 支持以下功能: 1. Register services – 注册服务 2. Discover devices and services – 发现蓝牙设备和设备上的服务 3. Establish RFCOMM, L2CAP and OBEX connections – 建立 RFCOMM, L2CAP, OBEX 连接 4. Conduct these activities in a secure fashion – 以一种安全的方式来执行以上操作 3 表 1 列出了蓝牙协议和协议层 蓝牙协议组 协议堆栈 蓝牙核心协议 Baseband, Link Manager Protocol, L2CAP, SDP 有线连接取代协议 RFCOMM 电话通讯控制协议 TCS Binary 已经采用的协议 PPP, UDP/TCP/IP, OBEX, WAP 表 1 图 1 展现了各协议和协议层的结构 图 1 HCI-Host Control Interface 以下为蓝牙传输底层协议,HCI 之上为上层协议,HCI 为上层协 议与底层协议的通信提供了接口。L2CAP 是长连接协议,作为其他高级协议的基础,数据 传输以 byte 流为基础。RFCOMM 为连接线取代协议,模拟 RS-232 控制,数据传输通过 Baseband。RFCOMM 协议是其他使用串口通讯协议的基础。TCS Binary 定义了通话控制信 号,为蓝牙设备接收语音数据提供了通道。OBEX 协议为对象级别的传输提供服务,比如 文本文件,音频文件,视频文件,联系人文件等各种文件。 什么是蓝牙简表(Profiles)?蓝牙简表是指对于某一类蓝牙功能所需协议的概括,它是一个 标准,为该类蓝牙功能挑选适合的蓝牙通讯协议。 4 图 2 展示了常用的蓝牙功能简表 图 2 如图 2 所示,SPP 简表支持电话网络,传真,耳麦,网络访问。GOEP 简表支持文件传输, 对象推送,数据同步。TCS 简表支持无线电话等。 客户端与服务端模式 蓝牙服务是指应用服务程序通过蓝牙为客户端提供数据交互。比如打印服务就是一个蓝牙 应用服务,该应用服务程序运行在与打印机相连的服务器上,一旦客户端通过蓝牙给服务 器发送打印请求,该应用服务会执行打印操作。作为蓝牙服务端,它的服务需要定义一个 Service Record (服务记录)并添加该记录到 Service Discovery Database (SDDB)。 服务记录注册后,服务应用等待客户端发起的访问。如果服务端应用与客户端匹配,双方 会建立蓝牙通讯连接。客户端向发服务端发送请求,服务端响应请求。 5 蓝牙客户端 蓝牙应用客户端与蓝牙服务端实现交互的步骤: 1. 搜索蓝牙设备 – Device Discovery 2. 搜索服务 – Service Search 3. 获取连接 URL – Obtain Bluetooth URL 4. 建立连接 (RFCOMM, L2CAP, OBEX) – Establish Connection 5. 数据交互 – Data Transmission 搜索蓝牙设备 启动设备搜索之前,首先要获取本地蓝牙设备 MAC ID, 搜索代理,和最大服务搜索数。 public class BlueEngine implements Runnable, DiscoveryListener { private DiscoveryAgent agent = null; private StreamConnection streamConnection = null; private DataInputStream dis = null; private DataOutputStream dos = null; private Thread thread = null; private int maxServiceSearches = 0; private int serviceSearchCount; private ServiceRecord record = null; private Vector deviceList; public String command = null; public String url = null; public String macId = null; private Object bluelock = new Object(); public BlueEngine() {} public void initBluetooth() throws Exception { // 获取本地设备 LocalDevice local = LocalDevice.getLocalDevice(); // 获取本地蓝牙设备 MAC ID macId = local.getBluetoothAddress(); // 获取蓝牙设备搜索代理 agent = local.getDiscoveryAgent(); try {// 获取最大服务搜索数 maxServiceSearches = Integer.parseInt(LocalDevice.getProperty("bluetooth.sd.trans.max")); } catch (NumberFormatException e) {} deviceList = new Vector(); // 蓝牙设备队列 } 6 类 BlueEngine 需要实现 DiscoveryListener 接口来执行和完成蓝牙设备搜索。调用搜索代理 agent.startInquiry(DiscoveryAgent.GIAC, this)启动蓝牙搜索。参数 GIAC 指明搜索所有能够发 现的蓝牙设备。参数 this 是 DiscoveryListener,当搜索完成后系统调用 DiscoveryListener 的 接口回调处理。搜完成后如果蓝牙设备队列不为空,启动服务搜索。 public void getBluetoothUrl() throws Exception { record = findService(); url = record.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); } public ServiceRecord findService() { try { // 启动搜索代理发现设备并与DiscoveryListener注册回调 agent.startInquiry(DiscoveryAgent.GIAC, this); // 设备搜索过程是异步的,这里需要同步搜索过程 // 设备搜索完毕会通知主线程继续运行 synchronized (bluelock) { try { bluelock.wait(); } catch (Exception e) {} } } catch (BluetoothStateException e) { System.out.println("Unable to find devices to search"); } // 蓝牙设备队列不为空,启动服务搜索 if (deviceList.size() > 0) { if (searchServices(deviceList)) { return record; } } return null; } 7 DiscoveryListener 的两个回调接口。接口 deviceDiscovered(RemoteDevice, DeviceClass)收入 被发现的蓝牙设备。接口 inquiryCompleted(int)通知程序设备搜索完毕,通知主线程继续。 搜索服务 搜索一个指定的服务需指定 UUID 组,UUID 定义了连接方式,和服务 ID。UUID 组包括两 个标识,第一个 标识表示连接方式,比如: 连接方式 UUID 值 位数 SDP 0x0001 16-bit RFCOMM 0x0003 16-bit OBEX 0x0008 16-bit L2CAP 0x0100 16-bit Serial Port 0x1101 16-bit 第二个标识表示服务 ID,这是蓝牙客户端与服务端共同定义好的服务唯一识别,可以是 16-bit, 32-bit, 128-bit。 DiscoveryAgent.searchServices(int[] attrSet, UUID[] uuidSet, RemoteDevice btDev, DiscoveryListener listener) ,该 API 启动服务搜索,uuidSet 定义寻找的服务, btDev 是蓝牙 服务端,listener 是 DiscoveryListener 为服务搜索提供的回调操作。函数 searchServices()在 每一个发现的蓝牙设备上寻找目标服务。DiscoveryAgent 可以同时在多个蓝牙设备上查找 服务,maxServiceSearches 定义本地设备最多可以发起的服务查找。当查找没有结束时, 函数 searchServices()让主线程进入等待,当服务查询结束再继续运行。 public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) { // 当发现蓝牙设备时,如果该设备蓝牙模块的id以00027开头 // 则收入该设备 if (btDevice.getBluetoothAddress().startsWith("00027")) { deviceList.addElement(btDevice); } } public void inquiryCompleted(int discType) { // 蓝牙搜索执行完毕,通知主线程继续 synchronized (bluelock) { try { bluelock.notify(); } catch (Exception e) { } } } 8 servicesDiscovered(int transID, ServiceRecord[] servRecord)和 serviceSearchCompleted(int transID, int respCode)为 DiscoveryListener 提供的服务搜索回调接口。当在蓝牙服务端上找 到指定的服务,servicesDiscovered()被调用收入找到的服务。当在一个蓝牙服务端搜索服 务完毕,serviceSearchCompleted()会被调用。 private boolean searchServices(Vector devList) { UUID[] searchList = new UUID[2]; searchList[0] = new UUID(0x0003); searchList[1] = new UUID("cccccccccccccccccccccccccccccccc", false); for (int i = 0; i < devList.size(); i++) { try { agent.searchServices(null, searchList, (RemoteDevice) devList.elementAt(i), this); } catch (BluetoothStateException e) {} synchronized (bluelock) { serviceSearchCount++; if (serviceSearchCount == maxServiceSearches) { try { bluelock.wait(); } catch (Exception e) {} } } } while (serviceSearchCount > 0) { synchronized (bluelock) { try { bluelock.wait(); } catch (Exception e) { } } } return record != null; } public void serviceSearchCompleted(int transID, int respCode) { serviceSearchCount--; synchronized (bluelock) { bluelock.notify(); } } public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { record = servRecord[0]; } 9 获取连接 URL 获取服务 ServiceRecord 之后,调用 ServiceRecord.getConnectionURL(int, boolean)获取连接 URL。 建立连接 获取连接 URL 之后,建立 StreamConnection 连接。从连接实例获取输入流和输出流进行数 据交互。 public void getBluetoothUrl() throws Exception { record = findService(); url = record.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); } public void connect() throws Exception { streamConnection = (StreamConnection) Connector.open(url); dos = streamConnection.openDataOutputStream(); dis = streamConnection.openDataInputStream(); } 10 客户端三种蓝牙连接方式 SPP 连接方式 SPP 连接方式通过 StreamConnection 进行连接,URL 格式如下: btspp://[bluetooth address]:[port] btspp://000a95020c7b:5;name=SPPex public void runSPPConnection(){ try{ StreamConnection connection = null; DataOutputStream os = null; DataInputStream is = null; try { // 创建StreamConnection connection = (StreamConnection) Connector.open(_url); // 打开输入流读取数据 String message = "\nJSR-82 CLIENT says hello!"; os = connection.openDataOutputStream(); os.write(message.getBytes()); os.flush(); // 打开输出流发送数据 is = connection.openDataInputStream(); byte[] buffer = new byte[ 1024 ]; int readBytes = is.read(buffer); String receivedMessage = new String(buffer, 0, readBytes); } finally{ os.close(); is.close(); connection.close(); } } catch(IOException ioe){ BluetoothJSR82Demo.errorDialog(ioe.toString()); } } 11 L2CAPConnection 连接方式 L2CAP 连接 URL 格式如下: btl2cap://[Bluetooth Address]:[port];name=[name text] btl2cap://000a95020c7b:5;name=sync l2cap public void runL2CAPConnection(){ try{ L2CAPConnection connection = null; try{ //打开L2CAP连接 connection = (L2CAPConnection) Connector.open(_url); //想服务端发送数据 String message = "\n[CLIENT] JSR-82 CLIENT says hello!"; connection.send(message.getBytes()); //读取服务端数据 byte[] buffer = new byte[ 1024 ]; int readBytes = connection.receive(buffer); String receivedMessage = new String(buffer, 0, readBytes); } finally{ connection.close(); } } catch(IOException ioe){ BluetoothJSR82Demo.errorDialog(ioe.toString()); } } 12 OBEX 连接方式 OBEX 连接 URL 格式如下: "tcpobex://litespeed:6512" "btgoep://000a95020c7b:996" "irdaobex://discover.08;ias=fax" public void runOBEXConnection() { try{ Connection connection = null; OutputStream outputStream = null; Operation putOperation = null; ClientSession cs = null; try{ //打开连接,获取ClientSession connection = Connector.open(_url); cs = (ClientSession) connection; cs.connect(null); //设置HeaderSet并填充数据 byte filebytes[] = "[CLIENT] Hello..".getBytes(); HeaderSet hs = cs.createHeaderSet(); hs.setHeader(HeaderSet.NAME, "test.txt"); hs.setHeader(HeaderSet.TYPE, "text/plain"); hs.setHeader(HeaderSet.LENGTH, new Long(filebytes.length)); //从ClientSession中获取Operation putOperation = cs.put(hs); //从Operation获取输出流,发送数据 outputStream = putOperation.openOutputStream(); outputStream.write(filebytes); } finally{ outputStream.close(); putOperation.close(); cs.disconnect(null); connection.close(); } } catch(Exception e) { BluetoothJSR82Demo.errorDialog(e.toString()); } } 13 蓝牙服务器端 首先启动蓝牙服务 1. 第一步获取 LocalDevice: LocalDevice device = LocalDevice.getLocalDevice(); 2. 第二步设置搜索模式并设置搜索模式为全局模式: device.setDiscoverable(DiscoveryAgent.GIAC); 3. 启动蓝牙服务: 三种连接方式 SPP, L2CAP, OBEX,根据条件启动不同服务。 public void startBluetoothService(int uuid){ _uuid = uuid; try{ // 获取本地蓝牙设备 LocalDevice device = LocalDevice.getLocalDevice(); int mode = device.getDiscoverable(); if (mode != DiscoveryAgent.GIAC){ // 设置搜索方式为全局方式,发现附近所有蓝牙设备 device.setDiscoverable(DiscoveryAgent.GIAC); switch (_uuid){ case BluetoothJSR82Demo.SPP_UUID: //启动SPP服务,StreamConnection SPPServerThread sppThread = new SPPServerThread(); sppThread.start(); break; case BluetoothJSR82Demo.OPP_UUID: //启动OPP服务, OBEX OPPServerThread oppThread = new OPPServerThread(); oppThread.start(); break; case BluetoothJSR82Demo.L2CAP_UUID: //启动L2CAP服务, L2CAPConnectionNotifier L2CAPServerThread l2capThread = new L2CAPServerThread(); l2capThread.start(); break; } } catch(BluetoothStateException bse){ BluetoothJSR82Demo.errorDialog(bse.toString()); } } 14 启用蓝牙 SPP 服务 蓝牙 SPP 服务基于 StreamConnection。 1. 首先创建服务唯一标识 UUID UUID uuid = new UUID(_uuid); 2. 获取本地蓝牙设备,启动服务 LocalDevice local = LocalDevice.getLocalDevice(); StreamConnectionNotifier service = (StreamConnectionNotifier) Connector.open("btspp://localhost:" + uuid + ";name="+ SERVICE_NAME_SPP); connection = service.acceptAndOpen(); 3. 获取输入输出流与客户端进行读写 public void runSPPService(){ try{ StreamConnection connection = null; DataOutputStream os = null; DataInputStream is = null; try { //创建服务唯一标识 UUID UUID uuid = new UUID(_uuid); //获取本地蓝牙设备 LocalDevice local = LocalDevice.getLocalDevice(); //创建StreamConnectionNotifier StreamConnectionNotifier service = (StreamConnectionNotifier) Connector.open("btspp://localhost:" + uuid + ";name="+ SERVICE_NAME_SPP); //等待 StreamConnection 客户端的链接 connection = service.acceptAndOpen(); //获取输入流读取数据 is = connection.openDataInputStream(); byte[] buffer = new byte[1024]; int readBytes = is.read(buffer); String receivedMessage = new String(buffer, 0, readBytes); //发送消息给客户端 String message = "\nJSR-82 SERVER says hello!"; os = connection.openDataOutputStream(); os.write(message.getBytes()); os.flush(); } finally{ os.close(); is.close(); } } catch(IOException ioe){ BluetoothJSR82Demo.errorDialog(ioe.toString()); } } 15 启动蓝牙 L2CAP 服务 1. 首先创建 UUID,获取本地连接 UUID uuid = new UUID(_uuid); LocalDevice local = LocalDevice.getLocalDevice(); 2. 打开 L2CAP 连接,等待客户端连接 L2CAPConnectionNotifier service = (L2CAPConnectionNotifier) Connector.open("btl2cap://localhost:" + uuid + ";name="+ SERVICE_NAME_L2CAP); connection = service.acceptAndOpen(); 3. 接收客户端消息 byte[] buffer = new byte[ 1024 ]; int readBytes = connection.receive(buffer); String receivedMessage = new String(buffer, 0, readBytes); 4. 向客户端发送数据 String message = "\nJSR-82 SERVER says hello!"; connection.send(message.getBytes()); public void runL2CAPService(){ try{ L2CAPConnection connection = null; try{ //创建UUID,获取本地蓝牙设备 UUID uuid = new UUID(_uuid); LocalDevice local = LocalDevice.getLocalDevice(); //打开L2CAP连接,等待客户端的链接 L2CAPConnectionNotifier service = (L2CAPConnectionNotifier) Connector.open("btl2cap://localhost:" + uuid + ";name="+ SERVICE_NAME_L2CAP); connection = service.acceptAndOpen(); //创建缓存,读取数据 byte[] buffer = new byte[ 1024 ]; int readBytes = connection.receive(buffer); String receivedMessage = new String(buffer, 0, readBytes); //向客户端发送数据 String message = "\nJSR-82 SERVER says hello!"; connection.send(message.getBytes()); } finally{ connection.close(); } } catch(IOException ioe){ BluetoothJSR82Demo.errorDialog(ioe.toString()); } } 16 启动蓝牙 OBEX 服务 1. 首先创建蓝牙服务唯一标识 UUID 并获取本地蓝牙设备 UUID uuid = new UUID(_uuid); LocalDevice local = LocalDevice.getLocalDevice(); 2. 打开 OBEX 连接 SessionNotifier sessionNotifier = (SessionNotifier) Connector.open("btgoep://localhost:" + uuid + ";name="+ SERVICE_NAME_OPP); 3. 注册 OBEX 处理器来处理客户端请求 sessionNotifier.acceptAndOpen(new ObexServerRequestHandler()); public void run(){ try{ //创建服务唯一标识UUID,获取本地蓝牙设备 UUID uuid = new UUID(_uuid); LocalDevice local = LocalDevice.getLocalDevice(); //打开OBEX连接并等待客户端OBEX连接 SessionNotifier sessionNotifier = (SessionNotifier) Connector.open("btgoep://localhost:" + uuid + ";name="+ SERVICE_NAME_OPP); //注册ObextServerRequestHandler来处理客户端请求 sessionNotifier.acceptAndOpen(new ObexServerRequestHandler()); } catch(IOException ioe){ BluetoothJSR82Demo.errorDialog(ioe.toString()); } } 17 4. 实现 OBEX 处理器接口 ServerRequestHandler。onConnect()在 OBEX 连接建立的时候 被调用,是处理连接相关逻辑的地方。当客户端有数据通过 OBEX 连接输入服务端 时,onPut() 被调用,读取数据并处理。同时通过 Operation 获取输出流 向客户端 发送数据。 class ObexServerRequestHandler extends ServerRequestHandler{ //当与客户端连接上时,调用该函数 public int onConnect(HeaderSet request, HeaderSet reply){ updateStatus("[SERVER] OPP session created"); return ResponseCodes.OBEX_HTTP_OK; } //当接收数据和发送数据时调用该函数,从Operation获取InputStream, OutputStream public int onPut(Operation op){ try{ InputStream is = op.openInputStream(); //读取数据 byte b[] = new byte[1024]; int len; while(is.available() > 0 && (len = is.read(b)) > 0){ updateStatus(new String(b, 0, len)); } } catch(IOException ioe){ BluetoothJSR82Demo.errorDialog(ioe.toString()); } return ResponseCodes.OBEX_HTTP_OK; } //当断开连接时,调用该函数 public void onDisconnect(HeaderSet req, HeaderSet resp){ updateStatus("[SERVER] OPP connection closed"); } }
还剩16页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

BlackBerry

贡献于2010-09-16

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf