蓝牙介绍
一、 蓝牙简介
是一个短距离无线通信标准,适用于手机、计算机和其他电子设备之间的通信。蓝牙采用分散式网络结构以及快跳频和短包技术,支持点对点及点对多点通信,工作在全球通用的
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格式为
|
|
|
|
|
|
0x80 |
包长度 |
OBEX版本 |
标志 |
最大OBEX包长度 |
可选Header |
注:OBEX版本现在为1.0。
Response格式为:
|
|
|
|
|
|
ResponseCode |
包长度 |
OBEX版本 |
标志 |
最大OBEX包长度 |
可选Header |
对于更多关于Connect的说明请参考IrOBEX
2
、
Disconnect(断开当前会话)
此操作断开OBEX会话。例如断开当前FolderListing Service,然后使用Connect连接到IrMC Sync Service实现同步通讯薄等功能。
Disconnect格式为:
|
|
|
0x81 |
包长度 |
可选Header |
成功的断开会返回0Xa0,拒绝会返回0xD3
3
、
Put操作
|
Put操作的格式如下:
回应格式如下:
关于Put操作的更详细的用法将在文件传输部分作解释。
4
Get操作的格式如下:
回应格式如下:
关于Get操作更详细的用法将在文件传输部分作解释。
5 Abort操作中断一个多包操作(例如发送一个大文件)。Abort操作可以包含描述中断原因的Description Header。 Abort操作的格式如下:
Abort对应的应该是一个成功的操作(0xA0),指明这个操作已被接收并且服务端已经重新与客户端同步。如果返回的另外的值,客户端应该Disconnect。
6
SetPath操作格式如下:
注: 回应格式如下:
Header
opcode
ResponseCode
|
在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
。