Table of Contents
参考来源
以及通过Google、百度查到的其他资料
蓝牙
- 提出者 1998年,爱立信、诺基亚、东芝、IBM、Intel。
- 名字由来 蓝牙技术统一了标准不一的短距离无线传输技术,就如同Harald Blatand统一了丹麦和挪威,因此用他的外号Bluetooth来命名这项技术(Blatand翻译为英语时就是Bluetooth)。
- 特点 传输距离10cm~10m,使用频段2.4GHz
- 已经实现的蓝牙协议栈
- BlueZ Linux官方的蓝牙协议栈,上层用socket封装,使用时类似于普通的网络编程。问题是没看见相关的好的文档。
原教程
该教程讲述了如何利用bluez进行简单的蓝牙编程(C、Python)。由于我只用C的,因此这里就只摘取了关于C的部分叙述。
蓝牙编程原理
寻址
- 设备地址(Bluetooth address/device address) 每个蓝牙芯片都有一个独一无二的48位设备地址;在蓝牙通讯的所有层都会使用这个设备地址,而不是像TCP/IP网络那样只在数据链路层使用设备地址在更高层就变为使用IP地址了。
- 设备名(Device Name) 为方便人类区别蓝牙设备,蓝牙设备在设备地址之外提供一个设备名方便人类分辨。不同的蓝牙设备可以有相同的设备名,但是不会有相同的设备地址。
类似于因特网还存在着TCP/IP之外的其他协议(RFC),蓝牙设备也存在着类似的称为Bluetooth Profile的协议。
协议
类比一般的网络编程,蓝牙的RFCOMM协议类似于TCP协议,L2CAP协议类似于UDP协议。
protocol | terminology | reserved/well-known ports | dynamically assigned ports |
---|---|---|---|
TCP | port | 1-1024 | 1025-65535 |
UDP | port | 1-1024 | 1025-65535 |
RFCOMM | channel | none | 1-30 |
L2CAP | PSM | odd numbered 1-4095 | odd numbered 4097-32765 |
- 在L2CAP协议中,端口号被称为Protocol Service Multiplexers,且只能使用1-32767(不含32767)之间的奇数;在RFCOMM协议中,则是用的channels,取值为1-30(含)。
- 由于RFCOMM协议只有30个不同的端口号,因此当同一个蓝牙设备上有7个服务端应用程序的时候就会有大于50%的概率产生端口使用上的冲突。相比于绑定固定的端口号,更好的解决办法是使用Service Discovery Protocol(SDP)。服务端应用程序并不在开发程序的时候就决定端口号,而是在运行时向SDP注册服务,由SDP指定端口号,服务端应用程序在注册时需要提供一些信息,这些信息包括一个128位的UUID。客户端程序通过向SDP服务器(SDP服务的端口号是固定和知道的)查询具有指定UUID的服务来找到相应的服务端应用程序的端口号。
bluez与C
安装BlueZ
我的Ubuntu16.04貌似默认安装了BlueZ,但是还需要安装以下东西:
apt install libbluetooth-dev
查询周围设备
源代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
int main(int argc, char **argv)
{
inquiry_info *ii = NULL;
int max_rsp, num_rsp;
int dev_id, sock, len, flags;
int i;
char addr[19] = { 0 };
char name[248] = { 0 };
dev_id = hci_get_route(NULL);
sock = hci_open_dev( dev_id );
if (dev_id < 0 || sock < 0) {
perror("opening socket");
exit(1);
}
len = 8;
max_rsp = 255;
flags = IREQ_CACHE_FLUSH;
ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info));
num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
if( num_rsp < 0 ) perror("hci_inquiry");
for (i = 0; i < num_rsp; i++) {
ba2str(&(ii+i)->bdaddr, addr);
memset(name, 0, sizeof(name));
if (hci_read_remote_name(sock, &(ii+i)->bdaddr, sizeof(name),
name, 0) < 0)
strcpy(name, "[unknown]");
printf("%s %s\n", addr, name);
}
free( ii );
close( sock );
return 0;
}
编译:
gcc -o simplescan simplescan.c -lbluetooth
值得注意的时,len代表扫描时间为len*1.28秒。
RFCOMM连接
服务器程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
int main(int argc, char **argv)
{
struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
char buf[1024] = { 0 };
int s, client, bytes_read;
socklen_t opt = sizeof(rem_addr);
// allocate socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
// bind socket to port 1 of the first available
// local bluetooth adapter
loc_addr.rc_family = AF_BLUETOOTH;
loc_addr.rc_bdaddr = *BDADDR_ANY;
loc_addr.rc_channel = (uint8_t) 1;
bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
// put socket into listening mode
listen(s, 1);
// accept one connection
client = accept(s, (struct sockaddr *)&rem_addr, &opt);
ba2str( &rem_addr.rc_bdaddr, buf );
fprintf(stderr, "accepted connection from %s\n", buf);
memset(buf, 0, sizeof(buf));
// read data from the client
bytes_read = read(client, buf, sizeof(buf));
if( bytes_read > 0 ) {
printf("received [%s]\n", buf);
}
// close connection
close(client);
close(s);
return 0;
}
客户端程序:
nclude <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
int main(int argc, char **argv)
{
struct sockaddr_rc addr = { 0 };
int s, status;
char dest[18] = "01:23:45:67:89:AB";
// allocate a socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
// set the connection parameters (who to connect to)
addr.rc_family = AF_BLUETOOTH;
addr.rc_channel = (uint8_t) 1;
str2ba( dest, &addr.rc_bdaddr );
// connect to server
status = connect(s, (struct sockaddr *)&addr, sizeof(addr));
// send a message
if( status == 0 ) {
status = write(s, "hello!", 6);
}
if( status < 0 ) perror("uh oh");
close(s);
return 0;
}
- 需要注意的是蓝牙连接使用的是小端序而不是像普通TCP/IP网络那样使用大端序。
- setsockopt这种用于设置TCP连接高级选项的函数对于蓝牙来说是没有意义的。
L2CAP连接
服务器程序:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
int main(int argc, char **argv)
{
struct sockaddr_l2 loc_addr = { 0 }, rem_addr = { 0 };
char buf[1024] = { 0 };
int s, client, bytes_read;
socklen_t opt = sizeof(rem_addr);
// allocate socket
s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
// bind socket to port 0x1001 of the first available
// bluetooth adapter
loc_addr.l2_family = AF_BLUETOOTH;
loc_addr.l2_bdaddr = *BDADDR_ANY;
loc_addr.l2_psm = htobs(0x1001);
bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
// put socket into listening mode
listen(s, 1);
// accept one connection
client = accept(s, (struct sockaddr *)&rem_addr, &opt);
ba2str( &rem_addr.l2_bdaddr, buf );
fprintf(stderr, "accepted connection from %s\n", buf);
memset(buf, 0, sizeof(buf));
// read data from the client
bytes_read = read(client, buf, sizeof(buf));
if( bytes_read > 0 ) {
printf("received [%s]\n", buf);
}
// close connection
close(client);
close(s);
}
客户端程序:
nclude <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
int main(int argc, char **argv)
{
struct sockaddr_l2 addr = { 0 };
int s, status;
char *message = "hello!";
char dest[18] = "01:23:45:67:89:AB";
if(argc < 2)
{
fprintf(stderr, "usage: %s <bt_addr>\n", argv[0]);
exit(2);
}
strncpy(dest, argv[1], 18);
// allocate a socket
s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
// set the connection parameters (who to connect to)
addr.l2_family = AF_BLUETOOTH;
addr.l2_psm = htobs(0x1001);
str2ba( dest, &addr.l2_bdaddr );
// connect to server
status = connect(s, (struct sockaddr *)&addr, sizeof(addr));
// send a message
if( status == 0 ) {
status = write(s, "hello!", 6);
}
if( status < 0 ) perror("uh oh");
close(s);
}
使用SDP
寻找具有指定UUID的服务:
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
int main(int argc, char **argv)
{
uint8_t svc_uuid_int[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xab, 0xcd };
uuid_t svc_uuid;
int err;
bdaddr_t target;
sdp_list_t *response_list = NULL, *search_list, *attrid_list;
sdp_session_t *session = 0;
str2ba( "01:23:45:67:89:AB", &target );
// connect to the SDP server running on the remote machine
session = sdp_connect( BDADDR_ANY, &target, SDP_RETRY_IF_BUSY );
// specify the UUID of the application we're searching for
sdp_uuid128_create( &svc_uuid, &svc_uuid_int );
search_list = sdp_list_append( NULL, &svc_uuid );
// specify that we want a list of all the matching applications' attributes
uint32_t range = 0x0000ffff;
attrid_list = sdp_list_append( NULL, &range );
// get a list of service records that have UUID 0xabcd
err = sdp_service_search_attr_req( session, search_list, \
SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
.
.
解析搜寻结果:
sdp_list_t *r = response_list;
// go through each of the service records
for (; r; r = r->next ) {
sdp_record_t *rec = (sdp_record_t*) r->data;
sdp_list_t *proto_list;
// get a list of the protocol sequences
if( sdp_get_access_protos( rec, &proto_list ) == 0 ) {
sdp_list_t *p = proto_list;
// go through each protocol sequence
for( ; p ; p = p->next ) {
sdp_list_t *pds = (sdp_list_t*)p->data;
// go through each protocol list of the protocol sequence
for( ; pds ; pds = pds->next ) {
// check the protocol attributes
sdp_data_t *d = (sdp_data_t*)pds->data;
int proto = 0;
for( ; d; d = d->next ) {
switch( d->dtd ) {
case SDP_UUID16:
case SDP_UUID32:
case SDP_UUID128:
proto = sdp_uuid_to_proto( &d->val.uuid );
break;
case SDP_UINT8:
if( proto == RFCOMM_UUID ) {
printf("rfcomm channel: %d\n",d->val.int8);
}
break;
}
}
}
sdp_list_free( (sdp_list_t*)p->data, 0 );
}
sdp_list_free( proto_list, 0 );
}
printf("found service record 0x%x\n", rec->handle);
sdp_record_free( rec );
}
sdp_close(session);
}
描述服务:
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
sdp_session_t *register_service()
{
uint32_t service_uuid_int[] = { 0, 0, 0, 0xABCD };
uint8_t rfcomm_channel = 11;
const char *service_name = "Roto-Rooter Data Router";
const char *service_dsc = "An experimental plumbing router";
const char *service_prov = "Roto-Rooter";
uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid;
sdp_list_t *l2cap_list = 0,
*rfcomm_list = 0,
*root_list = 0,
*proto_list = 0,
*access_proto_list = 0;
sdp_data_t *channel = 0, *psm = 0;
sdp_record_t *record = sdp_record_alloc();
// set the general service ID
sdp_uuid128_create( &svc_uuid, &service_uuid_int );
sdp_set_service_id( record, svc_uuid );
// make the service record publicly browsable
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root_list = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups( record, root_list );
// set l2cap information
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
l2cap_list = sdp_list_append( 0, &l2cap_uuid );
proto_list = sdp_list_append( 0, l2cap_list );
// set rfcomm information
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
rfcomm_list = sdp_list_append( 0, &rfcomm_uuid );
sdp_list_append( rfcomm_list, channel );
sdp_list_append( proto_list, rfcomm_list );
// attach protocol information to service record
access_proto_list = sdp_list_append( 0, proto_list );
sdp_set_access_protos( record, access_proto_list );
// set the name, provider, and description
sdp_set_info_attr(record, service_name, service_prov, service_dsc);
.
.
注册服务:
.
.
int err = 0;
sdp_session_t *session = 0;
// connect to the local SDP server, register the service record, and
// disconnect
session = sdp_connect( BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY );
err = sdp_record_register(session, record, 0);
// cleanup
sdp_data_free( channel );
sdp_list_free( l2cap_list, 0 );
sdp_list_free( rfcomm_list, 0 );
sdp_list_free( root_list, 0 );
sdp_list_free( access_proto_list, 0 );
return session;
}
近期评论