蓝牙介绍
是一个短距离无线通信标准,适用于手机、计算机和其他电子设备之间的通信。蓝牙采用分散式网络结构以及快跳频和短包技术,支持点对点及点对多点通信,工作在全球通用的 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)。功耗低,成本低。
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的。
D-Bus用于进程间的通信或进程与内核的通信。最基本的D-Bus协议是一对一的通信协议。但在很多情况下,通信的一方是消息总线。消息总线是一个特殊的应用,它同时与多个应用通信,并在应用之间传递消息。其实可以理解为上下文的概念。
Bluez 中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是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操作的格式如下:
回应格式如下:
关于Put操作的更详细的用法将在文件传输部分作解释。 4、 Get操作 Get操作从服务端返回一个对象。 Get操作的格式如下:
回应格式如下:
关于Get操作更详细的用法将在文件传输部分作解释。 5、 Abort操作 Abort操作中断一个多包操作(例如发送一个大文件)。Abort操作可以包含描述中断原因的Description Header。 Abort操作的格式如下:
Abort对应的应该是一个成功的操作(0xA0),指明这个操作已被接收并且服务端已经重新与客户端同步。如果返回的另外的值,客户端应该Disconnect。 6、 SetPath SetPath操作用于切换对方的路径。通常使用一个Name Header用于指定对方路径名称。如果为空,则返回默认目录,通常为根目录 SetPath操作格式如下:
注:Flags可以设置Bit0和Bit1。Bit0表示退回到上一层目录;Bit1表示如果目录不存在就创建一个目录,否则返回一个错误。 回应格式如下:
Header 定义
opcode
ResponseCode
|
在frommi2.txt有小米2 给机顶盒发送文件的打印
#! /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 },
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
在机顶盒启动之后 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 // 录制文件
Ø 修改$(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)
Ø 打开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编程非常重要,它和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
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
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
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
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。