蓝牙编程介绍

来源:互联网 发布:淘宝钻展怎么开通 编辑:程序博客网 时间:2024/05/16 04:41

蓝牙介绍

一、 蓝牙简介

是一个短距离无线通信标准,适用于手机、计算机和其他电子设备之间的通信。蓝牙采用分散式网络结构以及快跳频和短包技术,支持点对点及点对多点通信,工作在全球通用的 2.4GHz ISM(即工业、科学、医学)频段。采用时分双工传输方案实现全双工传输。在 Linux 中,通常使用的蓝牙协议栈实现是 BlueZ(其他协议还有很多,NOKIA,BCM 都有开发,这里就不一一列举了)。

蓝牙基本知识:

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

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

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

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

 

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

蓝牙3.02009年推出,传输速度达24Mbps,是蓝牙2.0 的八倍。能轻松用于录像机至高清电视,pc至打印机之间的资料传输。

蓝牙4.02010年推出,传输距离达100m,(3.0传输距离10m)。功耗低,成本低。

 

二、 蓝牙协议简介

 

1HCI协议编程

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,得到所有dongleDevice ID。存放在dl中。  

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

  perror("Can't get device list");

  exit(1);

 }

// 使用HCIGETDEVINFO,得到对应Device IDDongle信息。

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

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

 

 

Ø UPDown 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 protocolSocket,并将此Socketdevice ID=参数dev_idDongle绑定起来。只有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非常有用,它可以实现一切HostModules发送Command的功能。

参数1HCI Socket

参数2Command内容。

参数3:以milliseconds为单位的timeout.

下面详细解释此function和用法:

当应用程序需要向Dongle(对应为一个bind后的Socket)发送Command时,调用此function.

其中,参数一dd对应一个使用hci_open_dev()打开的SocketDongle)。

参数三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部分,statusCommand是否顺利执行), 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;  //CommandID

 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)

 

参数1HCI Socket,使用hci_open_dev()打开的SocketDongle)。

 

参数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)

 

参数1HCI Socket,使用hci_open_dev()打开的SocketDongle)。

 

参数2:读取或设置Name

 

参数3:以milliseconds为单位的timeout.

 

注意:这里的NameIOCTL HCIGETDEVINFO 得到hci_dev_info中的name不同。

 

Ø 得到HCI Version:

 

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

 

Ø 得到已经UPDongle 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不等于参数bdaddrDongle 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 传递回来。

 

参数1dev_id:指定Dongle Device ID。如果此值小于0,则会使用第一个可用的Dongle

 

参数2len: 此次inquiry的时间长度(每增加1,则增加1.25秒时间)

 

参数3nrsp:此次搜索最大搜索数量,如果给0。则此值会取255

 

参数4lap:BDADDRLAP部分,Inquiry时这块值缺省为0X9E8B33.通常使用NULL。则自动设置。

 

参数5ii:存放搜索到Bluetooth Device的地方。给一个存放inquiry_info指针的地址,它会自动分配空间。并把那个空间头地址放到其中。

 

参数6flags:搜索flags.使用IREQ_CACHE_FLUSH,则会真正重新inquiry。否则可能会传回上次的结果。

 

返回值是这次Inquiry到的Bluetooth Device 数目。

 

注意:如果*ii不是自己分配的,而是让hci_inquiry()自己分配的,则需要调用bt_free()来帮它释放空间。

 

Ø 得到指定BDAddrreomte 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.

 

参数3name 长度。

 

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

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

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

1Bus 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

 

2Object Paths

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

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

3InterfacesMethodsSignals

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

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

 

 

 

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

esktop.DBus / org.freedesktop.DBus.Introspectable.Introspect  //查看消息总线对象支持的接口

 

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

esktop.DBus / org.freedesktop.DBus.ListNames  //使用ListNames服务查看system dbus 总线名

 

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

 / org.freedesktop.DBus.Introspectable.Introspect  //列出总线名org.bluez下面的基本服务

 

4dbus-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实例讲解.pdfDbus基础知识.docx

三、 obex知识详解

OBEXObject Exchang的简称,本来是为红外传输制定的协议,但它并不限于特定的底层传输方式,可以运行于blueteethusbtcp/ip其它多种协议之上。OBEX主要是会话层协议,同时也包括应用层部分功能。它可以传输任何对象,在手机中,通常用来传输文件、图片、名片和日程等。OpenOBEX是一套开放源代码的OBEX协议实现,提供clientserver两端的功能。

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操作从客户端发送一个对象到服务端。一般至少含有NameLength两个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可以设置Bit0Bit1Bit0表示退回到上一层目录;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(继续)

  0x200xA0

OKSuccess

0x40(0xC0)

Bad Request(服务端不明白Request)

0x41(0xC1)

Unauthorized(未授权的)

0x43(0xC3)

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

 

 

在frommi2.txt有小米给机顶盒发送文件的打印

 

四、 蓝牙交叉编译

1ST交叉编译

#! /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

 

#编译alsabluez及依赖库

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 },

2HISI编译

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 命令到文件系统下面

8gmake 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,0x88000000,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端。

 

背景知识1Bluetooth设备的状态

 

之前HCI编程时,是用 ioctl(HCIGETDEVINFO)得到某个Device Infohci_dev_info).其中flags当时解释的很简单。其实它存放着Bluetooth Device(例如:USB Bluetooth Dongle)的当前状态:

 

其中,UP,Down状态表示此Device是否启动起来。可以使用ioctl(HCIDEVUP)等修改这些状态。

 

另外:就是Inquiry Scan, PAGE Scan这些状态:

 

Sam在刚开始自己做L2CAP层连接时,使用另一台Linux机器插USB Bluetooth DongleRemote Device。怎么也没法使用inquiry扫描到remote设备,也没法连接remote设备,甚至无法使用l2ping pingremote设备。觉得非常奇怪,后来才发现Remote Device状态设置有问题。没有设置PSCANISCAN

 

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了。

SocketBluetooth

LinuxBluetooth编程,借用了Socket体制。也就是说,BlueZ Kernel部分将Bluetooth协议栈以网络协议的形式添加进网络协议栈,这样极大的方便了用户编程。下面Sam就结合Socket概念将Linux Bluetooth做个研究。

 

1957104日,星期五,苏联发射了人类历史上第一颗人造地球卫星--Sputnik.这标志着人类外太空时代的开始。这颗卫星篮球大小,在发射98分钟后到达运转轨道,可以通过短波40.002MHz收听到它的声音。这也标志着苏联在航天科技领域超过美国。但当时谁能想到,Sputnik的升空竟然促进了TCP/IPInternel的出现。(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)中的协议l2capBTPROTO_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:使用想要建立的Socketprotocol.如果想建立HCI SocketBTPROTO_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 设备想要别人能够连接,也需要将SocketBluetooth地址绑定。

 

山寨机让我们知道了双卡双待,Bluetooth也可以实现这一点。建立一个Socket,只是一个手机,它可以与多个bdaddr绑定。这就是hci0,hci1等等。

 

五:理解网络字序:

 

对于多字节数据,不同的CPU有不同的组织方式,最基本的字节序位:

 

小端(little-endian将低序字节存储在起始位置。

 

大端(big-endian:将高序字节存储在其实位置。

 

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

 

他们之间的转化:

 

htobs(), htonl() 主机到网络

 

ntohl() , ntohs() 网络到主机。

L2CAP编程实例

例一:发送Signaling Packet

 

Signaling Command2Bluetooth实体之间的L2CAP层命令传输。所以得Signaling Command使用CID 0x0001.

 

多个Command可以在一个C-framecontrol frame)中发送。

 

 如果要直接发送Signaling Command.需要建立SOCK_RAW类型的L2CAP连接Socket。这样才有机会自己填充Command CodeIdentifier等。

 

以下是一个发送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 protocolRAW 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

 

例二:任意PSML2CAP连接间数据的传输:

 

此例子中:Serverclient其实是使用网络的概念定义的。

 

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

 

1 0