蓝牙编程介绍

  • Post author:
  • Post category:其他


蓝牙介绍

一、 蓝牙简介

是一个短距离无线通信标准,适用于手机、计算机和其他电子设备之间的通信。蓝牙采用分散式网络结构以及快跳频和短包技术,支持点对点及点对多点通信,工作在全球通用的

2.4GHz ISM


(即工业、科学、医学)频段。采用时分双工传输方案实现全双工传输。在


Linux


中,通常使用的蓝牙协议栈实现是


BlueZ


(其他协议还有很多,


NOKIA,BCM


都有开发,这里就不一一列举了)。

蓝牙基本知识:

Ø 采用跳频技术,数据包短,抗信号衰减能力强;

Ø 采用快速跳频和前向纠错方案以保证链路稳定,减少同频干扰和远距离传输时的       随机噪声影响;

Ø 使用2.4GHzISM频段,无须申请许可证;

Ø 可同时支持数据、音频、视频信号;

蓝牙

2.0





2004


年推出,虽然传输距离短,传输速度慢,但是已经基本满足数据传输需求。

蓝牙

3.0





2009


年推出,传输速度达


24Mbps


,是蓝牙


2.0


的八倍。能轻松用于录像机至高清电视,


pc


至打印机之间的资料传输。

蓝牙

4.0





2010


年推出,传输距离达


100m


,(


3.0


传输距离


10m


)。功耗低,成本低。

二、 蓝牙协议简介

1




HCI


协议编程

HCI

是沟通上层协议以及程序与底层硬件协议的通道。所以,通过


HCI


发送的


Command


都是上层协议或者应用程序发送给


Bluetooth Dongle


的。它命令


Bluetooth Dongle


(或其中的硬件协议)去做什么何种动作。

Ø 得到

Host


上插入


Dongle


数目以及


Dongle


信息:

分配一个空间给

hci_dev_list_req


。这里面将放所有


Dongle


信息。

struct hci_dev_list_req *dl;

struct hci_dev_req *dr;

struct hci_dev_info di;

int i;

if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) {

perror(“Can’t allocate memory”);

exit(1);

}

dl->dev_num = HCI_MAX_DEV;

dr = dl->dev_req;

//

打开一个


HCI socket.

if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {

perror(“Can’t open HCI socket.”);

exit(1);

}

//

使用


HCIGETDEVLIST,


得到所有


dongle





Device ID


。存放在


dl


中。

if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {

perror(“Can’t get device list”);

exit(1);

}

//

使用


HCIGETDEVINFO


,得到对应


Device ID





Dongle


信息。

di.dev_id = (dr+i)->dev_id;

ioctl(ctl, HCIGETDEVINFO, (void *) &di)


Ø UP




Down Bluetooth Dongle



ioctl(ctl, HCIDEVUP, hdev)

ioctl(ctl, HCIDEVDOWN, hdev)

ctl

:为使用


socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)


打开的


Socket.

hdev: Dongle Device ID.(

所以上面的


Socket


不需要


bind,


因为这边指定了


)

Ø 打开一个

HCI Socket—int hci_open_dev(int dev_id):

这个

function


用来打开一个


HCI Socket


。它首先打开一个


HCI protocol





Socket


,并将此


Socket





device ID=


参数


dev_id





Dongle


绑定起来。只有


bind


后,它才将


Socket


句柄与


Dongle


对应起来。

注意,所有的

HCI Command


发送之前,都需要使用


hci_open_dev


打开并绑定。

Ø 关闭一个

HCI Socket



int hci_close_dev(int dd) //

简单的关闭使用


hci_open_dev


打开的


Socket



Ø 向

HCI Socket


(对应一个


Dongle


)发送


request:

int hci_send_req(int dd, struct hci_request *r, int to)

BlueZ

提供这个


function


非常有用,它可以实现一切


Host





Modules


发送


Command


的功能。

参数

1





HCI Socket



参数

2





Command


内容。

参数

3


:以


milliseconds


为单位的


timeout.

下面详细解释此

function


和用法:

当应用程序需要向

Dongle(


对应为一个


bind


后的


Socket)


发送


Command


时,调用此


function.

其中,参数一

dd


对应一个使用


hci_open_dev


()打开的


Socket





Dongle


)。

参数三

to


则为等待


Dongle


执行并回复命令结果的


timeout.


以毫秒为单位。

参数二

hci_request * r


最为重要


,


首先看它的结构:

struct hci_request {

uint16_t ogf;    //Opcode Group

uint16_t ocf;    //Opcode Command

int      event;  //




Command


产生的


Event


类型。

void     *cparam; //Command

参数

int      clen;    //Command

参数长度

void     *rparam;  //Response

参数

int      rlen;    //Response

参数长度

};

至于

event.


如果设置,它会被


setsockopt


设置于


Socket





1


:得到某个连接的


Policy Setting.

HCI Spec

以及


~/include/net/bluetooth/hci.h


中均可看到,


OGF=OGF_LINK_POLICY(0x02). OCF=OCF_READ_LINK_POLICY(0x0C).

因为这个

Command


用来读取某个


ACL


连接的


Policy Setting


。所以输入参数即为此连接


Handle.

返回参数则包含

3


部分,


status





Command


是否顺利执行),


handle(


连接


Handle)





policy


(得到的


policy


值)

这就又引入了一个新问题,如何得到某个

ACL


连接的


Handle



可以使用

ioctl HCIGETCONNINFO


得到


ACL


连接


Handle



ioctl(dd, HCIGETCONNINFO, (unsigned long) cr)


Connect_handle = htobs(cr->conn_info->handle);

所以完整的过程如下:

struct hci_request HCI_Request;

read_link_policy_cp Command_Param;

read_link_policy_rp Response_Param;

// 1.

得到


ACL Connect Handle

if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0)

{

return -1


}

Connect_handle = htobs(cr->conn_info->handle);

memset(&HCI_Request, 0, sizeof(HCI_Request));

memset(&Command_Param, 0 , sizeof(Command_Param));

memset(&Response_Param, 0 , sizeof(Response_Param));

// 2.

填写


Command


输入参数

Command_Param.handle = Connect_handle;

HCI_Request.ogf = OGF_LINK_POLICY;  //Command




ID

HCI_Request.ocf = OCF_READ_LINK_POLICY; //Command ID

HCI_Request.cparam = &Command_Param;

HCI_Request.clen = READ_LINK_POLICY_CP_SIZE;

HCI_Request.rparam = &Response_Param;

HCI_Request.rlen = READ_LINK_POLICY_RP_SIZE;

if (hci_send_req(dd, &HCI_Request, to) < 0)

{

perror(“\nhci_send_req()”);

return -1;

}

//

如果返回值状态不对

if (Response_Param.status) {

return -1;

}

//

得到当前


policy

*policy = Response_Param.policy;

Ø 几个更基础的

function:

static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) //bdaddr copy

static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2)//bdaddr

比较

Ø 得到指定

Dongle BDAddr



int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to)


参数

1





HCI Socket,


使用


hci_open_dev


()打开的


Socket





Dongle


)。

参数

2


:输出参数,其中会放置


bdaddr.

参数

3


:以


milliseconds


为单位的


timeout.

Ø 读写

Dongle Name



int hci_read_local_name(int dd, int len, char *name, int to)

int hci_write_local_name(int dd, const char *name, int to)

参数

1





HCI Socket,


使用


hci_open_dev


()打开的


Socket





Dongle


)。

参数

2


:读取或设置


Name



参数

3


:以


milliseconds


为单位的


timeout.

注意:这里的

Name





IOCTL HCIGETDEVINFO


得到


hci_dev_info


中的


name


不同。

Ø 得到

HCI Version:

int hci_read_local_version(int dd, struct hci_version *ver, int to)

Ø 得到已经

UP





Dongle BDaddr



int hci_devba(int dev_id, bdaddr_t *bdaddr)


dev_id: Dongle Device ID.

bdaddr:

输出参数,指定


Dongle


如果


UP


, 则放置其


BDAddr



Ø 得到

Dongle Info



int hci_devinfo(int dev_id, struct hci_dev_info *di)

dev_id: Dongle Device ID.

di:




Dongle


信息。

出错返回

-1



注意,这个

Function


的做法与


3.0


的方法完全一致。

Ø 从

hciX


中得到


X



int hci_devid(const char *str)

str:

类似


hci0


这样的字串。

如果

hciX


对应的


Device ID(X)


是现实存在且


UP


。则返回此设备


Device ID



Ø 得到

BDADDR


不等于参数


bdaddr





Dongle Device ID



int hci_get_route(bdaddr_t *bdaddr)

查找

Dongle


,发现


Dongle Bdaddr


不等于参数


bdaddr


的第一个


Dongle


,则返回此


Dongle Device ID



所以,如果

: int hci_get_route(NULL),


则得到第一个可用的


Dongle Device ID



Ø 将

BDADDR


转换为字符串:

int ba2str(const bdaddr_t *ba, char *str)

Ø 将自串转换为

BDADDR



int str2ba(const char *str, bdaddr_t *ba)

Ø inquiry

远程


Bluetooth Device



int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap, inquiry_info **ii, long flags)

hci_inquiry

()用来命令指定的


Dongle


去搜索周围所有


bluetooth device.


并将搜索到的


Bluetooth Device bdaddr


传递回来。

参数

1





dev_id


:指定


Dongle Device ID


。如果此值小于


0


,则会使用第一个可用的


Dongle



参数

2





len:


此次


inquiry


的时间长度(每增加


1


,则增加


1.25


秒时间)

参数

3





nrsp:


此次搜索最大搜索数量,如果给


0


。则此值会取


255



参数

4





lap:BDADDR





LAP


部分,


Inquiry


时这块值缺省为


0X9E8B33.


通常使用


NULL


。则自动设置。

参数

5





ii:


存放搜索到


Bluetooth Device


的地方。给一个存放


inquiry_info


指针的地址,它会自动分配空间。并把那个空间头地址放到其中。

参数

6





flags:


搜索


flags.


使用


IREQ_CACHE_FLUSH


,则会真正重新


inquiry


。否则可能会传回上次的结果。

返回值是这次

Inquiry


到的


Bluetooth Device


数目。

注意:如果

*ii


不是自己分配的,而是让


hci_inquiry()


自己分配的,则需要调用


bt_free


()来帮它释放空间。

Ø 得到指定

BDAddr





reomte device Name:

int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to)

参数

1


:使用


hci_open_dev


()打开的


Socket



参数

2


:对方


BDAddr.

参数

3





name


长度。

参数

4





(out)


放置


name


的位置。

参数

5


:等待时间。

Ø 读取连接的信号强度:

int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)

注意,所有对连接的操作,都会有一个参数,

handle.


这个参数是连接的


Handle


。前面讲过如何得到连接


Handle


的。

2,DBUS

协议


D-Bus


用于进程间的通信或进程与内核的通信。最基本的


D-Bus


协议是一对一的通信协议。但在很多情况下,通信的一方是消息总线。消息总线是一个特殊的应用,它同时与多个应用通信,并在应用之间传递消息。其实可以理解为上下文的概念。


B


luez 中dbus应用:文件系统启一个线程 bluetoothd,比如说配对操作,


应用程序会使用dbus 接口向bluetoothd 发送一个配对的操作,bluetoothd收到此消息之后调用内核相关的配对函数进行配对,内核配对完成之后会发送相应消息到bluetoothd,bluetoothd收到消息之后将此消息反馈给应用。

1



Bus Name


可以把


Bus Name


理解为连接的名称,一个


Bus Name


总是代表一个应用和消息总线的连接。有两种作用不同的


Bus Name


,一个叫公共名(


well-known names


),还有一个叫唯一名(


Unique ConnectionName


)。


公共名提供众所周知的服务。其他应用通过这个名称来使用名称对应的服务。可能有多个连接要求提供同个公共名的服务,即多个应用连接到消息总线,要求提供同个公共名的服务。消息总线会把这些连接排在链表中,并选择一个连接提供公共名代表的服务。可以说这个提供服务的连接拥有了这个公共名。如果这个连接退出了,消息总线会从链表中选择下一个连接提供服务。公共名是由一些圆点分隔的多个小写标志符组成的,例如“org.fmddlmyy.Test”、“org.bluez”。


每个连接都有一个唯一名,当应用连接到消息总线时,消息总线会给每个应用分配一个唯一名。唯一名以“:”开头,“:”后面通常是圆点分隔的两个数字,例如“:1.0”。每个连接都有一个唯一名。在一个消息总线的生命期内,不会有两个连接有相同的唯一名。拥有公众名的连接同样有唯一名,例如在前面的图中,“org.bluez”的唯一名是“:1.0”。有的连接只有唯一名,没有公众名。可以把这些名称称为私有连接,因为它们没有提供可以通过公共名访问的服务。(开发蓝牙的时候只需要了解,蓝牙使用的是公共名。)


通过公共名获取为一名例子


dbus-send –type=method_call –print-reply –system –dest=org.freed


esktop.DBus / org.freedesktop.DBus.GetNameOwner string:org.bluez


2



Object Paths


Bus Name确定了一个应用到消息总线的连接。在一个应用中可以有多个提供服务的对象。这些对象按照树状结构组织起来。每个对象都有一个唯一的路径(Object Paths)。或者说,在一个应用中,一个对象路径标志着一个唯一的对象。


“org.bluez”有一个叫作“/org/bluez/925/hci0”的对象“org.bluez”有多个对象路径。

3




Interfaces





Methods





Signals


通过对象路径,我们找到应用中的一个对象。每个对象可以实现多个接口。例如:“/org/bluez/925/hci0”的


“/TestObj”实现了以下接口:





1,

dbus-send –type=method_call –print-reply –system –dest=org.freed

esktop.DBus / org.freedesktop.DBus.Introspectable.Introspect  //

查看消息总线对象支持的接口

2



dbus-send –type=method_call –print-reply –system –dest=org.freed

esktop.DBus / org.freedesktop.DBus.ListNames  //

使用


ListNames


服务查看


system dbus


总线名

3



dbus-send –type=method_call –print-reply –system –dest=org.bluez

/ org.freedesktop.DBus.Introspectable.Introspect  //

列出总线名


org.bluez


下面的基本服务

4



dbus-send –type=method_call –print-reply –system –dest=org.bluez

/ org.bluez.Manager.ListAdapters      //

使用


system dbus


总线名为


org.bluez


里面的服务

org.bluez.Manager.ListAdapters 查看当前

adapter

5

,可知当前


adapter

object path,

从而可获取当前


adapter


提供的服务

dbus-send –type=method_call –print-reply –system –dest=org.bluez

/org/bluez/915/hci0 org.freedesktop.DBus.Introspectable.Introspect

6

,从而可以使用


dbus


总线对


adapter


操作,比如说获取设备参数,创建配对设备,取消配对等操作,例如获取设备参数

dbus-send –type=method_call –print-reply –system –dest=org.bluez

/org/bluez/915/hci0 org.bluez.Adapter.GetProperties

有兴趣了解

dbus


的可以看一下文档


DBUS


实例讲解


.pdf





Dbus


基础知识


.docx

三、 obex

知识详解

OBEX




Object Exchang


的简称,本来是为红外传输制定的协议,但它并不限于特定的底层传输方式,可以运行于


blueteeth





usb





tcp/ip


其它多种协议之上。


OBEX


主要是会话层协议,同时也包括应用层部分功能。它可以传输任何对象,在手机中,通常用来传输文件、图片、名片和日程等。


OpenOBEX


是一套开放源代码的


OBEX


协议实现,提供


client





server


两端的功能。

OBEX

协议说明

1



Connect(连接)

此操作初始化会话然后设置参数。其Request格式为


Byte 0


Byte 1,2


Byte 3


Byte 4


Byte 5,6


Byte 7 to n

0x80

包长度

OBEX版本

标志

最大OBEX包长度

可选Header

注:OBEX版本现在为1.0。

Response格式为:


Byte 0


Byte 1,2


Byte 3


Byte 4


Byte 5,6


Byte 7 to n

ResponseCode

包长度

OBEX版本

标志

最大OBEX包长度

可选Header

对于更多关于Connect的说明请参考IrOBEX

2



Disconnect(断开当前会话)

此操作断开OBEX会话。例如断开当前FolderListing Service,然后使用Connect连接到IrMC Sync Service实现同步通讯薄等功能。

Disconnect格式为:


Byte 0


Bytes 1,2


Bytes 3 to n

0x81

包长度

可选Header

成功的断开会返回0Xa0,拒绝会返回0xD3

3



Put操作



Put


操作从客户端发送一个对象到服务端。一般


至少


含有


Name





Length


两个


Header





对于文件而言有可能还有


Date/Time Header



Put操作的格式如下:


Byte 0


Bytes 1,2


Bytes 3 to n

0x02(当FinalBit设置时为0x82)

PacketLength包长度

一组Header

回应格式如下:


Byte 0


Bytes 1,2


Bytes 3 to n

ResponseCode(要求继续为0x90;成功为0xA0)

包长度

可选Header

关于Put操作的更详细的用法将在文件传输部分作解释。

4



Get操作


Get


操作从服务端返回一个对象。

Get操作的格式如下:


Byte 0


Bytes 1,2


Bytes 3 to n

0x03

包长度

一组Header

回应格式如下:


Byte 0


Bytes 1,2


Bytes 3 to n

Response Code

包长度

可选Header

关于Get操作更详细的用法将在文件传输部分作解释。

5



Abort操作

Abort操作中断一个多包操作(例如发送一个大文件)。Abort操作可以包含描述中断原因的Description Header。

Abort操作的格式如下:


Byte 0


Bytes 1,2


Bytes 3 to n

0xFF

包长度

可选Header

Abort对应的应该是一个成功的操作(0xA0),指明这个操作已被接收并且服务端已经重新与客户端同步。如果返回的另外的值,客户端应该Disconnect。

6



SetPath


SetPath


操作用于切换对方的路径。通常使用一个


Name Header


用于指定对方路径名称。如果为空,则返回默认目录,通常为根目录

SetPath操作格式如下:


Byte 0


Bytes 1,2


Byte 3


Byte 4


Bytes 5 to n

0x85

包长度

Flags

Constants(常数)

可选Header

注:

Flags


可以设置


Bit0





Bit1





Bit0


表示退回到上一层目录;


Bit1


表示如果目录不存在就创建一个目录,否则返回一个错误

回应格式如下:


Byte 0


Bytes 1,2


Bytes 3 to n

ResponseCode

包长度

可选Response Header

Header

定义


HI


Header


名称


描述

0xC0

Count

连接中用于指名对象的数量。


0x01


Name


对象的名字。一般为文件名。


0x42


Type


对象的类型。例如


text,html,binary,manufacture specific

0x44

0xC4

Time

时间戳。ISO 8601版本

时间戳。4Byte版本(用于兼容)

0x05

Description

对对象的文本描述


0x46


Target


操作的目的服务名

0x47

HTTP

一个HTTP1.x头


0x48


Body


对象的一部分


0x49


End of body


对象的最后一部分

0x4A

Who

OBEX Application标识,用于表明是否是同一个应用。

0xCB

Connection ID

用于OBEX多路连接的标识

0x4C

App.Parameters

扩展的应用层请求和回复信息

0x4D

Auth.Challenge

Authentication digest-challenge

0x4E

Auth.Response

Authentication digest-response

0x4F

Object Class

对象的OBEX对象类

0x10 to 0x2F

Reserved

保留

0x30 to 0x3F

User defined

用户自定义的。

opcode



Opcode(w/high bit set)


定义


意义


0x80 *


Connect


连接

0x81 *

Disconnect

断开连接


0x02(0x82)


Put


发送一个对象


0x03(0x83)


Get


取得一个对象

0x04(0x84)

Reserved

保留的


0x85 *


SetPath


设置路径

0xFF *

Abort

取消当前的操作

0x06到0x0F

Reserved

作为扩展保留

0x10到0x1F

User definable

用户自定义的

ResponseCode


ResponseCode


定义

0x10(0x90)

Continue(继续)




0x20





0xA0




OK





Success

0x40(0xC0)

Bad Request(服务端不明白Request)

0x41(0xC1)

Unauthorized(未授权的)

0x43(0xC3)

Fobidden(禁止——服务器明白Request,但拒绝)



在frommi2.txt有小米

2


给机顶盒发送文件的打印

四、 蓝牙交叉编译

1




ST


交叉编译

#! /bin/bash

#

指定临时安装位置到

/usr/share/bluetooth


,最后会被复制到


install


目录

TMP_PATH=/usr/share/bluetooth

INSTALL_PATH=`pwd`/install

#

清除之间编译的文件,创建安装文件夹

rm -rf install

mkdir install

rm -rf $TMP_PATH/*

rm -rf $INSTALL_PATH/*

mkdir -p $INSTALL_PATH/root

mkdir -p $INSTALL_PATH/etc

mkdir -p $INSTALL_PATH/include

mkdir -p $INSTALL_PATH/usr/bin

mkdir -p $INSTALL_PATH/usr/sbin

mkdir -p $INSTALL_PATH/$TMP_PATH

mkdir -p $INSTALL_PATH/$TMP_PATH/lib

mkdir -p $INSTALL_PATH/$TMP_PATH/share/alsa

mkdir -p $INSTALL_PATH/$TMP_PATH/var

mkdir -p $INSTALL_PATH/$TMP_PATH/etc


指定编译器路径

PATH=$PATH:/opt/STM/STLinux-2.3/devkit/sh4/bin:/opt/STM/ST40R4.4.0:/opt/STM/ST40R4.4.0/bin:/opt/STM/STMCR1.4.0/bin:/opt/STM/STWORKBENCHR4.0.1:/opt/STM/STWORKBENCHR4.0.1/bin

export PATH

#

编译

alsa





bluez


及依赖库

rm -rf expat-2.0.1 && tar xvf expat-2.0.1.tar.gz

cd expat-2.0.1

./configure –host=sh4-linux –prefix=/usr/share/Bluetooth

make -j 20 && make install && cd –

rm -rf zlib-1.2.5 && tar xvf zlib-1.2.5.tar.gz

cd zlib-1.2.5

CC=sh4-linux-gcc ./configure –prefix=/usr/share/Bluetooth

make -j 20 && make install && cd –

rm -rf alsa-lib-1.0.23 && tar xjvf alsa-lib-1.0.23.tar.bz2

cd alsa-lib-1.0.23

./configure –host=sh4-linux –prefix=/usr/share/bluetooth –enable-shared –disable-python –with-versioned=no

make -j 20 && make install && cd –

rm -rf alsa-utils-1.0.23 && tar xjvf alsa-utils-1.0.23.tar.bz2

cd alsa-utils-1.0.23

./configure –host=sh4-linux –prefix=/usr/share/bluetooth  CPPFLAGS=-I/usr/share/bluetooth/include LDFLAGS=-L/usr/share/bluetooth/lib –disable-alsamixer –disable-xmlto –disable-nls

make -j 20 && make install && cd –

rm -rf dbus-1.2.26 && tar xvf dbus-1.2.26.tar.gz

cd dbus-1.2.26

./configure –host=sh4-linux –prefix=/usr/share/bluetooth CPPFLAGS=-I/usr/share/bluetooth/include LDFLAGS=-L/usr/share/bluetooth/lib –with-xml=expat –without-x –enable-selinux=no

make -j 20 && make install && cd –

rm -rf glib-2.24.0 && tar xvjf glib-2.24.0.tar.bz2

cd glib-2.24.0

./configure –host=sh4-linux –prefix=/usr/share/bluetooth CFLAGS=-I/usr/share/bluetooth/include LDFLAGS=-L/usr/share/bluetooth/lib glib_cv_stack_grows=yes glib_cv_uscore=yes ac_cv_func_posix_getpwuid_r=yes ac_cv_func_posix_getgrgid_r=yes

make -j 20 && make install && cd –

rm -rf bluez-4.95 && tar xvf bluez-4.95.tar.gz

cd bluez-4.95

./configure –host=sh4-linux –prefix=/usr/share/bluetooth PKG_CONFIG_PATH=/usr/share/bluetooth/lib/pkgconfig ALSA_CFLAGS=-I/usr/share/bluetooth/include ALSA_LIBS=-L/usr/share/bluetooth/lib –disable-gstreamer –enable-hid2hci –enable-hidd –enable-alsa –enable-audio –enable-service –enable-tools –enable-serial –enable-input –enable-static –enable-shared –enable-test

make -j 20 && make install && cd –

# bluez

安装后的头文件


hic_lib.h


需要修改,用已修改好的替换

cp modify/hci_lib.h install/include/bluetooth/

rm -rf openobex-1.3 && tar xvf openobex-1.3.tar.gz

cd openobex-1.3

./configure –prefix=$TMP_PATH –host=sh4-linux CC=sh4-linux-gcc CFLAGS=-I$TMP_PATH/include LDFLAGS=-L$TMP_PATH/lib –enable-apps  BLUEZ_LIBS=-lbluetooth

#

打开


obex


的蓝牙选项

cp ../modify/obex_config.h config.h

make -j 20 && make install && cd –

cp -fr cfg/* $INSTALL_PATH/


#

创建两个用户



messagebus





lp


#




使用

dbus


需要创建



messagebus


用户,

lp


用户

sed ‘$a messagebus:$1$84bC0OFA$m9ubAM6KGAmarFZOlbvCz.:1000:1000:Linux User,,,:/home/messagebus:/bin/sh’ $INSTALL_PATH/etc/passwd >./tmp

sed ‘$a lp:$1$pytSBKNZ$95Roy6wGH3V444ZOyZwjK0:1001:1001:Linux User,,,:/home/lp:/bin/sh’ ./tmp >$INSTALL_PATH/etc/passwd

sed ‘$a messagebus:x:1000:’ $INSTALL_PATH/etc/group >./tmp

sed ‘$a lp:x:1001:’ ./tmp >$INSTALL_PATH/etc/group


#

拷贝相关文件到


install


目录下

cd $TMP_PATH/bin

cp -lrf dbus-daemon hidd l2ping hcitool sdptool rfcomm dbus-cleanup-sockets dbus-launch dbus-monitor dbus-send dbus-uuidgen aplay arecord $INSTALL_PATH/usr/bin/

cd –

cd $TMP_PATH

cp -rf $TMP_PATH/include/* $INSTALL_PATH/include

cp -rf $TMP_PATH/sbin/* $INSTALL_PATH/usr/sbin

cp -rf $TMP_PATH/lib/* $INSTALL_PATH/$TMP_PATH/lib

cp -rf $TMP_PATH/share/alsa/* $INSTALL_PATH/$TMP_PATH/share/alsa

cp -rf $TMP_PATH/var/* $INSTALL_PATH/$TMP_PATH/var

cp -rf $TMP_PATH/etc/* $INSTALL_PATH/$TMP_PATH/etc

cd –


#

创建


bluez


启动自动化脚本

cd $INSTALL_PATH/etc

echo “rm -rf $TMP_PATH/var/run/dbus/pid” >bluez_init

echo “LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:$TMP_PATH/lib

export LD_LIBRARY_PATH” >>bluez_init

echo “touch $TMP_PATH/var/lib/dbus/machine-id

dbus-uuidgen >$TMP_PATH/var/lib/dbus/machine-id” >>bluez_init

echo “dbus-daemon –config-file=$TMP_PATH/etc/dbus-1/system.conf” >>bluez_init

echo “bluetoothd –udev” >>bluez_init

cd –

rm tmp

编译完成之后还需要将

install


目录下文件挪到


rootfs


里面,以下是在


stapp makefile


里面添加的部分逻辑,基本功能是编译九州添加的逻辑代码,将以上编译的文件挪到


rootfs


里面。

ALSA_DIR    = ../bluetooth

INCLUDE_DIR = ../include

LIB_DIR = ../share/target7162_A27/lib

ROOTFS_DIR = ../share/target7162_A27

BLUEZ_API_DIR_HI    = $(ALSA_DIR)/hi_api

BLUEZ_API_DIR    = $(ALSA_DIR)/bluetooth_utils

bluetooth:

# cd $(ALSA_DIR)/src;tar -zxf bluez_install.tar.gz;chmod 755 -R install;cd –

# @echo “tar -zxf bluez_install.tar.gz  done”

@-cp -rf $(ALSA_DIR)/src/install/include/* $(ALSA_DIR)/include

@-cp -rf $(ALSA_DIR)/src/install/usr/share/bluetooth/lib/* $(ALSA_DIR)/lib

make -C $(BLUEZ_API_DIR) //

九州内部代码

make -C $(BLUEZ_API_DIR_HI) //

九州内部代码

bluez_install: bluetooth

@-cp -rvf $(BLUEZ_API_DIR)/*.h $(INCLUDE_DIR)

@-cp -rvf $(BLUEZ_API_DIR)/lib* $(LIB_DIR)

@-cp -rvf $(BLUEZ_API_DIR)/*.so $(ROOTFS_DIR)/usr/lib

@-cp -rvf $(BLUEZ_API_DIR)/*.a $(ROOTFS_DIR)/usr/lib

@-cp -rvf $(BLUEZ_API_DIR_HI)/bluetooth*.h $(INCLUDE_DIR)

@-cp -rvf $(BLUEZ_API_DIR_HI)/lib* $(LIB_DIR)

@-cp -rvf $(BLUEZ_API_DIR_HI)/*.so $(ROOTFS_DIR)/usr/lib

@-cp -rvf $(BLUEZ_API_DIR_HI)/*.a $(ROOTFS_DIR)/usr/lib

mkdir -p $(INCLUDE_DIR)/alsa_bluez

mkdir -p $(LIB_DIR)/alsa_bluez

@-cp -rf $(ALSA_DIR)/src/install/include/* $(INCLUDE_DIR)/alsa_bluez

@-cp -rf $(ALSA_DIR)/src/install/usr/share/bluetooth/lib/* $(LIB_DIR)/alsa_bluez

@-cp -rf $(ALSA_DIR)/bluetooth_utils/bt_test $(ROOTFS_DIR)/bin

@-cp -rf $(ALSA_DIR)/src/install/etc/* $(ROOTFS_DIR)/etc

# @-cp -rf $(ALSA_DIR)/src/install/kmod/* $(ROOTFS_DIR)/kmod

for aaa in $$(find $(ALSA_DIR)/src/install/usr/share/bluetooth/lib -name “*.a”);do rm -rf $$aaa;done

@-cp -rf $(ALSA_DIR)/src/install/usr/* $(ROOTFS_DIR)/usr

cp ../bluetooth/src/expat-2.0.1/.libs/libexpat.so.1.5.2 ../share/target7162_A27/lib

,在启用

bluez_init


之前还需要对蓝牙残留文件做删除。

以下代码是在

profile


添加的

if [ -f /usr/share/bluetooth/var/run/messagebus.pid ]; then

rm /usr/share/bluetooth/var/run/messagebus.pid

fi

rm -rf /usr/share/bluetooth/var/lib/bluetooth/*

rm -rf /usr/share/bluetooth/var/run/dbus/*

cd /etc

source bluez_init

如果启动的时候出现找不到libbluetooth.so.2,需要做一下操作,其他错误类似

/usr/share/bluetooth/lib # ln -s libbluetooth.so.3.11.3 libbluetooth.so.2

如果有些

dongle


插入之后一直打印


unkown handle


。。。错误,是因为


kernel


版本过低

需要修改

kernel/driver/bluetooth/hci_usb.c

// { USB_DEVICE(0x0a12, 0x0001), .driver_info = HCI_CSR },

{ USB_DEVICE(0x0a12, 0x0001), .driver_info = HCI_BROKEN_ISOC },

2




HISI


编译

Mk.sh

在这里就不重复说了。

. ./655V300DDR256M

如果是下载最新的标准版,需要先

make build


(生成


rootfs_full



cd /home/work/HISI/3716SDK0A1/source/msp/component/alsa/src

. ./mk.sh(生成

alsa/src/install



cd /home/work/HISI/3716SDK0A1

gmake bluez_install (这个只是把

alsa/src/install


里面的东西拷贝到


rootfs_full


里面)

其中需要修改/home/work/HISI/3716SDK0A1 的

makefile

1, 拷贝头文件

2, 拷贝

lib

3, 修改

etc


下面的


passwd  group

Passwd

修改如下

Group

修改如下

4, 在

kmod


里面添加


bluez_init bluetoothd_server

Bluez_init

Bluetooth_server

5, 删除

.a


文件

6, 拷贝

usr


下面的命令 动态库 配置文件到文件系统下面

7, 拷贝

obex_test  agent


命令到文件系统下面

8



gmake rootfs

8, 修改

bin


目录下


rcS_MV300  makefile

Rcs

修改如下

这里的

makefile





ysstb


目录下面的


makefile


,在


gmake all


的时候添加

BT_CFLAGS,BT_LDFLAGS

10

,修改


/home/work/HISI/3716SDK0A1/pub/hi3716mv300/rootbox


,将


.asoundrc


放在根目录下

11

,编译驱动(将驱动部分代码蓝牙编译)

Driver




makefile


修改



link_path


添加

driver$(LOCALIZATION_STRING)/ysbluetooth

同时

ysstb.mak


也需要修改,把蓝牙的相关目录添加进去

12

,在


ysstb





gmake

五、 蓝牙在

HISI


盒子上使用

在机顶盒启动之后

ps


能看到


dbus-daemon





bluetoothd

–udev

进程

Ø 蓝牙扫描

Ø 蓝牙配对

Ø 文件传输

如果发送文件,需要获取远端OBEX Object Push 服务

channel



# sdptool browse 49:F2:1C:4E:66:12

Browsing 49:F2:1C:4E:66:12 …

Service Name: Voiceg ateway

Service RecHandle: 0x10001

Service Class ID List:

“Handsfree Audio Gateway” (0x111f)

“Generic Audio” (0x1203)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 2

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Profile Descriptor List:

“Handsfree” (0x111e)

Version: 0x0105

Service Name: AUDIO Gateway

Service RecHandle: 0x10002

Service Class ID List:

“Headset Audio Gateway” (0x1112)

“Generic Audio” (0x1203)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 1

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Profile Descriptor List:

“Headset” (0x1108)

Version: 0x0100

Service Name: Serial Port0

Service RecHandle: 0x10003

Service Class ID List:

“Serial Port” (0x1101)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 11

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Service Name: Dial-up Networking

Service RecHandle: 0x10004

Service Class ID List:

“Dialup Networking” (0x1103)

“Generic Networking” (0x1201)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 9

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Profile Descriptor List:

“Dialup Networking” (0x1103)

Version: 0x0100

Service Name: Advanced Audio

Service RecHandle: 0x10005

Service Class ID List:

“Audio Source” (0x110a)

Protocol Descriptor List:

“L2CAP” (0x0100)

PSM: 25

“AVDTP” (0x0019)

uint16: 0x100

Profile Descriptor List:

“Advanced Audio” (0x110d)

Version: 0x0100

Service RecHandle: 0x10006

Service Class ID List:

“AV Remote Target” (0x110c)

Protocol Descriptor List:

“L2CAP” (0x0100)

PSM: 23

“AVCTP” (0x0017)

uint16: 0x100

Profile Descriptor List:

“AV Remote” (0x110e)

Version: 0x0100

Service Name: Human interface device

Service Description: Human interface device

Service Provider: Mediatek Inc

Service RecHandle: 0x10007

Service Class ID List:

“Human Interface Device” (0x1124)

Protocol Descriptor List:

“L2CAP” (0x0100)

PSM: 17

“HIDP” (0x0011)

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Profile Descriptor List:

“Human Interface Device” (0x1124)

Version: 0x0100

Service Name: OBEX Object Push

Service RecHandle: 0x10008

Service Class ID List:

“OBEX Object Push” (0x1105)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 4

“OBEX” (0x0008)

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Service Name: OBEX File Transfer

Service RecHandle: 0x10009

Service Class ID List:

“OBEX File Transfer” (0x1106)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 3

“OBEX” (0x0008)

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Service Name: Imaging

Service RecHandle: 0x1000a

Service Class ID List:

“Imaging Responder” (0x111b)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 6

“OBEX” (0x0008)

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Profile Descriptor List:

“Imaging” (0x111a)

Version: 0x0100

Service Name: Imaging referenced Object

Service RecHandle: 0x1000b

Service Class ID List:

“Imaging Referenced Objects” (0x111d)

Protocol Descriptor List:

“L2CAP” (0x0100)

“RFCOMM” (0x0003)

Channel: 7

“OBEX” (0x0008)

Language Base Attr List:

code_ISO639: 0x656e

encoding:    0x6a

base_offset: 0x100

Profile Descriptor List:

“Imaging” (0x111a)

Version: 0x0100

发送文件

# obex_test -b 49:F2:1C:4E:66:12 4

Using Bluetooth RFCOMM transport

OBEX Interactive test client/server.

> c //

连接

Connect OK!

Version: 0x10. Flags: 0x00

> x //

发送文件

PUSH filename>123.wav //

需要发送的文件名字

接收文件

sdptool browse local 查看是否有 OBEX Object Push

如果没有 需要添加

sdptool add OPUSH

obex_test –b   //

接收文件

Ø 蓝牙耳机使用

蓝牙耳机使用需要在

~/


目录下面建立


.asoundrc


文件

内容如下:

pcm.bt_record{

type plug

slave {

pcm “bt_record_hw”

}

}

pcm.bt_record_hw{

type bluetooth

device 70:F1:A1:EE:C5:70

profile voice

}

pcm.bt_play{

type plug

slave {

pcm “bt_play_hw”

}

}

pcm.bt_play_hw{

type bluetooth

device 70:F1:A1:EE:C5:70

}

其中文件中

mac


地址为蓝牙耳机


mac


地址

配对之后

Apply –D bt_play 123.wav    //

播放

Arecord –D bt_record –f S16_LE 123.wav   //

录制文件

备注:

蓝牙

kernel


底层调试

Ø 修改

$(kerneldir)/driver/bluetooth/Kconfig

在文件结尾添加

config CONFIG_BT_HCI_CORE_DEBUG

bool “CONFIG_BT_HCI_CORE_DEBUG”

default y

help

CONFIG_BT_HCI_CORE_DEBUG.

config CONFIG_BT_RFCOMM_DEBUG

bool “CONFIG_BT_RFCOMM_DEBUG”

default y

help

CONFIG_BT_RFCOMM_DEBUG.

config CONFIG_BT_SOCK_DEBUG

bool “CONFIG_BT_SOCK_DEBUG”

default y

help

CONFIG_BT_SOCK_DEBUG.

config CONFIG_BT_HCI_SOCK_DEBUG

bool “CONFIG_BT_SOCK_DEBUG”

default y

help

CONFIG_BT_HCI_SOCK_DEBUG.

config CONFIG_BT_HCIUSB_DEBUG

bool “CONFIG_BT_HCIUSB_DEBUG”

default y

help

CONFIG_BT_HCIUSB_DEBUG.

config CONFIG_BT_L2CAP_DEBUG

bool “CONFIG_BT_L2CAP_DEBUG”

default y

help

CONFIG_BT_L2CAP_DEBUG.

Ø 同时在

hci_core.c


或者


hci_usb.c


或者其他蓝牙相关文件里面加上以下代码

#undef  BT_DBG

#undef  BT_ERR

#define BT_DBG(fmt, arg…) printk(“%s: ” fmt “\n” , __FUNCTION__ , ## arg)

#define BT_ERR(fmt, arg…) printk(“%s: ” fmt “\n” , __FUNCTION__ , ## arg)

HISI NFS

调试

Ø 打开

NFS


挂载文件系统功能

注释3716SDK0A1\Makefile

中的


603


行,因为每次编译


HISI


都会通过


603


使用默认的


.config

进入Y:\work\HISI\bluetooth\3716SDK0A1\source\osdrv\kernel\linux-2.6.35

Make menuconfig

修改


kernel


配置

Networking options

下面

Network file systems

下面

然后开机启动盒子进入

fastboot,


输入以下命令,然后就可以方面的使用


NFS


文件系统了

set bootargs mem=128M console=ttyAMA0,115200 root=/dev/nfs nfsroot=192.168.4.146:/opt/nfs/rootbox ip=192.168.4.235 mmz=ddr,0,0×88000000,128M DmxPoolBufSize=0x200000 LogBufSize=0x80000 mtdparts=hinand:768K(fastboot),256K(bootargs),1M(stbinfo),8M(loader),8M(loaderbak),5M(kernel),50M(rootfs),35M(elf),8M(ui),8M(flashdata),256K(baseparam),1M(logo),2M(fastplay),-(others)

L2CAP

编程方法

L2CAP

编程非常重要,它和


HCI


基本就是


Linux Bluetooth


编程的基础了。几乎所有协议的连接,断连,读写都是用


L2CAP


连接来做的。(但是


JZ


没有使用到


L2CAP


。。。有兴趣的同事可以研究一下为什么重要)

1.

创建


L2CAP Socket



socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);

domain=PF_BLUETOOTH, type

可以是多种类型。


protocol=BTPROTO_L2CAP.

2.

绑定:

// Bind to local address

memset(&addr, 0, sizeof(addr));

addr.l2_family = AF_BLUETOOTH;

bacpy(&addr.l2_bdaddr, &bdaddr); //bdaddr

为本地


Dongle BDAddr

if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {

perror(“Can’t bind socket”);

goto error;

}

3.

连接

memset(&addr, 0, sizeof(addr));

addr.l2_family = AF_BLUETOOTH;

bacpy(addr.l2_bdaddr, src);

addr.l2_psm = xxx;

if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {

perror(“Can’t connect”);

goto error;

}

注意:

struct sockaddr_l2 {

sa_family_t l2_family;  //

必须为


AF_BLUETOOTH

unsigned short l2_psm;  //

与前面


PSM


对应


,


这一项很重要

bdaddr_t l2_bdaddr;     //Remote Device BDADDR

unsigned short l2_cid;

};

4.

发送数据到


Remote Device



send()




write()


都可以。

5.

接收数据:

revc()




read()

以下为实例:

注:在

Bluetooth


下,主动去连接的一端作为主机端。被动等别人连接的作为


Client


端。

背景知识

1





Bluetooth


设备的状态

之前

HCI


编程时,是用


ioctl(HCIGETDEVINFO)


得到某个


Device Info





hci_dev_info).


其中


flags


当时解释的很简单。其实它存放着


Bluetooth Device


(例如:


USB Bluetooth Dongle


)的当前状态:

其中,

UP,Down


状态表示此


Device


是否启动起来。可以使用


ioctl(HCIDEVUP)


等修改这些状态。

另外:就是

Inquiry Scan, PAGE Scan


这些状态:

Sam

在刚开始自己做


L2CAP


层连接时,使用另一台


Linux


机器插


USB Bluetooth Dongle





Remote Device


。怎么也没法使用


inquiry


扫描到


remote


设备,也没法连接


remote


设备


,


甚至无法使用


l2ping ping





remote


设备。觉得非常奇怪,后来才发现


Remote Device


状态设置有问题。没有设置


PSCAN





ISCAN



Inquiry Scan

状态表示设备可被


inquiry. Page Scan


状态表示设备可被连接。

#hciconfig hci0 iscan

#hciconfig hci0 pscan

或者:

#hciconfig hci0 piscan

就可以设置为

PSCAN


或者


iSCAN


状态了。

编程则可以使用

ioctl(HCISETSCAN) . dev_opt = SCAN_INQUIRY;dr.dev_opt = SCAN_PAGE;dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY;

则可以

inquiry


或者


connect


了。


Socket




Bluetooth

Linux




Bluetooth


编程,借用了


Socket


体制。也就是说,


BlueZ Kernel


部分将


Bluetooth


协议栈以网络协议的形式添加进网络协议栈,这样极大的方便了用户编程。下面


Sam


就结合


Socket


概念将


Linux Bluetooth


做个研究。

1957




10





4


日,星期五,苏联发射了人类历史上第一颗人造地球卫星


–Sputnik.


这标志着人类外太空时代的开始。这颗卫星篮球大小,在发射


98


分钟后到达运转轨道,可以通过短波


40.002MHz


收听到它的声音。这也标志着苏联在航天科技领域超过美国。但当时谁能想到,


Sputnik


的升空竟然促进了


TCP/IP





Internel


的出现。


(Sam


:不知道朝鲜那个轨道高度几百米的卫星会促成什么出现,嘿嘿


)


。被


Sputnik


所刺激的美国总统艾森豪威尔五星上将积极推动


ARPA


。又因为美国政府为了公平起见,每次采购计算机时都从不同设备制造商处购买。大家很快发现,各个计算机无法兼容。


1962


年,


Licklider


提出:各个计算机高度自治,但他们也应该能够相互通讯。这就是


ARPA


网,它成为


Internel


的前身。

一:理解

Socket



在使用手机与女朋友联系时,必须用手机拨她的号码,然后心情坎坷的等待她的应答。当双方通话时,就建立了一个具有两个端点的通信线路。

Linux

中的


Socket


与电话非常相似。具体问题,稍后再分析。

二:

Socket


域(


domain


),类型(


type


),协议(


protoclo


)以及


Bluetooth


中的具体使用:

Berkeley

小组在构思


BSD Socket


时,


TCP/IP


协议也还处在发展之中,其他一些很有竞争力的协议如


X.25


等也在发展,其它很多协议还在构思与研究阶段(


Bluetooth


还没出生)。为了使


Socket


可以应用于各种不同协议,


domain


的作用就在于此。

domain

指出想要使用的协议族。

不得不佩服

Berkeley


小组的前瞻力。他们考虑在指定


Socket


时,可能还需要进一步的细分类目:

1.

某个协议族(


Domain


)中的一个或多个协议。

2.

某个协议中的一个或多个地址格式。

这个规则在

TCP/IP


等协议栈时并不明显,因为某个协议族只有同一种地址格式。但在


Bluetooth


中则非常有用。

protocol

则用来指出在此协议族中的具体某个协议。

虽然在

TCP/IP


协议栈中,因为协议族中某个


type


的协议栈只有一种,所以此项为


0


,但


Bluetooth


中,这一项则非常有用。

type

用来指出此协议族中的具体协议的


Socket


类型为何种:


SOCK_STREAM,SOCK_DGRAM,SOCK_SEQPACKET,SOCK_RAW.

三:

Socket


地址:

每一种通信协议都对网络地址格式作了明确规定。协议族(

Domain





+


协议(


protocol


)的作用就是指明使用哪种地址类型。

BSD Socket

是在


ANSI C


标准被采纳之前开发的,所以没有使用(


void*)


数据类型来接收结构化的地址。


BSD


的解决方案是定义了一个通用的地址结构:

struct sockaddr

{

sa_family_t sa_family;  //

地址族

char sa_data[14];   //

地址数据

};

sa_family

长度


2


字节,用来存放地址族。

sa_data

长度


14


字节,用来存放具体的协议的地址数据。

如果是用

AF_INET(IPV4),


则它的地址类型


sockaddr_in


如下,刚好与


struct sockaddr


对应

struct sockaddr_in

{

sa_family_t sin_family;    //

地址族

uint16_t sip_port;         //

端口

struct in_addr sin_addr;   //Internel

地址

unsigned char sin_zero[8]; //

占位字节

};

如果是用

Bluetooth


协议族(


PF_BLUETOOTH


)中的协议


l2cap





BTPROTO_L2CAP),


则地址格式如下:

struct sockaddr_l2

{

sa_family_t l2_family;  //

地址族

unsigned short l2_psm;  //PSM

bdaddr_t l2_bdaddr;     //Bluetooth

地址

unsigned short l2_cid;

};

四:

Bluetooth Socket


的建立和地址绑定:

int socket(int domain, int type, int protocol);

domain:

使用


PF_BLUETOOTH



protocol:

使用想要建立的


Socket





protocol.


如果想建立


HCI Socket





BTPROTO_HCI





L2cap:BTPROTO_L2CAP

type:SOCK_SEQPACKET,




Packet


为单位读取。


SOCK_SAW:


原始


Socket



int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);



socket


与某个地址绑定。

嘿嘿,接着前面

Socket


与手机的话题,建立一个


Socket


。就相当于是一个手机,地址,则相当于手机号码。

一个手机想要别人打进来,就需要让别人知道电话号码。 而一个

Bluetooth


设备想要别人能够连接,也需要将


Socket





Bluetooth


地址绑定。

山寨机让我们知道了双卡双待,

Bluetooth


也可以实现这一点。建立一个


Socket


,只是一个手机,它可以与多个


bdaddr


绑定。这就是


hci0,hci1


等等。

五:理解网络字序:

对于多字节数据,不同的

CPU


有不同的组织方式,最基本的字节序位:

小端(

little-endian





:


将低序字节存储在起始位置。

大端(

big-endian





:


将高序字节存储在其实位置。

Intel CPU

使用小端。


Motorola





CPU


使用大端,网络上传输数据的标准顺序为大端。

他们之间的转化:

htobs

(),


htonl()


主机到网络

ntohl() , ntohs()

网络到主机。


L2CAP

编程实例

例一:发送

Signaling Packet



Signaling Command




2





Bluetooth


实体之间的


L2CAP


层命令传输。所以得


Signaling Command


使用


CID 0x0001.

多个

Command


可以在一个


C-frame





control frame


)中发送。

如果要直接发送

Signaling Command.


需要建立


SOCK_RAW


类型的


L2CAP


连接


Socket


。这样才有机会自己填充


Command Code





Identifier


等。

以下是一个发送

signaling Command


以及接收


Response


的简单例子:

int main(int argc, char** argv)

{

int l2_sck = 0;

int iRel  = 0;

struct sockaddr_l2 local_l2_addr;

struct sockaddr_l2 remote_l2_addr;

char str[24] ={0};

int len = 0;

int size = 50;

char* send_buf;

char* recv_buf;

int i = 0;

int id = 1; //

不要为


0

send_buf = malloc(L2CAP_CMD_HDR_SIZE + size);

recv_buf = malloc(L2CAP_CMD_HDR_SIZE + size);

if(argc < 2)

{

printf(“\n%s <bdaddr>\n”, argv[0]);

exit(0);

}

// create l2cap raw socket

l2_sck = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); //

创建


L2CAP protocol





RAW Packet

if(l2_sck < 0)

{

perror(“\nsocket:”);

return -1;

}

//bind

memset(&local_l2_addr, 0, sizeof(struct sockaddr_l2));

local_l2_addr.l2_family = PF_BLUETOOTH;

bacpy(&local_l2_addr.l2_bdaddr , BDADDR_ANY);

iRel = bind(l2_sck, (struct sockaddr*) &local_l2_addr, sizeof(struct sockaddr_l2));

if(iRel < 0)

{

perror(“\nbind()”);

exit(0);

}

//connect

memset(&remote_l2_addr, 0 , sizeof(struct sockaddr_l2));

remote_l2_addr.l2_family = PF_BLUETOOTH;

//printf(“\nConnect to %s\n”, argv[1]);

str2ba(argv[1], &remote_l2_addr.l2_bdaddr);

iRel = connect(l2_sck, (struct sockaddr*)&remote_l2_addr, sizeof(struct sockaddr_l2));

if(iRel < 0)

{

perror(“\nconnect()”);

exit(0);

}

//get local bdaddr

len = sizeof(struct sockaddr_l2);

memset(&local_l2_addr, 0, sizeof(struct sockaddr_l2));

//

注意,


getsockname


()参数三是一个输入输出参数。输入时,为参数二的总体长度。输出时,

//

为实际长度。

iRel = getsockname(l2_sck, (struct sockaddr*) &local_l2_addr, &len);

if(iRel < 0)

{

perror(“\ngetsockname()”);

exit(0);

}

ba2str(&(local_l2_addr.l2_bdaddr), str);

//printf(“\nLocal Socket bdaddr:[%s]\n”, str);

printf(“l2ping: [%s] from [%s](data size %d) …\n”, argv[1], str, size);

for (i = 0; i < size; i++)

send_buf[L2CAP_CMD_HDR_SIZE + i] = ‘A’;

l2cap_cmd_hdr *send_cmd = (l2cap_cmd_hdr *) send_buf;

l2cap_cmd_hdr *recv_cmd = (l2cap_cmd_hdr *) recv_buf;

send_cmd->ident = id;  //

如上图所示,这一项为此


Command Identifier

send_cmd->len   = htobs(size);

send_cmd->code = L2CAP_ECHO_REQ;  //

如上图所示,此项为


Command code.


这项定为:

//Echo Request

。对端会发送


Response


回来。


code=L2CAP_ECHO_RSP

while(1)

{

send_cmd->ident = id;

if(send(l2_sck, send_buf, size + L2CAP_CMD_HDR_SIZE, 0) <= 0)

{

perror(“\nsend():”);

}

while(1)

{

if(recv(l2_sck, recv_buf, size + L2CAP_CMD_HDR_SIZE, 0) <= 0)

{

perror(“\nrecv()”);

}

if (recv_cmd->ident != id)

continue;

if( recv_cmd->code == L2CAP_ECHO_RSP)

{

//printf(“\nReceive Response Packet.\n”);

printf(“%d bytes from [%s] id %d\n”, recv_cmd->len, argv[1], recv_cmd->ident);

break;

}

}

sleep(1);

id ++;

}

close(l2_sck);

return 0;

}

所以说,如果想要发送接收

signaling Command


。只需要建立


l2cap RAW socket.


并按规则填充


command id, command code


等。就可以接收发送了。

Command Code:

这个值放在


l2cap.h


中。

#define L2CAP_COMMAND_REJ 0x01

#define L2CAP_CONN_REQ  0x02

#define L2CAP_CONN_RSP  0x03

#define L2CAP_CONF_REQ  0x04

#define L2CAP_CONF_RSP  0x05

#define L2CAP_DISCONN_REQ 0x06

#define L2CAP_DISCONN_RSP 0x07

#define L2CAP_ECHO_REQ  0x08

#define L2CAP_ECHO_RSP  0x09

#define L2CAP_INFO_REQ  0x0a

#define L2CAP_INFO_RSP  0x0b

例二:任意

PSM





L2CAP


连接间数据的传输:

此例子中:

Server





client


其实是使用网络的概念定义的。

server

用来监听指定


PSM


的连接,并监听数据。同时,利用


poll


来查看


peer


是否断掉了。

Server


#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <stdlib.h>

#include <poll.h>

#include <bluetooth/bluetooth.h>

#include <bluetooth/hci.h>

#include <bluetooth/hci_lib.h>

#include <bluetooth/l2cap.h>

void * Read_thread(void* pSK);

int main(int argc, char** argv)

{

int iRel = 0;

int sk = 0;

struct sockaddr_l2 local_addr;

struct sockaddr_l2 remote_addr;

int len;

int nsk = 0;

pthread_t nth = 0;

struct l2cap_options opts;

int optlen = 0;

int slen = 0;

char str[16] = {0};

if(argc < 2)

{

printf(“\nUsage:%s psm\n”, argv[0]);

exit(0);

}

// create l2cap socket

sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);  //

发送数据,使用


SOCK_SEQPACKET


为好

if(sk < 0)

{

perror(“\nsocket():”);

exit(0);

}

//bind

local_addr.l2_family = PF_BLUETOOTH;

local_addr.l2_psm = htobs(atoi(argv[argc -1]));  //last psm

bacpy(&local_addr.l2_bdaddr, BDADDR_ANY);

iRel = bind(sk, (struct sockaddr *)&local_addr, sizeof(struct sockaddr));

if(iRel < 0)

{

perror(“\nbind()”);

exit(0);

}

//get opts

// in mtu




out mtu.


每个包的最大值

memset(&opts, 0, sizeof(opts));

optlen = sizeof(opts);

getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen);

printf(“\nomtu:[%d]. imtu:[%d]. flush_to:[%d]. mode:[%d]\n”, opts.omtu, opts.imtu, opts.flush_to, opts.mode);

//set opts. default value

opts.omtu = 0;

opts.imtu = 672;

if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0)

{

perror(“\nsetsockopt():”);

exit(0);

}

//listen

iRel = listen(sk, 10);

if(iRel < 0)

{

perror(“\nlisten()”);

exit(0);

}

len = sizeof(struct sockaddr_l2);

while(1)

{

memset(&remote_addr, 0, sizeof(struct sockaddr_l2));

nsk = accept(sk, (struct sockaddr*)(&remote_addr), &len);

if(nsk < 0)

{

perror(“\naccept():”);

continue;

}

ba2str(&(remote_addr.l2_bdaddr), str);

printf(“\npeer bdaddr:[%s].\n”, str);  //

得到


peer


的信息

iRel = pthread_create(&nth, NULL, Read_thread, &nsk);

if(iRel != 0)

{

perror(“pthread_create():”);

continue;

}

pthread_detach(nth);  //

分离之

}

return 0;

}

void * Read_thread(void* pSK)

{

//struct pollfd fds[10];

struct   pollfd   fds[100];

char buf[1024] = {0};

int iRel = 0;

int exit_val = 0;

//fds[0].fd = *(int*)pSK;

//fds[0].events = POLLIN | POLLHUP;

fds[0].fd   =   (int)(*(int*)pSK);

fds[0].events   =   POLLIN   |   POLLHUP;

while(1)

{

if(poll(fds, 1, -1) < 0)

{

perror(“\npoll():”);

}

if(fds[0].revents & POLLHUP)

{

//hang up

printf(“\n[%d] Hang up\n”, *(int*)pSK);

close(*(int*)pSK);

pthread_exit(&exit_val);

break;

}

if(fds[0].revents & POLLIN)

{

memset(buf, 0 , 1024);

//read data

iRel = recv(*(int*)pSK, buf, 572, 0);

//printf(“\nHandle[%d] Receive [%d] data:[%s]”, *(int*)pSK, iRel, buf);

}

}

return 0;

}

client:

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <unistd.h>

#include <bluetooth/bluetooth.h>

#include <bluetooth/hci.h>

#include <bluetooth/hci_lib.h>

#include <bluetooth/l2cap.h>

int main(int argc, char** argv)

{

int sk;

int i = 0;

char buf[24] = “Sam is Good Guy!”;

struct sockaddr_l2 local_addr;

struct sockaddr_l2 remote_addr;

int iRel = 0;

if(argc < 3)

{

printf(“\nUsage:%s <bdaddr> <PSM>\n”, argv[0]);

exit(0);

}

sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);

if(sk < 0)

{

perror(“\nsocket():”);

exit(0);

}

//bind. bluetooth

好像不许有无名


Socket

local_addr.l2_family = PF_BLUETOOTH;

bacpy(&local_addr.l2_bdaddr, BDADDR_ANY);

iRel = bind(sk, (struct sockaddr *)&local_addr, sizeof(struct sockaddr));

if(iRel < 0)

{

perror(“\nbind()”);

exit(0);

}

memset(&remote_addr, 0, sizeof(struct sockaddr_l2));

remote_addr.l2_family = PF_BLUETOOTH;

str2ba(argv[1], &remote_addr.l2_bdaddr);

remote_addr.l2_psm = htobs(atoi(argv[argc -1]));

connect(sk, (struct sockaddr*)&remote_addr, sizeof(struct sockaddr_l2));

for(i = 0; i < 60; i++)

{

iRel = send(sk, buf, strlen(buf)+1, 0);

printf(“Send [%d] data\n”, strlen(buf)+1);

sleep(1);

}

close(sk);

return 0;

}

注意:

1.




Linux


网络编程中


,


主动发起连接方,因为不关心地址具体是什么,所以可以作为无名


socket,


也就是说可以不


bind.





Bluetooth


则不可以,一定需要


bind.

2. poll

可以查出连接断连,但需要注意:断开的


revent


值为:


11001B


。也就是说:


POLLIN | POLLERR |POLLHUP



3.

被连接一方,一定要指定


PSM