技术开发 频道

symbian利用蓝牙串口服务输出调试信息

  【IT168 技术】终于闲下来了,准备总结下之前Symbian上的一些经验,也算是告一个段落吧,由于工作原因基本上有半年多没有碰symbian了,已经跟不上Nokia的发展了,新的SDK也没有下下来试试,玩玩什么新的功能什么的,惭愧啊,嘿嘿,废话不说了,开始今天的主题,这也是我觉得在任何一个新平台上开发程序最先要解决的问题――调试环境。

  先说下我使用到的symbian输出trace的几种方法,一是把trace直接通过控制台输出到stdout,但是因为屏幕太小看不了几行trace,小程序还能勉强使用,trace多了就很痛苦了,虽然可以加个getchar函数其等待我按键再往下跑,不过这样使用起来还是极其的不方便;二是打到文件里面,也有一个很麻烦的问题,为了获得良好的分析环境,肯定不能在手机上直接看打出来的trace,还是因为屏幕太小,又懒得每次都需要把文件发到电脑上;三是曾经在XX论坛上看到有人使用symbian提供的调试工具好像是GDB(懒的查证了),可以在PC上调试手机上跑的程序,搞了好一阵一直都没有成功过,就放弃了。碰巧当时研究了下蓝牙相关的东西,就想到干脆自己搞个方便点的trace输出功能吧:注册一个蓝牙服务,手机上跑的程序通过这个服务把trace输出到PC上,手机做为服务器,PC做为客户端在程序每次运行时连接一下就可以了,至于PC这边的客户端完全可以用超级终端来代替,这样对我这种写EXE的人来说再好不过了。

  再说下蓝牙服务的相关概念,目前支持蓝牙的手机大多会支持几个标准的蓝牙服务,比如OPP(object push profile),FTP(file transfer profile)什么的,都是两个设备之间用来相互传送资料的,也有一些蓝牙耳机、拨号上网服务,蓝牙允许用户自定义服务,以便对端设备来访问,他们都是工作于蓝牙RFCOMM层之上的,RFCOMM是一个串口仿真协议,这样可以把某一个蓝牙服务虚拟为一个串口方便程序的编写。比如在蓝牙配对完成后,PC首先会去查询对方的SDP(Service Discovery Application Profile)服务,这其中有所有对端(这里就是手机)支持的服务的详细信息,PC得到这个信息后就会显示给用户对方有哪些服务,用户可以自由的选择使用哪些服务。这里举例说明我们的DEBUG服务,PC发现了我们在手机上注册的这个DEBUG串口服务(我们注册的当然可以是串口服务,标准中叫他SPP),将其显示给用户,用户在选择连接,在蓝牙链路连接成功后PC会将其虚拟为一个PC上的串口设备,这样我们就可以通过这个串口给手机通讯了。

  现在看看我们要实现的这个功能,主要就是两个功能,一是创建一个蓝牙服务,能处理pc过来的连接请求,并建立蓝牙连接,断开后继续监听等待下一次连接,第二是提供一个send函数发数据就可以了。所以我将其分为一个父类CBtSvr来处理第一个问题,再写一个BtDbg子类来处理第二个问题。

  先看看CBtSvr需要实现的功能,用一个活动对象来实现

  class CBtSvr : public public CActive

  {

  
public :

  
// 当前服务器的状态

  enum CBtBaseSvrStat {

  
// 空闲状态

  EWaitingToGetConnection,

  
// 监听状态

  EGettingConnection,

  
// 连接状态

  EInConnection,

  } ;

  CBtSvr();

  virtual ~CBtSvr(){} ;

  
// 类似消息处理的主循环

  virtual void RunL();

  
// 启动服务器

  virtual
int StartL();

  
// 关闭服务器

  virtual void CloseL();

  
// 取消当前提交的请求

  virtual void DoCancel();

  
// 返回服务器当先状态当前状态

  inline CBtBaseSvrStat Status(void) { return iStat; };

  protected:

  
// socket 服务器

  RSocketServ sockSvr;

  
// 监听socket

  RSocket listenSock;

  
// 连接 socket

  RSocket connSock;

  
// send状态标志

  TRequestStatus iSendStatus;

  
// 服务的当前状态

  CBtBaseSvrStat iStat;

  TSockXfrLength iLen;

  TInt channelNum;

  protected:

  
// 绑定服务,留给子类实现

  virtual
int BindL(void) = 0;

  
// 监听

  virtual
int ListenL(void);

  
// 接受连接请求

  virtual
int AcceptL(void);

  
// 注册蓝牙服务中的Protocol段

  virtual void BuildProtocolDescriptionL(CSdpAttrValueDES
* aProtocolDescriptor, TInt aPort);

  
// 设置蓝牙服务安全

  void SetSecurityOnChannelL(TBool aAuthentication, TBool aEncryption, TBool aAuthorisation, TInt aChannel);

  
// 注册蓝牙服务

  
int RegieterBlueToothServerL(const TDesC& KServiceName, TInt KSerialClassID);

  
// 绑定蓝牙服务名

  
int BuildSeriVCe(const TDesC& ServiceName, TInt KSerialClassID);

  };

  构造函数,初始化状态并加入活动对象调度器

  CBtSvr::CBtSvr()

  : iStat(EWaitingToGetConnection),CActive(
0)

  {

  CActiveScheduler::Add(this);

  }

  开启蓝牙debug服务

  
int CBtSvr::StartL()

  {

  
// 如果当前状态不对或者已经提交了事件

  
if (iStat != EWaitingToGetConnection || IsActive()) {

  return
-1;

  }

  
// 建立绑定debug服务

  
if (BindL() < 0) {

  return
-1;

  }

  
// 开始监听

  
if (ListenL() < 0) {

  listenSock.Close();

  sockSvr.Close();

  return
-1;

  }

  
// 接受连接

  
if (AcceptL() < 0) {

  listenSock.Close();

  sockSvr.Close();

  return
-1;

  }

  return
0;

  }

  
// 监听函数

  
int CBtSvr::ListenL(void)

  {

  assert(iStat
== EWaitingToGetConnection);

  
// 只支持一个连接

  
if(listenSock.Listen(1) != KErrNone) {

  return
-1;

  }

  return
0;

  }

  
// 提交接受连接请求

  
int CBtSvr::AcceptL(void)

  {

  
if (connSock.Open(sockSvr) != KErrNone) {

  return
-1;

  }

  listenSock.Accept(connSock, iStatus);

  iStat
= EGettingConnection;

  SetActive();

  return
0;

  }

  
// 实现CActive的doCancel函数供取消事件请求时调用

  void CBtSvr::DoCancel()

  {

  
if (!IsActive())

  return;

  switch (iStat) {

  
case EGettingConnection:

  listenSock.CancelAll();

  break;

  
case EInConnection:

  connSock.CancelAll();

  break;

  default:

  break;

  }

  }

  
// 服务器关闭函数

  void CBtSvr::CloseL()

  {

  Cancel();

  iStat
= EWaitingToGetConnection;

  listenSock.Close();

  sockSvr.Close();

  }

  
// 主事件循环

  void CBtSvr::RunL()

  {

  
if (iStatus != KErrNone) {

  
// 出错处理

  switch (iStat) {

  
case EGettingConnection:

  iStat
= EWaitingToGetConnection;

  break;

  
case EInConnection:

  
// 可能是对端断开连接

  iStat
= EWaitingToGetConnection;

  
// 重新提交接受连接事件

  AcceptL();

  break;

  default:

  break;

  }

  }

  
else {

  switch (iStat) {

  
case EGettingConnection:

  
// 连接建立成功

  iStat
= EInConnection;

  break;

  
case EInConnection:

  
// 收到数据不做任何处理

  break;

  default:

  break;

  }

  }

  }

  
// 这个函数是设置蓝牙服务安全,没有仔细研究,simple中搬出来

  void CBtSvr::SetSecurityOnChannelL(TBool aAuthentication,

  TBool aEncryption,

  TBool aAuthorisation,

  TInt aChannel)

  {

  
const TUid KUidBTMobTimeObexAppValue = {0x0};

  
// a connection to the security manager

  RBTMan secManager;

  
// a security session

  RBTSecuritySettings secSettingsSession;

  
// define the security on this port

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

  CleanupClosePushL(secManager);

  User::LeaveIfError(secSettingsSession.Open(secManager));

  CleanupClosePushL(secSettingsSession);

  
// the security settings

  TBTServiceSecurity serviceSecurity(KUidBTMobTimeObexAppValue, KSolBtRFCOMM,
0);

  
//Define security requirements

  serviceSecurity.SetAuthentication(aAuthentication);

  serviceSecurity.SetEncryption(aEncryption);

  serviceSecurity.SetAuthorisation(aAuthorisation);

  serviceSecurity.SetChannelID(aChannel);

  TRequestStatus status;

  secSettingsSession.ReGISterService(serviceSecurity, status);

  User::WaitForRequest(status);
// wait until the security settings are set

  User::LeaveIfError(status.Int());

  CleanupStack::PopAndDestroy();
// secManager

  CleanupStack::PopAndDestroy();
// secSettingsSession

  }

  
// 注册蓝牙串口服务

  
int CBtSvr::RegieterBlueToothServerL(const TDesC& KServiceName, TInt KSerialClassID)

  {

  
// reg the sdp server database

  RSdp sdp;

  RSdpDatabase iSdpDatabase;

  TSdpServRecordHandle iRecord;

  
// sdp服务器连接

  
if(sdp.Connect() != KErrNone) {

  return
-CNSE_SYS_ERR;

  }

  
// 打开数据库

  
if(iSdpDatabase.Open(sdp) != KErrNone) {

  return
-CNSE_SYS_ERR;

  }

  
// 创建一个服务

  iSdpDatabase.CreateServiceRecordL(KSerialClassID, iRecord);

  
// add a Protocol to the record

  CSdpAttrValueDES
* vProtocolDescriptor = CSdpAttrValueDES::NewDESL(NULL);

  CleanupStack::PushL(vProtocolDescriptor);

  
// 设置protocl相关信息

  BuildProtocolDescriptionL(vProtocolDescriptor, channelNum);

  iSdpDatabase.UpdateAttributeL(iRecord,

  KSdpAttrIdProtocolDescriptorList,

  
*vProtocolDescriptor);

  
// Add 0x5 display 设置为可见

  CSdpAttrValueDES
* browseGroupList = CSdpAttrValueDES::NewDESL(NULL);

  CleanupStack::PushL(browseGroupList);

  browseGroupList

  
->StartListL() // List of protocols required for this method

  
->BuildUUIDL(TUUID(TUint16(0x1002)))

  
->EndListL();

  iSdpDatabase.UpdateAttributeL(iRecord, KSdpAttrIdBrowseGroupList,
*browseGroupList);

  CleanupStack::PopAndDestroy(
2);

  
// Add a name to the record,名字

  iSdpDatabase.UpdateAttributeL(iRecord,

  KSdpAttrIdBasePrimaryLanguage
+

  KSdpAttrIdOffsetServiceName,

  KServiceName);

  
// Add a description to the record,描述

  iSdpDatabase.UpdateAttributeL(iRecord,

  KSdpAttrIdBasePrimaryLanguage
+

  KSdpAttrIdOffsetServiceDescription,

  KServiceName);

  iSdpDatabase.Close();

  sdp.Close();

  return
0;

  }

  
// 这里设置蓝牙SDP服务中的协议相关信息

  void CBtSvr::BuildProtocolDescriptionL(CSdpAttrValueDES
* aProtocolDescriptor, TInt aPort)

  {

  TBuf8
<1> channel;

  channel.Append((TChar)aPort);

  aProtocolDescriptor

  
->StartListL()

  
->BuildDESL()

  
->StartListL() // Details of lowest level protocol

  
// L2CAP层之上

  
->BuildUUIDL(KL2CAP)

  
->EndListL()

  
->BuildDESL()

  
->StartListL()

  
->BuildUUIDL(KRFCOMM)

  
// 这里是绑定的RFCOMM的端口号

  
->BuildUintL(channel)

  
->EndListL()

  
->EndListL();

  }

  
int CBtSvr::BuildSerivce(const TDesC& ServiceName, TInt KSerialClassID)

  {

  TBTSockAddr add;

  
int ret;

  _LIT(KRFCOMM,
"RFCOMM");

  
// connect to server

  
if (sockSvr.Connect() != KErrNone) {

  return
-1;

  }

  
// Open a socket

  
if(listenSock.Open(sockSvr, KRFCOMM) != KErrNone) { // ERR_OPEN

  ret
= -1;

  
goto ERR_CONN;

  }

  
// 得到一个可用的RFCOMM端口号

  listenSock.GetOpt(KRFCOMMGetAvailableServerChannel, KSolBtRFCOMM, channelNum);

  add.SetPort(channelNum);

  
if(listenSock.Bind(add) != KErrNone) {

  ret
= -1;

  
goto ERR_OPEN;

  }

  
else {

  
// 设置安全信息

  TRAPD(err, (SetSecurityOnChannelL(EFalse, EFalse, ETrue, channelNum)));

  
if (err != KErrNone) {

  ret
= -1;

  
goto ERR_OPEN;

  }

  
// 注册SDP服务

  TRAP(err, (RegieterBlueToothServerL(ServiceName, KSerialClassID)));

  
if (err != KErrNone) {

  ret
= -1;

  
goto ERR_OPEN;

  }

  return
0;

  }

  ERR_OPEN:

  listenSock.Close();

  ERR_CONN:

  sockSvr.Close();

  return ret;

  }

 

  整个CBtSvr就完成了,其实看着代码比较多,其实就实现了两个功能。蓝牙服务如果有什么不清楚可以多看看SDK自带的那几个示例代码,还有一些nokia提供的相关文档。

  现在再派生出一个子类BtDbg,实现send功能

  class BtDbg : public CBtSvr

  {

  
public :

  ~BtDbg();

  static BtDbg
* NewLC();

  static BtDbg
* NewL();

  void ConstructL();

  
int Send(const TDesC8& aDesc);

  friend
int BtDbg_printf(const char* format, ...);

  
private:

  
int BindL(void);

  };

  BtDbg
* BtDbg::NewLC()

  {

  BtDbg
* result = new (ELeave) BtDbg();

  CleanupStack::PushL( result );

  result
->ConstructL();

  return result;

  }

  BtDbg
* BtDbg::NewL()

  {

  BtDbg
* result = BtDbg::NewLC();

  CleanupStack::Pop( result );

  return result;

  }

  void BtDbg::ConstructL()

  {

  }

  BtDbg::~BtDbg()

  {

  }

  
int BtDbg::BindL(void)

  {

  _LIT(ServiceName,
"Debug");

  return BuildSerivce(ServiceName, 0x1101);

  }

  
int BtDbg::Send(const TDesC8& aDesc)

  {

  
if (iStat != EInConnection) {

  return
-1;

  }

  
// 同步的发送数据

  connSock.Write(aDesc, iSendStatus);

  User::WaitForRequest(iSendStatus);

  
if(iSendStatus != KErrNone) {

  return
-1;

  }

  return
0;

  }

 

  由于比较习惯c的printf来打印调试信息,所以我一般实例化一个BtDbg类,然后再赋给一个全局的指针,写exe程序用全局的东西比较方便。最后封装一个BtDbg_printf来打印trace。

  下面代码中的btDbg是事先实例化好的一个BtDbg对象。

  static char rxBuf[1024 * 10];

  
int BtDbg_printf(const char* format, ...)

  {

  va_list ap;

  
int len = 0;

  va_start(ap, format);

  vsprintf((char
*)rxBuf, format, ap);

  va_end(ap);

  TPtrC8 PBuf
= TPtrC8::TPtrC8((unsigned char*)rxBuf);

  
if (btDbg && btDbg->Status() == CBtSvr::EInConnection) {

  
len = btDbg->Send(pBuf);

  }

  return
len;

  }
0
相关文章