此文档为开发视界翻译转载者请注明出处(开发视界 www.sf.org.cn)否则追究法律责任
三、设备搜索
Symbian系统中主要有两种搜索设备的方法。第一种方法,客户端以用程序向有效范围内的所有设备发送请求,然后处理返回的数据。这个时候,可以使用蓝牙的用户接口,该接口自动发送请求,处理反馈,向用户提供一个对话用于选择发现的设备。另外一个方法就是使用RHostResolver 类。这两种设备搜索方法,下面还会讲到。
1、使用蓝牙用户接口
蓝牙用户接口处理所有的工作,包括搜索可用蓝牙设备以及将搜索到的设备反馈给用户。用户接口作为以给插件提供给RNotifier类。该类是一个通用的类,可以接受任何由操作系统提供的用户接口。该类在头文件e32std.h中定义。
RNotifier类可以用于各种不同类型的客户应用程序,该类可以对后台的县城进行访问,在前台提供选择设备的界面。该方法同样不需要客户应用程序具备图形用户界面,而且,蓝牙设备选择也可以以DLL的形式来使用。
因为RNotifier类是一个基类,可以用于各种不同的对话中,因此需要一个唯一的一个编号来表示一种对话。蓝牙用户接口的设备选择是通过KDeviceSelectionNotifierUid的常量值来表示的。该类定义在头文件btextnotifiers.h 中。
设备选择线程使用一系列类在客户端和服务器段传输数据和设置命令。这些内容将在下面的段落中阐述。
TBTDeviceSelectionParams 类可以把客户端应用程序的初始参数传输给选择过程。
客户端用用程序可以对搜索的设备条件进行设置,该功能通过SetDeviceClass类实现。客户端应用程序可以对搜索设备的数量和类型进行限制,也限制了用户可见的搜索结果。
客户端应用程序同羊可以对所需服务的UUID进行设置,这同样可以限制用户所得到的搜索结果。该功能可以通过SetUUID实现。
客户端应用程序搜索所有的可用设备的时候,不管设备能够提供什么服务,客户端应用程序都将生成一个默认参数的TBTDeviceSelectionParams 对象。
当使用者选择了一个设备后,后台的线程马上结束。向客户端显示用户选择的功能是通过TBTDeviceResponseParams 实现的。
IsValidxxxx 函数用于确定在类中相关的信息已经完成设置。比如,如果IsValidBDAddr 函数反回真,客户端应用程序可以认为选择的蓝牙设备的地址已经设置完成。如果返回假,说明信息不存在。这通常在发生错误的时候会产生。
客户端应用程序还可以通过BDAddr函数来获取被选择的蓝牙设备的地址,设备的名称由DcviceName函数来实现。
前面已经提到,RNotifier类通过访问后台现程完成了最困难的工作-设备的搜索和显示。这个过程是在服务器端运行的。在客户端和服务器数据传递的过程中,Symbian操作系统的开发接口提供了一系列的数据缓冲包如下所示:
 􀂃 TBTDeviceSelectionParamsPckg 
 􀂃 TBTDeviceSelectionParams 
 􀂃 TBTDeviceResponseParamsPckg 
 􀂃 TBTDeviceResponseParams 
蓝牙OBEX例子6运行了Bluetooth Device Selection Notifier,该程序可以从www.forum.nokia.com下载。下面是BtServiceSearcher.cpp的代码。
前面已经提醒,Rnotifier提供了访问服务器端的方法。但是在使用Rnotifer前,必须保证客户端应用程序已经和服务器连接。
RNotifier iDeviceSelector; //in btservicesearcher.
User::LeaveIfError(iDeviceSelector.Connect()); 
通过这个方法创建了TBTDeviceSelectionParamsPckg项目,该项目包含TBTDeviceSelectionParams 类。需要注意的是,该类初始化的参数都是默认的,一个客户端应用程序按照这个参数运行,将显示有效范围内所有的可发现的设备。
TBTDeviceSelectionParamsPckg selectionFilter; 
selectionFilter().SetUUID( ServiceClass() ); 
当使用者选择了一个设备,RNotifier通过使用一个动态对象TRequestStatus 发送信息。在OBEX的例子中,CObjectExchangeClient::ConnectL 函数调用SelectDeviceByDiscoveryL 。当使用者选择了一个设备,就调用CObjectExchangeClient::RunL 函数。CObjectExchangeClient 这里不详细介绍。
RNotifier被调用于显示蓝牙选择对话框。这个时候,RNotifier执行代码搜索可用的设备并向使用者显示。第一个需要介绍的参数是TRequestStatus,第二个参数是一个整形常量,表示蓝牙设备选择对话框;第三个参数是选择过滤器;最后一个参数是反馈参数。
当用户选择一个设备的时候,就会产生TBTDeviceResponseParamsPckg,实际上这是一个异步的调用,正在运行的线程并不暂停而是继续执行下一个过程。
iDeviceSelector.StartNotifierAndGetResponse( 
aObserverRequestStatus, 
KDeviceSelectionNotifierUid, 
selectionFilter, 
iResponse ); 
TRequestStatus 用于在设备选择出现错误的时候返回错误的状态。
当RNotifier对象结束的时候客户端应用程序结束。RNotifier卸载蓝牙设备选择对话,关闭与服务器的联接。如果RNotifier服务器没有被其他应用程序占用,该程序也从操作系统中释放。
if ( iIsDeviceSelectorConnected ) 

iDeviceSelector.CancelNotifier(KDeviceSelectionNotifierUid); 
iDeviceSelector.Close(); 

关于更多蓝牙设备选择对话的信息,请参考SDK。
1)设备选择通知的局限性
当启动设备选择通知的时候,程序开发者需要启动设备选择通知然后选择一个远程设备。这就意味着开发者不能对该插件进行编程。用这个插件,一次只能建立一个连接,而不能实现一点对多点的连接。而且,用户界面也不能满足程序开发者个性的设计的视觉和感觉的要求。
在任何情况下,开发者都需要使用RHostResolver 类完成查询
2)使用RHostResolver 运行设备搜索
Symbian操作系统通过基本的端口类RHostResolver执行地址和名称的搜索。TinquirySockAddr 函数就是完成这样的功能。这个蓝牙端口雷将蓝牙设备地址、查询访问代码以及服务和设备封装在一起。
RHostResolver 被定义在es_sock.h 头文件中。其公共接口代码如下:
class RHostResolver : public RSubSessionBase 

public: 
IMPORT_C TInt Open(RSocketServ& aSocketServer,TUint anAddrFamily,TUint aProtocol); 
IMPORT_C TInt Open(RSocketServ& aSocketServer,TUint anAddrFamily,TUint aProtocol, RConnection& aConnection); 
IMPORT_C void GetByName(const TDesC& aName,TNameEntry& aResult,TRequestStatus& aStatus); 
IMPORT_C TInt GetByName(const TDesC& aName,TNameEntry& aResult); 
IMPORT_C void Next(TNameEntry& aResult,TRequestStatus& aStatus); 
IMPORT_C TInt Next(TNameEntry& aResult); 
IMPORT_C void GetByAddress(const TSockAddr& anAddr,TNameEntry& aResult,TRequestStatus& aStatus); 
IMPORT_C TInt GetByAddress(const TSockAddr& anAddr,TNameEntry& aResult); 
IMPORT_C TInt GetHostName(TDes& aName); 
IMPORT_C void GetHostName(TDes& aName,TRequestStatus &aStatus); 
IMPORT_C TInt SetHostName(const TDesC& aName); 
IMPORT_C void Close(); 
IMPORT_C void Cancel(); 
IMPORT_C void Query(const TDesC8& aQuery, TDes8& aResult, TRequestStatus& aStatus); 
IMPORT_C TInt Query(const TDesC8& aQuery, TDes8& aResult); 
IMPORT_C void QueryGetNext(TDes8& aResult, TRequestStatus& aStatus); 
IMPORT_C TInt QueryGetNext(TDes8& aResult); 
private: 
};
1)获取远程设备的地址
通过使用RHostResolver查询远程设备的地址:
(1) 连接端口服务器(RSocketServ),通过RSocketServ::FindProtocol()选择使用的协议。BTLinkManager 提供了地址和名称的查询功能。
(2) 创建和初始化RHostResolver对象。
(3) 设置查询参数。尤其地址查询,KHostResInquiry 标志必须通过TInquirySockAddr::SetAction() 设置。
然后,查询从RHostResolver::GetByAddress()开始。 
(4) GetByAddress() 完成,将其获取的地址和第一个获取的设备类传递给TNameEntry 对象(如果没有发现设备的话,就不会定义)。
(5) 如果要获取所有发现的设备,就反复调用RHostResolver::Next()直到KerrHostResNoMoreResults 返回。
这部分在例子7中还将继续阐述。

2)获取远程设备名称
获取名称的过程和获取地址的过程非常相似,差别是获取名称的时候TInquirySockAddr 行为标志要设置成KHostResName。
名称作为一个成员返回,可以被TNameEntry访问。
如果要同时对地址和名称进行查询,需要使用SetAction(KHostResName|KHostResInquiry)。

3)查询设置代码
在蓝牙说明书中,共提到了两种不同的查询设置代码。默认的情况是查询有效范围内所有设备。在这种情况下,查询代码使用得是基本查询设置(GIAC)。但是这种方法可能会使用非常长的时间。
另外,开发人员可以对扫描条件进行限制叫限制搜索模式(LIAC),在这种模式下,只能搜索到符合条件地设备,其他设备将被过滤。
当应用程序运行于两个或者更多设备给的时候,通常使用LICA模式。
使用LIAC模式进行扫描的设备,可以被使用LIAC或者GIAC的查询发现(因为LIAC查询的优先权比较高)。
使用LIAC模式查询设备的时候只能找到用LIAC模式扫描的设备。
如果要充分利用LIAC的快速查询的优势,最好将执行搜索的设备也设置成LIAC模式。
TInquirySockAddr::SetIAC() 就是设置蓝牙查询设置的方法。KGIAC 用于基本查询,而KLIAC 用于条件查询。
以上介绍了两种查询方法。其中使用条件查询的设备之对条件查询起作用,这样使查询更快。
通过Symbian操作系统的公共借口设置条件查询的方法如下:
socket.Ioctl(KHCIWriteDiscoverabilityIoctl, 
status, &mode(=KLIAC), KSolBtHCI); returns -5 in V2 
//In V2: 
//first define the property: 
RProperty::Define(KPRopertyUidBluetoothControlCategory, KPropertyKeyBluetoothLimitedDiscoverable, RProperty::EByteArray); 
//KPropertyKeyBluetoothGetLimitedDiscoverableStatus 
//in S60 3rd Edition! 
TPckgBuf<TUint32> type = KLIAC; // or KGIAC 
int error = RProperty::Set(KPropertyUidBluetoothControlCategory, KPropertyKeyBluetoothLimitedDiscoverable, type); 
//OR directly like this: 
int error = RProperty::Set(KPropertyUidBluetoothControlCategory, KPropertyKeyBluetoothLimitedDiscoverable , (mode == KLIAC)?ETrue:EFalse); 

4)一点对多点查询案例
下面的代码来源于蓝牙PMP案例7。虽然这个例子是为S60平台编写的,但是其中与蓝牙相关的功能也适用于其他平台。
在PMP中,CBluetoothPMPExampleEngine 用于管理监听操作、搜索设备、连接、数据发送、断开连接。在该结构中,断口服务已经打开。
void CBluetoothPMPExampleEngine::ConstructL() 

User::LeaveIfError(iSocketServ.Connect()); 

当用户开始搜索设备的时候,CBluetoothPMPExampleEngine::DiscoverDevicesL()调用CDeviceDiscoverer::DiscoverDevicesL() 函数。所有发现的设备都存储在aDevDataList:
void CDeviceDiscoverer::DiscoverDevicesl(TDeviceDataList *aDevDataList) 

iDiscoveredDeviceCount=0; 
清除现有设备数据:
iDevDataList=aDevDataList; 
iDevDataList->Reset(); 
加载搜索协议:
TProtocolDesc pdesc; 
User::LeaveIfError(iSocketServ->FindProtocol 
(_L("BTLinkManager"), pdesc)); 
Host Resolver 初始化
User::LeaveIfError(iResolver.Open(*iSocketServ, pdesc.iAddrFamily, pdesc.iProtocol)); 
一个远程的地址搜索代表设备搜索的开始,基本的查询判断通过SetIAC(KGIAC) 函数实现。如果使用SetAction(KHostResInquiry|KHostResName) 函数,那么名称和地址搜索将同时进行。增加一个KHostResIgnoreCache 函数,不清除缓存中的数据,那么得到的地址和名称也不是准确的。
iAddr.SetIAC(KGIAC); 
iAddr.SetAction(KHostResInquiry|KHostResName| 
KHostResIgnoreCache); 
iResolver.GetByAddress(iAddr, iEntry, iStatus); 
SetActive(); 

void CDeviceDiscoverer::RunL() 

RHostResolver.GetByAddress(..) 完成后,调用CBluetoothPMPExampleEngine::HandleDeviceDiscoveryCompleteL() 函数。
if ( iStatus != KErrNone ) 

iObserver->HandleDeviceDiscoveryCompleteL(); 

如果列表中存在设备信息,将新的名称和地址添加到记录中。
else 

New device data entry: 
TDeviceData *devData = new (ELeave) TDeviceData(); 
devData->iDeviceName = iEntry().iName; 
devData->iDeviceAddr = 
static_cast<TBTSockAddr>(iEntry().iAddr).BTAddr(); 
devData->iDeviceServicePort = 0; 
增加设备信息,增加搜索到设备的数量。
iDevDataList->Append(devData); 
iDiscoveredDeviceCount++; 
获取下一个设备:
iResolver.Next(iEntry, iStatus); 
等待完成:
SetActive(); 


取消和关闭处理:
void CDeviceDiscoverer::DoCancel() 

iResolver.Close(); 

设备搜索过程返回说明搜索完毕:
void CBluetoothPMPExampleEngine::HandleDeviceDiscoveryCompleteL() 

显示搜索到的设备:
if ( iDevDataList.Count()> 0 ) 

ShowMessageL(_L(“Found devices:\n”), ETrue; 
for (TInt idx=0; idx<(iDevDataList.Count()); idx++) 

TDeviceData *dev = iDevDataList[idx]; 
TBuf<40> name; 
name = dev->iDeviceName; 
name.Append(_L(“\n”)); 
ShowMessageL(name, EFalse); 


else 

If no devices were found: 
ShowMessageL(_L(“\nNo devices found!”), EFalse); 


 3.2.5 RHostResolver 搜索
 使用RHostResolver 函数,开发人员可以编写功能更加强大的设备搜索方式。可以运行可编程搜索(比如在后台阶段性搜索)和搜索设备的可编程连接。
设备完成连接后建立PMP连接。但是需要注意的是:所有的连接都只能连接到一个主机,因为一个设备不能即作为主机有作为客户端(这需要散射网的支持)。
开发人员还可以根据不同类型的应用程序开发与设备搜索相关的用户接口组件和设备选择列表。
但是缺点就是需要运行很多手动的内容(在数据库中搜集数据、向用户显示)。但是,我们建议需要高级查询的时候使用RNotifier。
最后,在搜索以前,首先确定蓝牙设备是否启动(如果需要,可以提示用户启动)。参考8.4.1 。

創作者介紹
創作者 shadow 的頭像
shadow

資訊園

shadow 發表在 痞客邦 留言(0) 人氣()