和菜鸟一起学linux之bluez学习记录2
這里主要摘取對(duì)于hci,l2cap,sdp和rfcomm的一些應(yīng)用編程。
?
關(guān)于hci
?
一、HCI層協(xié)議概述
?
1、HCI Command Packets
詳見(jiàn)bluez源碼:lib/hci.h
/* Link Control */ #define OGF_LINK_CTL 0x01 #define OCF_INQUIRY 0x0001 #define OCF_INQUIRY_CANCEL 0x0002 #define OCF_PERIODIC_INQUIRY 0x0003 #defineOCF_EXIT_PERIODIC_INQUIRY 0x0004 #define OCF_CREATE_CONN 0x0005 #define OCF_DISCONNECT 0x0006 #define OCF_ADD_SCO 0x0007 ……/* Link Policy */ #define OGF_LINK_POLICY 0x02 #define OCF_HOLD_MODE 0x0001 #define OCF_SNIFF_MODE 0x0003 #define OCF_EXIT_SNIFF_MODE 0x0004 #define OCF_PARK_MODE 0x0005 #define OCF_EXIT_PARK_MODE 0x0006 #define OCF_QOS_SETUP 0x0007 #define OCF_ROLE_DISCOVERY 0x0009 #define OCF_SWITCH_ROLE 0x000B #define OCF_READ_LINK_POLICY 0x000C #defineOCF_WRITE_LINK_POLICY 0x000D #defineOCF_READ_DEFAULT_LINK_POLICY 0x000E #defineOCF_WRITE_DEFAULT_LINK_POLICY 0x000F #defineOCF_FLOW_SPECIFICATION 0x0010 #define OCF_SNIFF_SUBRATING 0x0011/* Host Controller andBaseband */ #define OGF_HOST_CTL 0x03 #define OCF_SET_EVENT_MASK 0x0001 #define OCF_RESET 0x0003 #define OCF_SET_EVENT_FLT 0x0005 #define OCF_FLUSH 0x0008 #define OCF_READ_PIN_TYPE 0x0009 #define OCF_WRITE_PIN_TYPE 0x000A #defineOCF_CREATE_NEW_UNIT_KEY 0x000B #defineOCF_READ_STORED_LINK_KEY 0x000D #defineOCF_WRITE_STORED_LINK_KEY 0x0011 #defineOCF_DELETE_STORED_LINK_KEY 0x0012 #defineOCF_CHANGE_LOCAL_NAME 0x0013 #define OCF_READ_LOCAL_NAME 0x0014 #defineOCF_READ_CONN_ACCEPT_TIMEOUT 0x0015 #defineOCF_WRITE_CONN_ACCEPT_TIMEOUT 0x0016 #defineOCF_READ_PAGE_TIMEOUT 0x0017 ……/* InformationalParameters */ #define OGF_INFO_PARAM 0x04 #defineOCF_READ_LOCAL_VERSION 0x0001 #defineOCF_READ_LOCAL_COMMANDS 0x0002 #defineOCF_READ_LOCAL_FEATURES 0x0003 #defineOCF_READ_LOCAL_EXT_FEATURES 0x0004 #define OCF_READ_BUFFER_SIZE 0x0005 #defineOCF_READ_COUNTRY_CODE 0x0007 #define OCF_READ_BD_ADDR 0x0009 #define OCF_READ_DATA_BLOCK_SIZE 0x000A/* Status params */ #define OGF_STATUS_PARAM 0x05 #defineOCF_READ_FAILED_CONTACT_COUNTER 0x0001 #defineOCF_RESET_FAILED_CONTACT_COUNTER 0x0002 #defineOCF_READ_LINK_QUALITY 0x0003 #define OCF_READ_RSSI 0x0005 #define OCF_READ_AFH_MAP 0x0006 #define OCF_READ_CLOCK 0x0007 #defineOCF_READ_LOCAL_AMP_INFO 0x0009 #defineOCF_READ_LOCAL_AMP_ASSOC 0x000A #defineOCF_WRITE_REMOTE_AMP_ASSOC 0x000B
2、HCI ACL Data Packets
?
?
3、HCI synchronous (SCO and eSCO) DataPackets
?
?
4、HCI Event Packet
?
#define EVT_INQUIRY_COMPLETE 0x01 #define EVT_INQUIRY_RESULT 0x02 #define EVT_CONN_COMPLETE 0x03 #define EVT_CONN_REQUEST 0x04 #define EVT_DISCONN_COMPLETE 0x05 #define EVT_AUTH_COMPLETE 0x06 #defineEVT_REMOTE_NAME_REQ_COMPLETE 0x07 #define EVT_ENCRYPT_CHANGE 0x08 ……
?
HCI Event分3種:Command complete Event,Command States Event,Command Subsequently Completed.
1)、Command complete Event: 如果Host發(fā)送的Command可以立刻有結(jié)果,則會(huì)發(fā)送此類
???? Event。也就是說(shuō),如果發(fā)送的Command只與本地Modules有關(guān),不與remote設(shè)備打??
???? 交道,則使用Command complete Event。例如:HCI_Read_Buffer_Size.
2)、Command States Event:如果Host發(fā)送的Command不能立刻得知結(jié)果,則發(fā)送此類???
???? Event。Host發(fā)送的Command執(zhí)行要與Remote設(shè)備打交道,則必然無(wú)法立刻得知結(jié)
???? 果,所以會(huì)發(fā)送Command States Event.例如:
?HCI Connect。
3)、Command SubsequentlyCompleted:Command延后完成Event。例如:連接已建立。
?
?
例子:remote namerequest
?
從這里可以看出,如果Host發(fā)送的Command是與Remote device有關(guān)的,則會(huì)先發(fā)送Command States Event 。等動(dòng)作真正完成了,再發(fā)送Command Subsequently Completed。
?
l2cap數(shù)據(jù)傳輸
?
?
?
一個(gè)l2cap包會(huì)按照規(guī)則先切割為多個(gè)HCI數(shù)據(jù)包。HCI數(shù)據(jù)包再通過(guò)HCI-usb這一層傳遞給USB設(shè)備。每個(gè)包又通過(guò)USB driver發(fā)送到底層。
?
二、HCI層的編程
HCI是溝通上層協(xié)議以及程序與底層硬件協(xié)議的通道。所以,通過(guò)HCI發(fā)送的Command都是上層協(xié)議或者應(yīng)用程序發(fā)送給Bluetooth device的。它命令Bluetooth device(或其中的硬件協(xié)議)去做什么何種動(dòng)作。
Socket基礎(chǔ)概念:
使用函數(shù)socket()建立一個(gè)Socket,就如同你有一部電話。bind()則是把這個(gè)電話和某個(gè)電話號(hào)碼(網(wǎng)絡(luò)地址)對(duì)應(yīng)起來(lái)。
類似的,我們可以把Host理解為一個(gè)房間,這個(gè)房間有多部電話(Dongle)。
當(dāng)使用socket() 打開(kāi)一個(gè)HCI protocol的socket,表明得到這個(gè)房間的句柄。HOST可能會(huì)有多個(gè)device。換句話說(shuō),這個(gè)房間可以有多個(gè)電話號(hào)碼。所以HCI會(huì)提供一套指令去得到這些device。
?
得到Host上插入設(shè)備數(shù)目以及device信息
// 0. 分配一個(gè)空間給 hci_dev_list_req。這里面將放所有device信息。 structhci_dev_list_req *dl; structhci_dev_req *dr; structhci_dev_info di; int i;if (!(dl = malloc(HCI_MAX_DEV * sizeof(structhci_dev_req) + sizeof(uint16_t)))) {perror("Can't allocate memory");exit(1);}dl->dev_num = HCI_MAX_DEV;dr = dl->dev_req;
//1. 打開(kāi)一個(gè)HCI socket.此socket相當(dāng)于一個(gè)房間。 if ((ctl= socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {perror("Can't open HCI socket.");exit(1);}
// 2. 使用HCIGETDEVLIST,得到所有device的Device ID。存放在dl中。 if (ioctl(ctl, HCIGETDEVLIST, (void *) dl)< 0) {perror("Can't get device list");exit(1);}
?
// 2. 使用HCIGETDEVLIST,得到所有device的Device ID。存放在dl中。 if (ioctl(ctl, HCIGETDEVLIST, (void *) dl)< 0) {perror("Can't get device list");exit(1);}
device信息:
struct hci_dev_info {uint16_t dev_id; //Device IDchar name[8]; //device namebdaddr_t bdaddr; //device bdaddruint32_t flags; //device Flags:如:UP,RUNING,Down等。uint8_t type; //device 連接方式:如USB,PC Card,UART,RS232等。uint8_t features[8];uint32_t pkt_type;uint32_t link_policy;uint32_t link_mode;uint16_t acl_mtu;uint16_t acl_pkts;uint16_t sco_mtu;uint16_t sco_pkts;struct hci_dev_stats stat; //此device的數(shù)據(jù)信息,如發(fā)送多少個(gè)ACL Packet,正確多少,錯(cuò)誤多少,等等。 };
UP和Down Bluetooth device:
ioctl(ctl, HCIDEVUP, hdev) ioctl(ctl, HCIDEVDOWN, hdev) ctl:為使用socket(AF_BLUETOOTH,SOCK_RAW, BTPROTO_HCI)打開(kāi)的Socket. hdev: Device ID.(所以上面的Socket不需要bind,因?yàn)檫@邊指定了)
?BlueZ提供的HCI編程接口一(針對(duì)本地device的API系列)
?
1 打開(kāi)一個(gè)HCI Socket
inthci_open_dev(int dev_id):
這個(gè)function用來(lái)打開(kāi)一個(gè)HCI Socket。它首先打開(kāi)一個(gè)HCI protocol的Socket(房間),并將此Socket與device ID=參數(shù)dev_id的device綁定起來(lái)。只有bind后,它才將Socket句柄與device對(duì)應(yīng)起來(lái)。
注意,所有的HCI Command發(fā)送之前,都需要使用 hci_open_dev打開(kāi)并綁定。
?
2 關(guān)閉一個(gè)HCI Socket:
inthci_close_dev(int dd) //簡(jiǎn)單的關(guān)閉使用hci_open_dev打開(kāi)的Socket。
?
3? 向HCI Socket(對(duì)應(yīng)一個(gè)Dongle)發(fā)送 request
inthci_send_req(int dd, struct hci_request *r, int to)
BlueZ提供這個(gè)function非常有用,它可以實(shí)現(xiàn)一切Host向Modules發(fā)送Command的功能。
參數(shù)1:HCI Socket。
參數(shù)2:Command內(nèi)容。
參數(shù)3:以milliseconds為單位的timeout。
?
下面詳細(xì)解釋此function和用法:
當(dāng)應(yīng)用程序需要向Dongle(對(duì)應(yīng)為一個(gè)bind后的Socket)發(fā)送Command時(shí),調(diào)用此function。其中,
參數(shù)一dd對(duì)應(yīng)一個(gè)使用hci_open_dev()打開(kāi)的Socket(Dongle)。
參數(shù)三to則為等待Dongle執(zhí)行并回復(fù)命令結(jié)果的timeout.以毫秒為單位。
參數(shù)二hci_request * r 最為重要,首先看它的結(jié)構(gòu):
structhci_request {uint16_togf; //Opcode Groupuint16_tocf; //Opcode Commandint event; //此Command產(chǎn)生的Event類型。void *cparam; //Command 參數(shù)int clen; //Command參數(shù)長(zhǎng)度void *rparam; //Response 參數(shù)int rlen; //Response 參數(shù)長(zhǎng)度 };
ogf,ocf不用多說(shuō),對(duì)應(yīng)前面的圖就明白這是Group Code和Command Code。這兩項(xiàng)先確定下來(lái),然后可以查HCI Spec。察看輸入?yún)?shù)(cparam)以及輸出參數(shù)(rparam)含義。至于他們的結(jié)構(gòu)以及參數(shù)長(zhǎng)度,則在~/include/net/bluetooth/hci.h中有定義。
至于event.如果設(shè)置,它會(huì)被setsockopt設(shè)置于Socket。
?
例1:得到某個(gè)連接的Policy Setting.
HCI Spec以及~/include/net/bluetooth/hci.h中均可看到,OGF=OGF_LINK_POLICY(0x02).OCF=OCF_READ_LINK_POLICY(0x0C).
因?yàn)檫@個(gè)Command用來(lái)讀取某個(gè)ACL連接的Policy Setting。所以輸入?yún)?shù)即為此連接Handle。返回參數(shù)則包含3部分,status(Command是否順利執(zhí)行), handle(連接Handle)。 policy(得到的policy值)
這就又引入了一個(gè)新問(wèn)題,如何得到某個(gè)ACL連接的Handle。
可以使用ioctlHCIGETCONNINFO得到ACL 連接Handle。
ioctl(dd, HCIGETCONNINFO,(unsigned long) cr); Connect_handle =htobs(cr->conn_info->handle);
?
所以完整的過(guò)程如下:
?
struct hci_requestHCI_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.填寫(xiě)Command輸入?yún)?shù)Command_Param.handle = Connect_handle;HCI_Request.ogf = OGF_LINK_POLICY; //Command組IDHCI_Request.ocf = OCF_READ_LINK_POLICY;//Command IDHCI_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;} //如果返回值狀態(tài)不對(duì)if (Response_Param.status) {return -1;}//得到當(dāng)前policy*policy = Response_Param.policy;?
4 幾個(gè)更基礎(chǔ)的function static inline void bacpy(bdaddr_t*dst, const bdaddr_t *src) //bdaddr copy static inline intbacmp(const bdaddr_t *ba1, const bdaddr_t *ba2)//bdaddr 比較
5 得到指定Dongle BDAddr int hci_read_bd_addr(int dd,bdaddr_t *bdaddr, int to); 參數(shù)1:HCI Socket,使用hci_open_dev()打開(kāi)的Socket(Dongle)。 參數(shù)2:輸出參數(shù),其中會(huì)放置bdaddr. 參數(shù)3:以milliseconds為單位的timeout.
?
6 讀寫(xiě)device Name int hci_read_local_name(intdd, int len, char *name, int to) int hci_write_local_name(intdd, const char *name, int to) 參數(shù)1:HCI Socket,使用hci_open_dev()打開(kāi)的Socket(Dongle)。 參數(shù)2:讀取或設(shè)置Name。 參數(shù)3:以milliseconds為單位的timeout. 注意:這里的Name與IOCTL HCIGETDEVINFO 得到hci_dev_info中的name不同。
7 得到HCI Version: inthci_read_local_version(int dd, struct hci_version *ver, int to)
8 得到已經(jīng)UP的device BDaddr int hci_devba(int dev_id,bdaddr_t *bdaddr); dev_id: Device ID. bdaddr:輸出參數(shù),指定device如果UP, 則放置其BDAddr。
9 得到device Info int hci_devinfo(int dev_id,struct hci_dev_info *di) dev_id: Device ID. di: 此device信息。 出錯(cuò)返回 -1。 注意,這個(gè)Function的做法與3.0的方法完全一致。
10 從hciX中得到X int hci_devid(const char*str) str: 類似 hci0這樣的字串。 如果hciX對(duì)應(yīng)的Device ID(X)是現(xiàn)實(shí)存在且UP。則返回此設(shè)備Device ID。
11得到BDADDR不等于參數(shù)bdaddr的Device ID int hci_get_route(bdaddr_t*bdaddr) 查找device,發(fā)現(xiàn)device Bdaddr不等于參數(shù)bdaddr的第一個(gè)device,則返回此 Device ID。所以,如果: int hci_get_route(NULL),則得到第一個(gè)可用的 Device ID。
12 將BDADDR轉(zhuǎn)換為字符串 int ba2str(const bdaddr_t*ba, char *str)
13 將自串轉(zhuǎn)換為BDADDR int str2ba(const char *str,bdaddr_t *ba)
?
BlueZ提供的HCI編程接口二(針對(duì)Remote Device的API系列)
1? inquiry 遠(yuǎn)程Bluetooth Device
int hci_inquiry(int dev_id,int len, int nrsp, const uint8_t *lap, inquiry_info **ii, long flags)
hci_inquiry()用來(lái)命令指定的Dongle去搜索周圍所有bluetooth device.并將搜索到的Bluetooth Device bdaddr 傳遞回來(lái)。
參數(shù)1:dev_id:指定Dongle Device ID。如果此值小于0,則會(huì)使用第一個(gè)可用的Dongle。
參數(shù)2:len: 此次inquiry的時(shí)間長(zhǎng)度(每增加1,則增加1.25秒時(shí)間)
參數(shù)3:nrsp:此次搜索最大搜索數(shù)量,如果給0。則此值會(huì)取255。
參數(shù)4:lap:BDADDR中LAP部分,Inquiry時(shí)這塊值缺省為0X9E8B33.通常使用NULL。則自動(dòng)設(shè)置。
參數(shù)5:ii:存放搜索到Bluetooth Device的地方。給一個(gè)存放inquiry_info指針的地址,它會(huì)自動(dòng)分配空間。并把那個(gè)空間頭地址放到其中。
參數(shù)6:flags:搜索flags.使用IREQ_CACHE_FLUSH,則會(huì)真正重新inquiry。否則可能會(huì)傳回上次的結(jié)果。
?
返回值是這次Inquiry到的Bluetooth Device 數(shù)目。
?
注意:如果*ii不是自己分配的,而是讓hci_inquiry()自己分配的,則需要調(diào)用bt_free()來(lái)幫它釋放空間。
?
2 得到指定BDAddr的reomte device Name
int hci_read_remote_name(intdd, const bdaddr_t *bdaddr, int len, char *name, int to)
參數(shù)1:使用hci_open_dev()打開(kāi)的Socket。
參數(shù)2:對(duì)方BDAddr.
參數(shù)3:name 長(zhǎng)度。
參數(shù)4:(out)放置name的位置。
參數(shù)5:等待時(shí)間。
?
3 讀取連接的信號(hào)強(qiáng)度
int hci_read_rssi(int dd,uint16_t handle, int8_t *rssi, int to)
注意,所有對(duì)連接的操作,都會(huì)有一個(gè)參數(shù),handle.這個(gè)參數(shù)是連接的Handle。前面講過(guò)如何得到連接Handle的。
?
例子:
//scan是一個(gè)利用hci層協(xié)議 獲得遠(yuǎn)程藍(lán)牙設(shè)備的藍(lán)牙地址和藍(lán)牙昵稱的函數(shù);
?
int scan() {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);printf("/nhci%d is scanning....../n",dev_id);//dev_id = lc[scannum].name;sock = hci_open_dev( dev_id );if (dev_id < 0 || sock < 0){system("reboot");perror("opening socket");return 0;}bdaddr_t src;bacpy(&src, BDADDR_ANY);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));int c=0;if (sdp_get_channel_opush(&src, &(ii+i)->bdaddr,&c))//printf("/n%d/n",c);if(c>0)addadr(addr,c); //此處是我在做項(xiàng)目中做的一個(gè)與應(yīng)用程序連接的接口函數(shù),功能是完成遠(yuǎn)程藍(lán)牙設(shè)備的地址和文件傳輸通道號(hào)添加到我自己建的地址列表中.//printf("chinal=%d/n",c);// 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; }
?
//sdp_get_channel_opush,這是一個(gè)我自己編寫(xiě)的通過(guò) hci獲得的遠(yuǎn)程藍(lán)牙地址和初始化為零的channel來(lái)獲取遠(yuǎn)程藍(lán)牙設(shè)備有無(wú)文件傳輸功能,并獲得文件傳輸通道的過(guò)程。
?
intsdp_get_channel_opush(bdaddr_t *src, bdaddr_t *dst, int *channel) {uuid_t service;sdp_session_t *session;sdp_list_t *search, *attrs, *rsp;uint16_t attr;int err;/* build search request */sdp_uuid16_create(&service, OBEX_OBJPUSH_SVCLASS_ID);search = sdp_list_append(0, &service);attr = SDP_ATTR_PROTO_DESC_LIST;attrs = sdp_list_append(NULL, &attr);/* connect */session = sdp_connect(src, dst, SDP_RETRY_IF_BUSY);if (!session) return(-1);/* send request */err = sdp_service_search_attr_req(session, search,SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);/* close connection */sdp_close(session);printf("sdp_service_search_attr_req-return=%d/n",err);if (err) return(0);/* get rfcomm channel */for(; rsp; rsp = rsp->next){sdp_record_t *rec = (sdp_record_t *) rsp->data;sdp_list_t *protos;if (!sdp_get_access_protos(rec, &protos)){int ch = sdp_get_proto_port(protos, RFCOMM_UUID);printf("channel=%d/n",ch);if (ch > 0){*channel = ch;return(1);}}}return(0); }
關(guān)于l2cap
?
一、L2CAP協(xié)議簡(jiǎn)介
L2CAP 基于 通道(channel) 的概念。 通道 (Channel) 是位于基帶 (baseband) 連接之上的邏輯連接。每個(gè)通道以多對(duì)一的方式綁定一個(gè)單一協(xié)議(single protocol)。多個(gè)通道可以綁定同一個(gè)協(xié)議,但一個(gè)通道不可以綁定多個(gè)協(xié)議。每個(gè)在通道里接收到的 L2CAP 數(shù)據(jù)包被傳到相應(yīng)的上層協(xié)議。 多個(gè)通道可共享同一個(gè)基帶連接。
也就是說(shuō),所有L2CAP數(shù)據(jù)均通過(guò)HCI傳輸?shù)絉emote Device。且上層協(xié)議的數(shù)據(jù),大都也通過(guò)L2CAP來(lái)傳送。
?
L2CAP可以發(fā)送Command。例如連接,斷連等等。其命令格式如下
Command format:
?
?
?
例子:CONNECTIONREQUEST (CODE 0x02)
Identifier:請(qǐng)求的匹配響應(yīng)。
Source CID:Set up sourceconnection channel identifier
PSM(Protocol/ServiceMultiplexer):比較需要注意,L2CAP 使用L2CAP連接請(qǐng)求(ConnectionRequest )命令中的PSM字段實(shí)現(xiàn)協(xié)議復(fù)用。L2CAP可以復(fù)用發(fā)給上層協(xié)議的連接請(qǐng)求,這些上層協(xié)議包括服務(wù)發(fā)現(xiàn)協(xié)議SDP(PSM = 0x0001)、RFCOMM(PSM = 0x0003)和電話控制(PSM = 0x0005)等。
?
| Protocol | PSM |
| SDP | 0x0001 |
| RFCOMM | 0x0003 |
| TCS-BIN | 0x0005 |
| TCS-BIN-CORDLESS | 0x0007 |
| BNEP | 0x000F |
| HID_Control | 0x0011 |
| HID_Interrupt | 0x0013 |
| UPnP | 0x0015 |
| AVCTP | 0x0017 |
| AVDTP | 0x0019 |
| AVCTP_Browsing | 0x001B |
| UDI_C-Plane | 0x001D |
?
?
?
二、L2CAP編程方法
幾乎所有協(xié)議的連接,斷連,讀寫(xiě)都是用L2CAP連接來(lái)做的。
?
1.創(chuàng)建L2CAP Socket:
socket(PF_BLUETOOTH,SOCK_RAW, BTPROTO_L2CAP);
domain=PF_BLUETOOTH,type可以是多種類型。protocol=BTPROTO_L2CAP.
?
2.綁定:
// Bindto local addressmemset(&addr, 0, sizeof(addr));addr.l2_family = AF_BLUETOOTH;bacpy(&addr.l2_bdaddr, &bdaddr);//bdaddr為本地Dongle BDAddrif (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;}
?
注意:
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;}
?
4. 發(fā)送數(shù)據(jù)到Remote Device:
send()或write()都可以。
?
5. 接收數(shù)據(jù):
revc() 或read()
?
Bluetooth設(shè)備的狀態(tài)
之前HCI編程時(shí),是用 ioctl(HCIGETDEVINFO)得到某個(gè)Device Info(hci_dev_info).其中flags當(dāng)時(shí)解釋的很簡(jiǎn)單。其實(shí)它存放著B(niǎo)luetoothDevice(例如:USB BluetoothDongle)的當(dāng)前狀態(tài):
其中,UP,Down狀態(tài)表示此Device是否啟動(dòng)起來(lái)。可以使用ioctl(HCIDEVUP)等修改這些狀態(tài)。
另外:就是Inquiry Scan,PAGE Scan這些狀態(tài):
Sam在剛開(kāi)始自己做L2CAP層連接時(shí),使用另一臺(tái)Linux機(jī)器插USB BluetoothDongle作Remote Device。怎么也沒(méi)法使用inquiry掃描到remote設(shè)備,也沒(méi)法連接remote設(shè)備,甚至無(wú)法使用l2ping ping到remote設(shè)備。覺(jué)得非常奇怪,后來(lái)才發(fā)現(xiàn)Remote Device狀態(tài)設(shè)置有問(wèn)題。沒(méi)有設(shè)置PSCAN和ISCAN。
InquiryScan狀態(tài)表示設(shè)備可被inquiry. PageScan狀態(tài)表示設(shè)備可被連接。
#hciconfighci0 iscan
#hciconfighci0 pscan
或者:#hciconfighci0 piscan
就可以設(shè)置為PSCAN或者iSCAN狀態(tài)了。
編程則可以使用ioctl(HCISETSCAN). dev_opt = SCAN_INQUIRY;dr.dev_opt = SCAN_PAGE;dr.dev_opt = SCAN_PAGE |SCAN_INQUIRY;
則可以inquiry或者connect了。
?
例一:發(fā)送SignalingPacket:
SignalingCommand是2個(gè)Bluetooth實(shí)體之間的L2CAP層命令傳輸。所以得SignalingCommand使用CID 0x0001.
多個(gè)Command可以在一個(gè)C-frame(control frame)中發(fā)送。
?
如果要直接發(fā)送SignalingCommand.需要建立SOCK_RAW類型的L2CAP連接Socket。這樣才有機(jī)會(huì)自己填充Command Code,Identifier等。
?
發(fā)送signalingCommand以及接收Response的簡(jiǎn)單例子:
#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>intmain(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; //不要為0send_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 socketl2_sck = socket(PF_BLUETOOTH, SOCK_RAW,BTPROTO_L2CAP); //創(chuàng)建L2CAPprotocol的RAW Packetif(l2_sck < 0){perror("\nsocket:");eturn -1;}//bindmemset(&local_l2_addr, 0, sizeof(structsockaddr_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);}//connectmemset(&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, (structsockaddr*)&remote_l2_addr, sizeof(struct sockaddr_l2));if(iRel < 0){perror("\nconnect()");exit(0);}//get local bdaddrlen = sizeof(struct sockaddr_l2);memset(&local_l2_addr, 0, sizeof(structsockaddr_l2));//注意,getsockname()參數(shù)三是一個(gè)輸入輸出參數(shù)。輸入時(shí),為參數(shù)二的總體長(zhǎng)度。輸出時(shí),//為實(shí)際長(zhǎng)度。iRel = getsockname(l2_sck, (structsockaddr*) &local_l2_addr, &len);if(iRel < 0){perror("\ngetsockname()");exit(0);}ba2str(&(local_l2_addr.l2_bdaddr), str);//printf("\nLocal Socketbdaddr:[%s]\n", str);printf("l2ping: [%s] from [%s](datasize %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; //如上圖所示,這一項(xiàng)為此CommandIdentifiersend_cmd->len = htobs(size);send_cmd->code = L2CAP_ECHO_REQ; //如上圖所示,此項(xiàng)為Command code.這項(xiàng)定為://Echo Request。對(duì)端會(huì)發(fā)送Response回來(lái)。code=L2CAP_ECHO_RSPwhile(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("\nReceiveResponse 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; }
?
所以說(shuō),如果想要發(fā)送接收signalingCommand。只需要建立l2cap RAWsocket. 并按規(guī)則填充command id,command code等。就可以接收發(fā)送了。
?
CommandCode: 這個(gè)值放在l2cap.h中。
?
#defineL2CAP_COMMAND_REJ 0x01 #defineL2CAP_CONN_REQ 0x02 #defineL2CAP_CONN_RSP 0x03 #defineL2CAP_CONF_REQ 0x04 #defineL2CAP_CONF_RSP 0x05 #defineL2CAP_DISCONN_REQ 0x06 #defineL2CAP_DISCONN_RSP 0x07 #defineL2CAP_ECHO_REQ 0x08 #defineL2CAP_ECHO_RSP 0x09 #defineL2CAP_INFO_REQ 0x0a #defineL2CAP_INFO_RSP 0x0b
?
?
例二:任意PSM的L2CAP連接間數(shù)據(jù)的傳輸
server用來(lái)監(jiān)聽(tīng)指定PSM的連接,并監(jiān)聽(tīng)數(shù)據(jù)。同時(shí),利用poll來(lái)查看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); intmain(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 socketsk = socket(PF_BLUETOOTH, SOCK_SEQPACKET,BTPROTO_L2CAP); //發(fā)送數(shù)據(jù),使用SOCK_SEQPACKET為好if(sk < 0){perror("\nsocket():");exit(0);}//bindlocal_addr.l2_family = PF_BLUETOOTH;local_addr.l2_psm = htobs(atoi(argv[argc-1])); //last psmbacpy(&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.每個(gè)包的最大值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 valueopts.omtu = 0;opts.imtu = 672;if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS,&opts, sizeof(opts)) < 0){perror("\nsetsockopt():");exit(0);}//listeniRel = 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, (structsockaddr*)(&remote_addr), &len);if(nsk < 0){perror("\naccept():");continue;}ba2str(&(remote_addr.l2_bdaddr),str);printf("\npeerbdaddr:[%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 upprintf("\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 dataiRel = 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(intargc, 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好像不許有無(wú)名Socketlocal_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(structsockaddr_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, (structsockaddr*)&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 網(wǎng)絡(luò)編程中,主動(dòng)發(fā)起連接方,因?yàn)椴魂P(guān)心地址具體是什么,所以可以作為無(wú)名socket,也就是說(shuō)可以不bind. 但Bluetooth則不可以,一定需要bind.
2. poll可以查出連接斷連,但需要注意:斷開(kāi)的revent值為:11001B。也就是說(shuō):POLLIN |POLLERR |POLLHUP。
3. 被連接一方,一定要指定PSM。
?
?
?
關(guān)于sdp
?
Servicediscovery機(jī)制提供client應(yīng)用程序偵測(cè)server應(yīng)用程序提供的服務(wù)的能力,并且能夠得到服務(wù)的特性。服務(wù)的品質(zhì)包含服務(wù)type或服務(wù)class.
SDP也提供SDP server與SDP client之間的通訊。SDP server維護(hù)著一個(gè)服務(wù)條目(service record)列表.每個(gè)服務(wù)條目描述一個(gè)單獨(dú)的服務(wù)屬性。 SDP client可以通過(guò)發(fā)送SDP request來(lái)得到服務(wù)條目。
如果一個(gè)client或者依附于client之上的應(yīng)用程序決定使用某個(gè)service. 它創(chuàng)建一個(gè)單獨(dú)的連接到service提供者。 SDP 只提供偵測(cè)Service的機(jī)制,但不提供如何利用這些Service的機(jī)制。這里其實(shí)是說(shuō):SDP只提供偵測(cè)Service的辦法,但如何用,SDP不管。
每個(gè)Bluetooth Device最多只能擁有一個(gè)SDP Server。如果一個(gè)Bluetooth Device只擔(dān)任Client,那它不需要SDP Server。但一個(gè)Bluetooth Device可以同時(shí)擔(dān)當(dāng)SDP Server和SDP client.
?
ServiceRecord(Service 條目):
一個(gè)service是一個(gè)實(shí)體為另一個(gè)實(shí)體提供信息,執(zhí)行動(dòng)作或控制資源。一個(gè)service可以由軟件,硬件或軟硬件結(jié)合提供。
所有的Service信息都包含于一個(gè)Service Record內(nèi)。一個(gè)Service Record 包含一個(gè)Service attribute(Service屬性) list.
?
在一個(gè)SDP Server內(nèi),每個(gè)Service Record擁有一個(gè)32-bit的唯一性數(shù)據(jù)。通常,這個(gè)唯一性只是在每個(gè)SDP Server內(nèi)部。 如果SDP Server S1 和SDP Server S2擁有同樣的一個(gè)Service Record。那他們?cè)诓煌琒DP Sever內(nèi)的獨(dú)特?cái)?shù)值并不一定相同。
SDP在SDP Server增加或減少Service Record時(shí),并不會(huì)通知SDP client.
?
ServiceAttribute(Service 屬性):
每個(gè)Service屬性描述servcie的特性.一個(gè)Service Attribute由2部分:
AttributeID + Attribute Value。
AttributeID:16-bit無(wú)符號(hào)整數(shù),用于區(qū)別一個(gè)Service Record內(nèi)的其它屬性。
AttributeValue:Attribute值。
?
ServiceClass:
每個(gè)Service 都是某個(gè)Service Class的實(shí)例. Service Class定義了Service Record中包含的Service 屬性。屬性ID,屬性值都被定義好了。
每個(gè)Service Class也有一個(gè)獨(dú)特ID。這個(gè)Service Class標(biāo)識(shí)符包含在屬性值ServiceClassIDList屬性中。并描繪為UUID。自從Service Record中的屬性格式以及含義依賴于Service Class后,ServiceClassIDList屬性變得非常重要。
?
SearchingFor Service:
ServiceSearch transaction(事務(wù))允許client得到Service Record Handle。一旦SDP Client得到Service Record Handle,它就可以請(qǐng)求這個(gè)Record內(nèi)具體屬性的值。
如知道某個(gè)屬性值UUID,則可以通過(guò)查找UUID查到這個(gè)屬性。
UUID: universally uniqueidentifier.(唯一性標(biāo)識(shí)符)
?
SDP協(xié)議棧使用request/response模式工作,每個(gè)傳輸過(guò)程包括一個(gè)request protocoldata unit(PDU)和一個(gè)response PDU. SDP使用L2CAP連接傳輸數(shù)據(jù)。在發(fā)送Request PDU但未收到Response PDU之前,不能向同一個(gè)server再發(fā)送Request PDU。
?
PDU ID:用來(lái)識(shí)別PDU。
TransactionID:用來(lái)識(shí)別Request PUD以及Response PUD。并用來(lái)對(duì)比某個(gè)Response PUD是否對(duì)應(yīng)著Request PUD。
?
?
關(guān)于rfcomm
?
測(cè)試端輸入字符串,并顯示在服務(wù)端。
?
Client:#include <stdio.h> #include <stdlib.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,*buf; //="00:11:67:32:61:62";if(argc==2){dest=argv[1];}else{printf("prarmerror/n");exit(1);}// allocate a sockets=socket(PF_BLUETOOTH,SOCK_STREAM,BTPROTO_RFCOMM);if(s<0){perror("createsocket error");exit(1);} // 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 serverprintf("connectting.../n");status=connect(s,(struct sockaddr *)&addr,sizeof(addr));// send a messageif(status==0){printf("scuess!/n");status=write(s,"hello!",6);do{scanf("%s",buf);status=write(s,buf,strlen(buf));if(status<0) perror("uh oh");}while(strcmp(buf,"goodbye")!=0);}else{printf("Failed!/n");}close(s);return 0; }
?
Server:#include <stdio.h> #include <stdlib.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};//,*addr;int s,client, bytes_read,result;int opt = sizeof(rem_addr);printf("Creating socket.../n");s =socket(PF_BLUETOOTH,SOCK_STREAM,BTPROTO_RFCOMM);if(s<0){perror("createsocket error");exit(1);}else{printf("success!/n");}loc_addr.rc_family=AF_BLUETOOTH;loc_addr.rc_bdaddr=*BDADDR_ANY;loc_addr.rc_channel=(uint8_t)1;printf("Binding socket.../n");result=bind(s,(struct sockaddr *)&loc_addr,sizeof(loc_addr));if(result<0){perror("bind socket error:");exit(1);}else{printf("success!/n");}/*result=ba2str(&loc_addr.rc_bdaddr,addr);if(result<0){perror("addrconvert error");exit(1);}printf("localaddr is:%s/n",addr);*/printf("Listen... ");result=listen(s,1);if(result<0){printf("error:%d/n:",result);perror("listen error:");exit(1);}else{printf("requested!/n");}printf("Accepting.../n");client= accept(s,(struct sockaddr *)&rem_addr,&opt);if(client<0){perror("accepterror");exit(1);}else{printf("OK!/n");}ba2str(&rem_addr.rc_bdaddr,buf);fprintf(stderr,"accepted connection from %s /n",buf);memset(buf,0,sizeof(buf));while(1){bytes_read = read(client,buf,sizeof(buf));if(bytes_read>0){printf("received[%s]/n",buf);if(strcmp(buf,"goodbye")==0)exit(1);memset(buf,0,bytes_read);}}close(client);close(s);return 0 ; }
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/wuyida/archive/2013/03/27/6300007.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的和菜鸟一起学linux之bluez学习记录2的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 登录和操作员管理
- 下一篇: Linux 内核态与用户态通信 netl