第一篇:Android蓝牙开发小结
学习之前先了解两个基本概念:
一、RFCOMM协议:
一个基于欧洲电信标准协会ETSI07.10规程的串行线性仿真协议。此协议提供RS232控制和状态信号,如基带上的损坏,CTS以及数据信号等,为上层业务(如传统的串行线缆应用)提供了传送能力。
RFCOMM是一个简单传输协议,其目的是针对如何在两个不同设备上的应用之间保证一条完整的通信路径,并在它们之间保持一通信段。
RFCOMM是为了兼容传统的串口应用,同时取代有线的通信方式,蓝牙协议栈需要提供与有线串口一致的通信接口而开发出的协议。RFCOMM协议提供对基于L2CAP协议的串口仿真,基于ETSI07.10。可支持在两个BT设备之间同时保持高达60路的通信连接。
RFCOMM只针对直接互连设备之间的连接,或者是设备与网络接入设备之间的互连。通信两端设备必须兼容于RFCOMM协议,有两类设备:DTE(Data Terminal Endpoint,通信终端,如PC,PRINTER)和DCE(Data Circuit Endpoint,通信段的一部分,如Modem)。此两类设备不作区分。
二、MAC硬件地址
MAC(Medium/MediaAccess Control, 介质访问控制)MAC地址是烧录在NetworkInterfaceCard(网卡,NIC)里的.MAC地址,也叫硬件地址,是由48比特长(6字节),16进制的数字组成.0-23位叫做组织唯一标志符(organizationally unique,是识别LAN(局域网)节点的标识.24-47位是由厂家自己分配。其中第40位是组播地址标志位。网卡的物理地址通常是由网卡生产厂家烧入网卡的EPROM(一种闪存芯片,通常可以通过程序擦写),它存储的是传输数据时真正赖以标识发出数据的电脑和接收数据的主机的地址。
Android平台提供的蓝牙API去实现蓝牙设备之间的通信,蓝牙设备之间的通信主要包括了四个步骤:设置蓝牙设备、寻找局域网内可能或者匹配的设备、连接设备和设备之间的数据传输。以下是建立蓝牙连接的所需要的一些基本类:
BluetoothAdapter类:代表了一个本地的蓝牙适配器。它是所有蓝牙交互的的入口点。利用它你可以发现其他蓝牙设备,查询绑定了的设备,使用已知的MAC地址实例化一个蓝牙设备和建立一个BluetoothServerSocket(作为服务器端)来监听来自其他设备的连接。
BluetoothDevice类:代表了一个远端的蓝牙设备,使用它请求远端蓝牙设备连接或者获取远端蓝牙设备的名称、地址、种类和绑定状态。(其信息是封装在bluetoothsocket中)。
Bluetoothsocket类:代表了一个蓝牙套接字的接口(类似于tcp中的套接字),它是应用程序通过输入、输出流与其他蓝牙设备通信的连接点。
Blueboothserversocket类:代表打开服务连接来监听可能到来的连接请求(属于server端),为了连接两个蓝牙设备必须有一个设备作为服务器打开一个服务套接字。当远端设备发起连接连接请求的时候,并且已经连接到了的时候,Blueboothserversocket类将会返回一个bluetoothsocket。
Bluetoothclass类:描述了一个蓝牙设备的一般特点和能力。它的只读属性集定义了设备的主、次设备类和一些相关服务。然而,它并没有准确地描述所有该设备所支持的蓝牙文件和服务,而是作为对设备种类来说的一个小小暗示。下面说说具体的编程实现 1.启动蓝牙功能:
首先通过调用静态方法getDefaultAdapter()获取蓝牙适配器BluetoothAdapter,以后你就可以使用该对象了。如果返回为空,the story is over。例如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();if(mBluetoothAdapter == null){ // Device does not support Bluetooth } 其次,调用isEnabled()来查询当前蓝牙设备的状态,如果返回为false,则表示蓝牙设备没有开启,接下来你需要封装一个ACTION_REQUEST_ENABLE请求到intent里面,调用startActivityForResult()方法使能蓝牙设备,例如:
if(!mBluetoothAdapter.isEnabled()){ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);}
2.查找设备:
使用BluetoothAdapter类里的方法,你可以查找远端设备(大概十米以内)或者查询在你手机上已经匹配(或者说绑定)的其他手机了。当然需要确定对方蓝牙设备已经开启或者已经开启了“被发现使能”功能(对方设备是可以被发现的是你能够发起连接的前提条件)。如果该设备是可以被发现的,会反馈回来一些对方的设备信息,比如名字、MAC地址等,利用这些信息,你的设备就可以选择去向对方初始化一个连接。
如果你是第一次与该设备连接,那么一个配对的请求就会自动的显示给用户。当设备配对好之后,他的一些基本信息(主要是名字和MAC)被保存下来并可以使用蓝牙的API来读取。使用已知的MAC地址就可以对远端的蓝牙设备发起连接请求。
匹配好的设备和连接上的设备的不同点:匹配好只是说明对方设备发现了你的存在,并拥有一个共同的识别码,并且可以连接。连接上:表示当前设备共享一个RFCOMM信道并且两者之间可以交换数据。也就是是说蓝牙设备在建立RFCOMM信道之前,必须是已经配对好了的。
3.查询匹配好的设备:
在建立连接之前你必须先查询配对好了的蓝牙设备集(你周围的蓝牙设备可能不止一个),以便你选取哪一个设备进行通信,例如你可以你可以查询所有配对的蓝牙设备,并使用一个数组适配器将其打印显示出来:
Set
4.扫描设备:
扫描设备,只需要简单的调用startDiscovery()方法,这个扫描的过程大概持续是12秒,应用程序为了ACTION_FOUND动作需要注册一个BroadcastReceiver来接受设备扫描到的信息。对于每一个设备,系统都会广播ACTION_FOUND动作。例如: // Create a BroadcastReceiver for ACTION_FOUND private final BroadcastReceiver mReceiver = new BroadcastReceiver(){ public void onReceive(Context context, Intent intent){ String action = intent.getAction();// When discovery finds a device if(BluetoothDevice.ACTION_FOUND.equals(action)){ // Get the BluetoothDevice object from the Intent BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);// Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName()+ “n” + device.getAddress());} } };// Register the BroadcastReceiver IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);registerReceiver(mReceiver, filter);// Don't forget to unregister during onDestroy 注意:扫描的过程是一个很耗费资源的过程,一旦你找到你需要的设备之后,在发起连接请求之前,确保你的程序调用cancelDiscovery()方法停止扫描。显然,如果你已经连接上一个设备,启动扫描会减少你的通信带宽。
5.使能被发现:Enabling discoverability 如果你想使你的设备能够被其他设备发现,将ACTION_REQUEST_DISCOVERABLE动作封装在intent中并调用startActivityForResult(Intent, int)方法就可以了。他将在不使你应用程序退出的情况下使你的设备能够被发现。缺省情况下的使能时间是120秒,当然你可以可以通过添加EXTRA_DISCOVERABLE_DURATION字段来改变使能时间(最大不超过300秒,这是出于对你设备上的信息安全考虑)。例如: Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);startActivity(discoverableIntent);运行该段代码之后,系统会弹出一个对话框来提示你启动设备使能被发现(此过程中如果你的蓝牙功能没有开启,系统会帮你开启),并且如果你准备对该远端设备发现一个连接,你不需要开启使能设备被发现功能,因为该功能只是在你的应用程序作为服务器端的时候才需要。
6.连接设备:
在应用程序中,想建立两个蓝牙设备之间的连接,必须实现客户端和服务器端的代码(因为任何一个设备都必须可以作为服务端或者客户端)。一个开启服务来监听,一个发起连接请求(使用服务器端设备的MAC地址)。当他们都拥有一个蓝牙套接字在同一RFECOMM信道上的时候,可以认为他们之间已经连接上了。服务端和客户端通过不同的方式或其他们的蓝牙套接字。当一个连接监听到的时候,服务端获取到蓝牙套接字。当客户可打开一个FRCOMM信道给服务器端的时候,客户端获取到蓝牙套接字。
注意:在此过程中,如果两个蓝牙设备还没有配对好的,android系统会通过一个通知或者对话框的形式来通知用户。RFCOMM连接请求会在用户选择之前阻塞。如下图:
7.服务端的连接:
当你想要连接两台设备时,一个必须作为服务端(通过持有一个打开的BluetoothServerSocket),目的是监听外来连接请求,当监听到以后提供一个连接上的BluetoothSocket给客户端,当客户端从BluetoothServerSocket得到BluetoothSocket以后就可以销毁BluetoothServerSocket,除非你还想监听更多的连接请求。
建立服务套接字和监听连接的基本步骤:
首先通过调用listenUsingRfcommWithServiceRecord(String, UUID)方法来获取BluetoothServerSocket对象,参数String代表了该服务的名称,UUID代表了和客户端连接的一个标识(128位格式的字符串ID,相当于PIN码),UUID必须双方匹配才可以建立连接。其次调用accept()方法来监听可能到来的连接请求,当监听到以后,返回一个连接上的蓝牙套接字BluetoothSocket。最后,在监听到一个连接以后,需要调用close()方法来关闭监听程序。(一般蓝牙设备之间是点对点的传输)
注意:accept()方法不应该放在主Acitvity里面,因为它是一种阻塞调用(在没有监听到连接请求之前程序就一直停在那里)。解决方法是新建一个线程来管理。例如: private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket;public AcceptThread(){ // Use a temporary object that is later assigned to mmServerSocket, // because mmServerSocket is final BluetoothServerSocket tmp = null;try { // MY_UUID is the app's UUID string, also used by theclient code tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);} catch(IOException e){ } mmServerSocket = tmp;} public void run(){ BluetoothSocket socket = null;// Keep listening until exception occurs or a socket is returned while(true){ try { socket = mmServerSocket.accept();} catch(IOException e){ break;} // If a connection was accepted if(socket!= null){ // Do work to manage the connection(in a separate thread)manageConnectedSocket(socket);mmServerSocket.close();break;} } } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel(){ try { mmServerSocket.close();} catch(IOException e){ } } }
8.客户端的连接:
为了初始化一个与远端设备的连接,需要先获取代表该设备的一个BluetoothDevice对象。通过BluetoothDevice对象来获取BluetoothSocket并初始化连接,具体步骤:
使用BluetoothDevice对象里的方法createRfcommSocketToServiceRecord(UUID)来获取BluetoothSocket。UUID就是匹配码。然后,调用connect()方法来。如果远端设备接收了该连接,他们将在通信过程中共享RFFCOMM信道,并且connect()方法返回。例如: private class ConnectThread extends Thread { private final BluetoothSocket mmSocket;private final BluetoothDevice mmDevice;public ConnectThread(BluetoothDevice device){ // Use a temporary object that is later assigned to mmSocket, // because mmSocket is final BluetoothSocket tmp = null;mmDevice = device;// Get a BluetoothSocket to connect with the given BluetoothDevice try { // MY_UUID is the app's UUID string, also used by the server code tmp = device.createRfcommSocketToServiceRecord(MY_UUID);} catch(IOException e){ } mmSocket = tmp;}
public void run(){ // Cancel discovery because it will slow down the connection mAdapter.cancelDiscovery();try { // Connect the device through the socket.This will block // until it succeeds or throws an exception mmSocket.connect();} catch(IOException connectException){ // Unable to connect;close the socket and get out try { mmSocket.close();} catch(IOException closeException){ } return;} // Do work to manage the connection(in a separate thread)manageConnectedSocket(mmSocket);}
注意:conncet()方法也是阻塞调用,一般建立一个独立的线程中来调用该方法。在设备discover过程中不应该发起连接connect(),这样会明显减慢速度以至于连接失败。且数据传输完成只有调用close()方法来关闭连接,这样可以节省系统内部资源。
9.管理连接(主要涉及数据的传输):
当设备连接上以后,每个设备都拥有各自的BluetoothSocket。现在你就可以实现设备之间数据的共享了。
1> 首先通过调用getInputStream()和getOutputStream()方法来获取输入输出流。然后通过调用read(byte[])和write(byte[]).方法来读取或者写数据。
2> 实现细节:以为读取和写操作都是阻塞调用,需要建立一个专用现成来管理。3>
private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket;private final InputStream mmInStream;private final OutputStream mmOutStream;public ConnectedThread(BluetoothSocket socket){ mmSocket = socket;InputStream tmpIn = null;OutputStream tmpOut = null;// Get the input and output streams, using temp objects because // member streams are final try { tmpIn = socket.getInputStream();tmpOut = socket.getOutputStream();} catch(IOException e){ } mmInStream = tmpIn;mmOutStream = tmpOut;} public void run(){ byte[] buffer = new byte[1024];// buffer store for the stream int bytes;// bytes returned from read()// Keep listening to the InputStream until an exception occurs while(true){ try { // Read from the InputStream bytes = mmInStream.read(buffer);// Send the obtained bytes to the UI Activity mHandler.obtainMessage(MESSAGE_READ, bytes,-1, buffer).sendToTarget();} catch(IOException e){ break;} } } /* Call this from the main Activity to send data to the remote device */ public void write(byte[] bytes){ try { mmOutStream.write(bytes);} catch(IOException e){ } } /* Call this from the main Activity to shutdown the connection */ public void cancel(){ try { mmSocket.close();} catch(IOException e){ } } }
第二篇:Android蓝牙连接总结
蓝牙连线时首先会定义一个专门用来连接的函数体BtconnectDevice,当按下Btconnect(为一个Buttton名时)时,程序会判断此时Btconnect键是什么状态,当Btconnect键为连接状态时,因为按了Btconnect键程序会断开蓝牙连接(调用BtconnectDevice函数,注意此函数为有参函数,实参为蓝牙设备的连接地址),并且在Btconnect上显示connect(代表此时用户可以点选Btconnect连接蓝牙);当Btconnect键为断开状态时(程序通过判断Btconnect上的字符是否为connect来判断,因为断开蓝牙连接时,会让Btconnect上显示connect),此时按下Btconnect时,程序会连接蓝牙设备(把实参DeviceMAC传给形参MACaddrofBTDevice,调用BtconnectDevice函数,但此时不知道DeciceMAC是否有数据,代表所以先要判断,当DeviceMAC大于0时,DeciceMAC内有数据,可以连接,否则没有,则不能连接)。
当程序打开时,设备会执行初始化程序(Screen1.initialize,每当程序页面返回主界面时,此函数即执行一次,现在这样理解),在程序中,首先要Btconnect按键功能失效(致Btconnect.Enable为False),然后把数据库TinyDB1中标签名为StoredDevice处存储的的蓝牙设备地址给DeviceMAC(相当于数组,TinyDB1相当于数组名,StoredDevice相当于数组下标,DeviceMAC为一个全局变量,各个程序模块可分别调用它,并修改它,修改后的数据会影响别的程序模块,不像局部变量,在这个程序中修改了变量,对别的程序不影响,APP Inventor中各个模块程序中即使是局部变量,变量名也不可以相同,C和C++就不是这样),然后程序去调用蓝牙设备配对函数(lsDevicePaired)判断是否与DeviceMAC这个设备地址配对上了,如果已配对成功,程序会首先使Btconnect按钮功能可使用,同时lstDevice上显示已配对的蓝牙MAC地址;如果没配对成功,则首先会清空DeviceMAC这个变量和StoredDevice处的数据,并通过调用Notifier.ShowAlert函数来显示配对失败。请注意初始化程序中没有使用蓝牙连接函数,它所要做的就是判断Btconnect是否可使用,如果已配对成功,则Btconnect可使用,用户可通过按此键来连接蓝牙设备。
检查错误函数,是在程序中发现错误时,程序会在屏幕上显示相关错误信息,供用户查看信息,从而准确的查出错误。
当要选择连接哪一个蓝牙设备时,按下lstDevice但没有选择弹出来的项目时(要调用蓝牙函数BluetoothClient1.AddressesAndNames,才能知道是否有蓝牙设备)会调用lstDevice.BeforePicking函数。此函数首先会断开蓝牙连接,然后在Btconnect上显示connect,表示蓝牙现在可连接,然后调用蓝牙函数BluetoothClient1.AddressesAndNames,将可用的蓝牙设备名称和地址放到 lstDevice列表中。然后用户点选lstDevice列表中的项目,程序会调用lstDeviceAfterPicking函数。函数中会将刚才点选的数据,放到DeviceMAC和TinyDB1中的StoredDevice中,lstDevice会显示连接的蓝牙设备的地址和MAC地址,然后使能Btconnect。
仅供参考,如有错误,还是以实际操作结果为准。
第三篇:蓝牙知识小结
蓝牙协议知识总结
蓝牙设备 和 主机进行连接和数据通信的 流程如下:外部设备发出广告(带有UUID信息等其他信息); 主机(集中器设备)收到广告信息,进而发送扫描请求;表示我扫描到你的信息; 3 外部设备收到扫描请求后,返回扫描回应,表示我知道你扫描到我的信息; 4 主机进而发送连接请求信息,表示主机要跟设备建立无线连接; 5 设备收到连接请求后,发送相应请求回应;表示已经建立连接;
数据读写流程如下进一步(在建立连接的基础上): 主机发送主服务UUID(设备的广告UUID)给设备;
服务发现 7 设备收到后回应服务信息; 主机发送特性UUID;
特性发现 9 设备收到后回应特性值句柄;(类似于存储设备的地址)主机发送特性值句柄;
读信息 11 设备收到后回应特性值; 主机发送特性值句柄和要写入值;
写信息 13 设备回应写入成功响应;
在睡眠状态,耗电只有1微安(uA),而在连接事件中最高的是10几个毫安
连接建立之后,再进行安全密钥的交换配对,进而进行数据的读写;
主机和从机绑定之后,断开连接后,可以快速的建立连接并进行加密读写,而不需要再次配对; 特点 低功耗蓝牙速度只有100bps,传统蓝牙有3Mbps 2 低功耗蓝牙不需要IOS 的MFI 认证,传统蓝牙必须; 3 低功耗蓝牙能纽扣电池能用1年多,传统蓝牙不行;
频道:
2.4G – 2.48G 总共40个频段,每2M 一个频段;
其中 37(2.40G),38(2.426G),39(2.48G)为 3个广播频道;这3个频道避开了wifi 常用的频道,与wifi可以共存; 其他37个为连接频道;
1、BLE中主从机建立连接,到配对和绑定的过程如下图。
正如上图所示,最简单一次蓝牙通信需要以上相关步骤,包括discovery device,connect,pairing,bond等4个主要部分。
1)广播:广播包可以包含广播数据,广播包可以无指定或者对指定的设备发送。可以声明该器件是可连接的还是不可连接的。在一次广播中,广播包可以在三个广播通道中同时发送。
广播类型 :1 未指定可连接 2 指定可连接 3 未指定 不可见 4 未指定不可连接 #define GAP_ADTYPE_ADV_IND
0x00 //!< Connectable undirected advertisement #define GAP_ADTYPE_ADV_DIRECT_IND 0x01 //!< Connectable directed advertisement #define GAP_ADTYPE_ADV_DISCOVER_IND
0x02 //!< Discoverable undirected advertisement #define GAP_ADTYPE_ADV_NONCONN_IND
0x03 //!< Non-Connectable undirected advertisement #define GAP_ADTYPE_SCAN_RSP_IND
0x04 //!< Only used in gapDeviceInfoEvent_t 在peripheral.c中
GAPRole_Init(taskID++);进行了初始化设置
还有有以下函数bStatus_t GAPRole_SetParameter(uint16 param, uint8 len, void *pValue)GAPRole_GetParameter(…..)可以调用进行设置。2)scanning 1)被动扫描: 扫描者监听广播频道的广播包,收到后将其上传到host层
2)主动扫描:扫描者监听广播频道的广播包,当收到广播包后扫描者发送一个scan Request包,广播设备回应一个scan reponse包
3)Connection 在扫描设备扫描到一个可连接的广播消息后,扫描设备可以通过发送 connection reequst 包给广播设备从而成为连接的发起者
Connection resqust 包含从机链路层一系列的参数,这些参数声明连接时的通道及时序要求。建立连接
GAPCentralRole_EstablishLink(DEFAULT_LINK_HIGH_DUTY_CYCLE,DEFAULT_LINK_WHITE_LIST,addrType, peerAddr);广播设备接收了连接请求,就进入了连接状态,发起者成了主机,广播设备成了从机。两个已连接的设备的所有通信发生在连接事件中,连接事件周期性的发生,周期由连接间隔参数决定。
连接间隔:使用调频的间隔;两个连接事件之间的时间间隔,蓝牙传数据是在一个频段发送数据后,然后跳到另一个频段再传数据,从一个频道另一个频段的时间间隔就是连接间隔;即使没有数据发送,也要调频切换测试包是否连接断开;所以,连接间隔是定时的存在;可以认为是一个固定的时序;每隔一段时间就自动调到另一个频道的去建立连接;这个时间中,是很少功耗的,基本没有;
单位是1.25毫秒;范围是 6----3200个单位;也就是1.25ms到4s的范围 不同的应用 时间间隔不一样,时间间隔长,功耗就低,传输数据慢; 时间间隔短,功耗就高,传输数据就快。
从机延时: 从机如果没有数据发送,可以跳过连接间隔,不用频繁的定时去建立连接,从而过一段较长时间再去建立连接;这个时间就是从机延时时间;从而功耗降低很多;单位是和连接间隔一样;范围是 0---499
管理超时
超过这个时间,还没有建立连接,则认为是连接丢失,断开。回到未连接状态;
单位是10ms,范围是 10(100ms)-----3200(32s)。超时值必须比有效连接间隔大;有效连接间隔= 连接间隔×(1+从机延时)
如果从机不想使用当前的连接参数,可以向主机发送连接更新请求,从机设备可以在任何时候发送连接更新请求,使得从机可以动态的调整连接参数。
GAPCentralRole_UpdateLink(simpleBLEConnHandle,DEFAULT_UPDATE_MIN_CONN_INTERVAL,DEFAULT_UPDATE_MAX_CONN_INTERVAL,DEFAULT_UPDATE_SLAVE_LATENCY,DEFAULT_UPDATE_CONN_TIMEOUT);无论主机还是从机,都可以无条件的终止当前连接,一方请求终止,另一方必须在断开连接状态之前响应。
连接还可以由超时而终止。超时时间小于32s,大于有效连接间隔(连接间隔×(1+从机延时))
终止连接函数:
GAPCentralRole_TerminateLink(simpleBLEConnHandle);主机和从机保存各自的超时计时器,每次收到数据包就清零,一旦达到超时数值,就认为连接已经丢失就会断开连接。
连接超时判断,终止连接在程序中还没找到。
2.BLE中的GAP和GATT GAP个人认为就是监控上图中的交互状态,比如从广播变成连接,到配对等。
GATT通俗理解为用于主从机之间的客户端和服务器端的数据交互,以Attribute Table来体现。
GAP Role Profile:在GAP所处的4个角色:广播Advertise,主机central,从机Peripheral,观察者Observer。GATT Attribute:通用属性配置文件。
GAP作为Peripheral Role需要设置的核心参数如下 GAPROLE_ADVERT_ENABLED:广播使能。GAPROLE_ADVERT_DATA:广播时的参数,GAPROLE_SCAN_RSP_DATA:从机扫描响应,返回的数据包
GAPROLE_MIN_CONN_INTERVAL:处于连接状态后的设备,都会有个hop,一段时间内进行数据交互,以保证两者是连接的。当前后两次交互时,需要等待的最小间隔时间 GAPROLE_MAX_CONN_INTERVAL:...需要等待的最大间隔时间
GAPROLE_SLAVE_LATENCY:处于连接后,从机可以做出不响应连接请求的间隔数目,即跳过n个交互的连接。
GAPROLE_TIMEOUT_MULTIPLIER:从上次成功连接到这次连接成功的最大允许延时。如果规定时间内未成功则认为本次连接失败,丢弃。该值必须比有效连接的间隔大。GAPROLE_PARAM_UPDATE_ENABLE:请求主机更新参数,主机可以接受也可以拒绝。.GATT Server的相关设置函数。
// Initialize GATT attributes GGS_AddService(GATT_ALL_SERVICES);// GAP Service GATTServApp_AddService(GATT_ALL_SERVICES);// GATT attributes DevInfo_AddService();// Device Information Service SimpleProfile_AddService(GATT_ALL_SERVICES);// Simple GATT Profile 通常一个GATT中GAP server和GATT server是必须强制存在的,还有设备信息服务以及自己设计的profile server.SimpleProfile_AddService就是添加自己设计的profile server 在SimpleProfile_AddService函数中调用了如下函数
GATTServApp_RegisterService(simpleProfileAttrTbl, GATT_NUM_ATTRS(simpleProfileAttrTbl),&simpleProfileCBs);
} simpleProfileCBs 的函数定义如下:
CONST gattServiceCBs_t simpleProfileCBs = { simpleProfile_ReadAttrCB,// Read callback function pointer
simpleProfile_WriteAttrCB, // Write callback function pointer
NULL
// Authorization callback function pointer };实际上就是底层读写数据的函数,主机读数据时从机会调用simpleProfile_ReadAttrCB函数,写数据时从机会调用simpleProfile_WriteAttrCB函数。这两个函数在simpleGaatprofile.c 中实现。
注意在simpleProfile_WriteAttrCB,函数中有如下语句 if((notifyApp!= 0xFF)&& simpleProfile_AppCBs && simpleProfile_AppCBs->pfnSimpleProfileChange)
{
simpleProfile_AppCBs->pfnSimpleProfileChange(notifyApp);
} 注意函数指针的用法,实际是在接收到主机数据发过来的数据后调用simpleProfileChangeCB函数来处理接收到的数据。这个函数在初始化时注册,下面会讲到。作为GATT的server和client,主要通过Attribute来进行交互,当client请求server读取数据时,通过如下注册的回调函数来进行访问。
// Register callback with SimpleGATTprofile
VOID SimpleProfile_RegisterAppCBs(&simpleBLEPeripheral_SimpleProfileCBs);//给应用注册回调函数,这个函数非常重要 在回调函数中对数据做出处理。
static simpleProfileCBs_t simpleBLEPeripheral_SimpleProfileCBs = {
simpleProfileChangeCB
// Charactersitic value change callback };
在SimpleProfile_RegisterAppCBs 函数中赋值。simpleProfile_AppCBs = simpleProfileChangeCB
在simpleProfileChangeCB函数中可以启动定时器来给主机发送Notification数据
发送数据函数为GATT_Notification(noti_cHandle,&pReport,FALSE);
如下:
{ static attHandleValueNoti_t pReport;//声明attHandleValueNoti_t这个结构体
uint16 noti_cHandle;//存放handle
pReport.handle = simpleProfileAttrTbl[11].handle;//读取notification对应的handle
GAPRole_GetParameter(0x30E, ¬i_cHandle);//获取Connection Handle
pReport.len = 1;//数据长度
pReport.value[0] = 0x03;//赋值
GATT_Notification(noti_cHandle,&pReport,FALSE);}
主机使能Notification:
这个handle应该是相应的characteristic value的handle的后面一个, 就是characteristic value的handle加 1.例子: { attWriteReq_t writeReq;writeReq.handle =;writeReq.len = 2;writeReq.value[0] = LO_UINT16(GATT_CLIENT_CFG_NOTIFY);
这里是 0x01 writeReq.value[1] = HI_UINT16(GATT_CLIENT_CFG_NOTIFY);
这里是 0x00 writeReq.sig = 0;writeReq.cmd = 0;GATT_WriteCharValue(simpleBLEConnHandle, &writeReq, simpleBLETaskId);}
这两个值目的是打开Notification功能.CCC的参数有两个, 一个Notification, 一个indication.value[0]就是打开关闭notification, value[1]是打开关闭indication.主机数据处理:
simpleBLECentral.c 这个文件, 里面有个函数:simpleBLECentralProcessGATTMsg()这个函数就是处理各种从peripheral过来的数据.但是在示例代码中并没有加入通知, 就是notification的接收, 所以你得自己添加代码.很简单, 类似 if((pMsg->method == ATT_READ_RSP)||........), 添加 else if((pMsg->method == ATT_HANDLE_VALUE_NOTI)||......)就可以处理从机Notification的数据。
value 被写的时候首先 simpleProfile_WriteAttrCB()会被调到.最后才会调用 simpleProfileChangeCB()
GAP通过在启动设备事件的任务处理中启动设备,其实主要是向GAP中注册回调函数,让系统在发现自身运行状态变化时,调用该函数,方便应用层进行相关操作。if(events & SBP_START_DEVICE_EVT)
{
// Start the Device VOID GAPRole_StartDevice(&simpleBLEPeripheral_PeripheralCBs);//启动设备,注册回调函数,用于监督设备的状态变化:广播、连接、配对、绑定等。
// Start Bond Manager VOID GAPBondMgr_Register(&simpleBLEPeripheral_BondMgrCBs);} simpleBLEPeripheral_PeripheralCBs函数定义如下
static gapRolesCBs_t simpleBLEPeripheral_PeripheralCBs = {
peripheralStateNotificationCB, // Profile State Change Callbacks
NULL
// When a valid RSSI is read from controller(not used by application)};static void peripheralStateNotificationCB(gaprole_States_t newState)//传入参数由GPA自己输入,内部调用回调函数给用户,处理连接状态的改变 simpleBLEPeripheral_BondMgrCBs函数定义如下: static gapBondCBs_t simpleBLEPeripheral_BondMgrCBs = {
ProcessPasscodeCB,// 生成配对密码,发送给主机
ProcessPairStateCB
//主机密码的校验处理。配对状态管理 };
一、修改广播功率 { #define LL_EXT_TX_POWER_MINUS_23_DBM
0 //-23dbm 功率 最小 #define LL_EXT_TX_POWER_MINUS_6_DBM
//-6dbm
#define LL_EXT_TX_POWER_0_DBM
// 0dbm
#define LL_EXT_TX_POWER_4_DBM // +dbm 功率 最大
HCI_EXT_SetTxPowerCmd(gTxPower);
更新广播内容
GAP_UpdateAdvertisingData(simpleBLEPeripheral_TaskID, TRUE, sizeof(advertData_Ex), advertData_Ex);}
二、数据加密解密 { uint8 key[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
// 需要加密的数据
uint8 plaintextData[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
// 加密后数据存放区
uint8 encryptedData[16];
// 解密后数据存放区
uint8 deccryptedData[16];
// 开始加密
LL_Encrypt(key, plaintextData, encryptedData);
// 开始解密
LL_EXT_Decrypt(key, encryptedData, deccryptedData);}
三、设置从机广播时间 1)、广播模式必须是Limited Discoverable mode 在advertData 中加入此AD string: 0x02, GAP_ADTYPE_FLAGS, GAP_ADTYPE_FLAGS_LIMITED| GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED 2)、设置时间
//Maximum time to remain advertising, when in Limited Discoverable mode.unit is seconds #define USER_DEF_ADV_TIMEOUT
GAP_SetParamValue(TGAP_LIM_ADV_TIMEOUT, USER_DEF_ADV_TIMEOUT);
第四篇:android 开发心得
即 使你的应用程序是快速且响应灵敏的,但一些设计仍然会给用户造成问题——与其它应用程序或对话框未事先计划的交互,意外的数据丢失,意料之外的阻塞等等。避免这些问题,有助于理解应用程序运行的上下文和系统的交互过程,而这些又正影响着你的应用程序。简而言之,你应该竭尽全力去开发一个与系统和其它应用程 序流畅交互的应用程序。
一 个常见的流畅问题是,一个应用程序的后台处理——例如,一个 Service或者
BroadcastReceiver——弹出一个对话框来响应一些事件。这可能看起来没啥大碍,尤其是你在模拟器上单独地构建和测试你 的应用程序的时候。然而,当你的应用程序运行在真机上时,有可能你的应用程序在没有获得用户焦点时后台处理显示了一个对话框。因此,可能会出现在活跃的应 用程序后方显示了你的应用程序的对话框,或者从当前应用程序夺取焦点显示了一个对话框,而不管当前用户正在做什么(例如,正在打电话)。那种行为,对应用 程序或用户来说,就不应该出现。
为了避免这些问题,你的应用程序应该使用合适的系统资源来通知用户——Notification类。使用Notification,你的应用程序可以在状态栏显示一个 icon来通知用户已经发生的事情,而不是夺取焦点和打断用户。
另 一个流畅问题的例子是未能正确实现Activity的 onPause()和其它生命周期方法而造成意外丢失了状态或用户数据。又或者,如果你的应用程序想暴露数据给其它应用程序使用,你应该通过 ContentProvider来暴露,而不是(举例)通过一个可读的原始文件或数据库来实现。
这 些例子的共同点是它们都应该与系统和其它应用程序协作好。Android系统设计时,就把应用程序看作是一堆松散耦合的组件,而不是一堆黑盒代码。作为开 发者来说,允许我们把整个系统看作是更大的组件集合。这有益于我们可以与其它应用程序进行清晰无缝的集成,因此,作为回报,我们应该更好的设计我们的代 码。
下面将讨论常见的流畅问题以及如何避免它们:
一 定要记住Android是一个移动平台。可以显而易见地说,其它Activity(例如,“Incoming Phone Call”应用程序)可能会在任何时候弹出来遮盖你的Activity,记住这个事实很重要。因为这个过程将触发 onSaveInstanceState()和 onPause()方法,并可能导致你的应用程序
被杀死。
如 果用户在你的应用程序中正在编辑数据时,其它 Activity出现了,这时,你的应用程序被杀死时可能丢失那些数据。当然了,除非你事先保存了正在进行的工作。“Android方式”是这样做的:能 接收和编辑用户输入的 Android应用程序应该重写 onSaveInstanceState()方法,并以恰当的方式保存它们的状态。当用户重新访问应用程序时,她能得到她的数据。进行这种处理方式最经典的例子是 mail应用程序。如果用户正在输入 email,这时其它 Activity启动了,mail应用程序应该把正在编辑的email以草稿的方式保存起来。
如果你不想穿着内衣在大街上溜达的话,你的数据也不应该这样。尽管可能存在暴露应用程序的某种形式给其它应用程序,但这通常不是最好的主意。暴露原始数据,要求其它应用程序能够理解你的数据的格式;如果你变更了格式,那么,你将破坏那些没有进行同步更新的应用程序。
“Android 方式”是创建一个 ContentProvider,以一种清晰的、深思熟虑的和可维护的API方式暴露你的数据给其它应用程序。使用 ContentProvider,就好像是插入Java接口来分离和组装两片高耦合的代码。这意味着你可以修改数据的内部格式,而不用修改由 ContentProvider暴露的接口,这样,也不会影响其它应用程序。
如果用户正在运行一个应用程序(例如,Phone程序),断定对用户操作的目的才是安全的。这也就是为什么必须避免创建Activity,而是直接在当前的 Activity中响应用户的输入。那 就是说,不要在 BroadcastReceiver或在后台运行的 Service中调用 callActivity()。这么做会中断当前运行的应用程序,并导致用户恼怒。也许更糟糕的是,你的 Activity可能成为“按键强盗”,窃取了用户要提供给前一个 Activity的输入。视乎你的应用程序所做的事情,这可能是个坏消息。
不 选择在后台直接创建 Activity UI,取而代之的是,应该使用NotificationManager来设置 Notification。它们会出现在状态栏,并且用户可以在他空闲的时候点击它们,来查看你的应用程序向他显示了什么。(注意,如果你的 Activity已经在前台了,以上将不适用:这时,对于用户的输入,用户期望的是看到下一个 Activity来响应)
如果你的应用程序需要执行一些昂贵或耗时的计算的话,你应该尽可能地将它挪到线程里。这将阻止向用户显示可怕的“Application Not Responding”对话框,如果不这样做,最终的结果会导致你的应用程序完全终止。
一 般情况下,Activity中的所有代码,包括它的 View,都运行在相同的线程里。在这个线程里,还需要处理UI事件。例如,当用户按下一个按键,一个 key-down事件就会添加到 Activity的主线程队列里。事件处理系统需要很快让这个事件出列并得到处理;如果没有,系统数秒后会认为应用程序已经挂起并为用户提供杀死应用程序 的机会。
如果有耗时的代码,内联在Activity上运行也就是运行在事件处理线程里,这在很大程度上阻塞了事件处理。这会延迟输入处理,并导致ANR对话框。为了避免这个,把你的计算移到线程里。
任 何值得使用的应用程序都可能有几个不同的屏幕。当设计UI屏幕时,请一定要使用多个Activity对象实例。依赖于你的开发背景,你可能理解 Activity类似于 Java Applet,它是你应用程序的入口点。然而,那并不精确:Applet子类是一个 Java Applet的单一入口点,而一个Activity应该看作是你的应用程序多个潜在入口点之一。你的“main”Activity和其它之间的唯一不同点 是“main”Activity正巧是在AndroidManifest.xml文件中唯一对“android.intent.action.MAIN”动作感兴趣的Activity。因此,当设计你的应用程序的时候,把你的应用程序看作是Activity对象的 集合。从长远来看,这会使得你的代码更加方便维护。
当 谈到 UI观感时,巧妙地交融非常重要。用户在使用与自己期望相反的 UI的应用程序时,会产生不愉快的感觉。当设计你的 UI时,你应该尽量避免太多自己的主题。相反的,使用同一个主题。你可以重写或扩展你需要的主题部分,但至少在与其它应用程序相同的 UI基础上开始。
不 同的 Android设备可能支持不同的屏幕分辨率。甚至一些可以自己变更分辨率,例如,切换到风景模式。确保你的布局和图片能足够灵活地在不同的设备屏幕上正 常显示。幸运的是,这很容易做到。简而言之,你需要做的是为主要分辨率提供不同版本的作品,然后为不同的尺寸设计你的布局。(例如,避免使用硬编码位置而 使用相对布局。)如果那样做的话,系统会处理剩下的部分,而且你的应用程序在任何设备上都看起来很棒。
Android设备会有多种网络连接选项。所有的都提供数据访问,但之间肯定有更快的。其中,速度最慢的是GPRS,GSM网络的非 3G数据服务。即使具备 3G能力的设备在非3G的网络上也会花费很多的时间,所以,网络很慢仍然是一个长期存在的事实。
这 就是为什么你应该按照最小化的网络访问和带宽来编写你的代码。你不能假设网络是快速的,所以,你应该总是计划它是慢的。如果你的用户碰巧在一个快速的网络 上,那很好——他们的用户体验会提升。你要避免相反的情形:在不同的地点和不同时间,应用程序有时可用,有时慢得令人抓狂,这样的程序可能不会受欢迎。
还 有一个潜在的地方是,如果你正在使用模拟器,那么你很容易受它迷糊,因为模拟器使用电脑的网络连接。这比手机网络快很多,所以,你需要修改模拟器设定来模 拟较低的网络速度。你可以在 Eclipse中做到这点,在启动选项的模拟器设置页里设置或者在启动模拟器时通过命令行选项设置。
Android 可以支持多种外观形状。也就是说,一些Android设备拥有全“QWERTY”键盘,而其它可能会有40键、12键或其它键盘设置。同样的,一些设备可 能有触摸屏,但一些也会没有。当创建你的应用程序的时候,记住这一点。不要假定特定的键盘布局——除非你真的想限定你的应用程序只运行在某些设备上。
如 果移动设备经常插在墙上,那么,它也就不是很“移动”。移动设备是电池供电的,如果我们能让每次充电的电池使用得更持久一些,那么每个人都会更加开心—— 尤其是用户。
其中两大耗电硬件是处理器和无线;这也就是我们为什么要写尽可能少做工作、尽可能少去使用网络的应用程序的重要原因。
如 何让你的应用程序最小化的占用处理器,归根结底还是要写高效代码。为了减少无线的电量消耗,确保对错误条件进行正确的处理,并只获取你要的东西。例如,如 果某一个网络操作失败了,不要不断地进行重试。如果失败了一次,有可能是用户不受欢迎,因此,如果你再以正确的方式操作,有可能还会失败;所有你做的都是 在浪费电池。
用户是相当聪明的:如果你的程序高耗电,他们是一定会发现的。到那个时点,你唯一可以确定的是,你的程序将很快被卸载掉。
第五篇:CSR蓝牙耳机开发简介(模版)
CSR蓝牙耳机开发简介
一.BC6145 1.连接spi板和BC6145,电脑并口连接到spi下载板上,bc6145要开机,如下图:
2.打开Headset Config Tool BC6145软件,修改pskey
先选择从目标板上读取pskey设置,如下图,选择open from device 在各设计选择完后,选择write to device(若要保存设置文件,则选择write PSR)3.需要复位eeprom,才能使设置起作用,复位可以断电复位,或者关机后等1分钟进入待机后再开机即可起作用。