本文旨在做爲入門藍牙開發的一個簡單介紹python
$ sudo apt install libglib2.0-dev libbluetooth-dev bluetooth $ pip install pybluez
每一個藍牙芯片包含惟一的48-bit地址,稱爲Bluetooth address和device address。能夠把它看做爲以太網的MAC地址,一樣由IEEE註冊受權。在製做的時候就寫入到芯片李,做爲藍牙編成最基本的地址單元。程序員
一個藍牙設備須要和另一個設備通訊,必需要有某種機制獲悉到其餘設備的藍牙地址。編程
在互聯網中,一般會利用DNS技術實現一個簡單域名來進行IP地址轉換,在藍牙中一般是提供一個友好的名字,例如"My Phone",客戶端經過查找附近藍牙設備來轉換爲數字地址。服務器
如今客戶端已經確認好要通訊的設備,如今就須要確認使用什麼樣的傳輸協議了。網絡
RFCOMM相似TCP可靠性,雖然協議規範定義設爲模仿 RS-232串口通訊,就如相似TCP場景操做同樣簡單。socket
一般,應用程序使用TCP考慮使用點對點的鏈接,進行可靠的數據流傳輸。若是出現了固定次數失敗傳輸,那麼鏈接會斷開而且會拋出一個錯誤。tcp
RFCOMM和TCP最大的不一樣就是端口的選擇,TCP支持最大65525端口,RFCOMM僅支持30。學習
UDP一般設計用來在對可靠性傳輸沒有強制要求,就是爲了足夠輕量。L2CAP提供了相似的設計。ui
L2CAP,默認提供了一個面向鏈接,經過發送固定最大長度的單個數據包來提供可靠性。L2CAP能夠定製爲不一樣的可靠級別。爲了提供該能力,L2CAP提供了傳輸和確認的方式,爲被確認包進行重傳。有三種可用的策略:網絡傳輸協議
雖然藍牙容許應用可使用最大努力通訊而不是可靠傳輸,有幾點仍是須要注意。
| Requirement | Internet | Bluetooth | |---------------------- |---------- |------------------------------------------ | | 基於流的可靠傳輸 | TCP | RFCOMM | | 可靠的數據報傳輸 | TCP | RFCOMM or L2CAP with infinite retransmit | | 最大努力的數據報傳輸 | UDP | L2CAP (0-1279 ms retransmit) |
在搞清楚傳輸協議以後,第二個要弄清楚與遠程機器通訊部分就是端口號。在網絡傳輸協議裏,端口號是用來在同一個主機上來區分不一樣的具體應用的能力。藍牙也不例外,可是使用了略微不一樣的術語。在L2CAP中,端口稱爲Protocol Service Mutiplexers,能夠取1到32767基數端口號。在RFCOMM,有1-30通道可使用。除了這些差異,兩個協議提供相似TCP/IP多路複用功能。L2CAP,和RFCOMM不同,存在(1-1023)保留端口。
| 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 | port | odd numbered 1-4095 | odd numbered 4097 - 32765 |
在網絡編程中,服務器一般會使用經常使用端口進行服務,客戶端也使用經常使用端口進行鏈接。缺點就是不能在同一個服務器使用相同的端口應用程序,應用TCP/UDP可選擇的端口很是多,因此這裏也沒有多大的問題。
藍牙傳輸協議中,設計了較少的有效端口,咱們不能夠隨意在設計期間選擇任意端口。雖然在L2CAP中也不是什麼問題,它存在15,000保留端口,RFCOMM僅有30個端口。結果就是有在7個應用程序就有可能超過50%的可能性端口衝突。藍牙解決這個問題的方式使用Service Discovery Protocol(SDP)。
不是在設計時肯定端口,藍牙經過在運行時經過發佈-訂閱模型來肯定端口。宿主服務器提供一個叫作SDP服務,它使用了L2CAP一些保留端口。其餘服務器在運行時應用程序使用動態端口,而且註冊一些它們的描述信息。客戶端應用程序會從SDP服務(使用定義號的端口號)獲取須要的信息。
這裏的問題是客戶端端如何知道哪一個描述信息時它要找的呢?標準方式時經過給藍牙賦於128-bits的數值,成爲UUID(Universally Unique Identifier),客戶端和服務端使用了相同的UUID機制一邊SDP服務能夠找到它們。
SDP還能夠用來描述哪一個傳輸協議在使用,SDP在通訊當中也不是必須的,也可使用TCP/UDP的方式來提早定義端口,但要當心端口衝突。
PyBluez提供了藍牙編程的能力。
下面的代碼示例,是將附近全部的設備打印出來
import bluetooth nearby_devices = bluetooth.discover_devices(lookup_names=True) print("found %d devices" % len(nearby_devices)) for addr, name in nearby_devices: print(" %s - %s" % (addr, name))
結果:
# python bluetooth_ex1.py found 1 devices C0:A5:3E:88:F8:BB - 何文祥的 iPhone
咱們能夠看到地址使用16進制方式呈現,每一個佔用8位,一共48-bit。
discover_devices()大概花費10秒來檢測設備列表。lookup_names是請求時獲取到名稱,例如這裏的何文祥的 iPhone。
discover_devices()有時候會檢測失敗,lookup_name()有時也會返回None。
在Python中藍牙編程遵循了socket編程。這對於大部分網絡程序編寫過的程序員來講應該是很是熟悉,切換過來也至關簡單,如下展現瞭如何使用RFCOMM創建鏈接,以及傳輸數據,最後進行了斷開操做。
rfcomm_server.py
import bluetooth server_sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM ) port = 1 server_sock.bind(("",port)) server_sock.listen(1) client_sock,address = server_sock.accept() print "Accepted connection from ",address data = client_sock.recv(1024) print "received [%s]" % data client_sock.close() server_sock.close()
rfcomm_client.py
import bluetooth bd_addr = "01:23:45:67:89:AB" port = 1 sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM ) sock.connect((bd_addr, port)) sock.send("hello!!") sock.close()
在socket通訊中,socket提供了一個通訊通道,建立時尚未鏈接,直到有另外一端有發起鏈接過來,一旦鏈接創建,就能夠進行收發數據。
PyBluez支持兩種BluetoothSocket對象:RFCOMM和L2CAP。上面的例子就是RFCOMM socket,經過傳遞RFCOMM做爲BluetootheSocket構造參數。L2CAP咱們會在下節描述
咱們必須使用bind方法綁定給操做系統,bind方法接受一個元組參數,提供給藍牙適配器進行監控的地址和端口號。一般咱們在設備上只有一個藍牙適配器,第一個參數使用爲空就能夠了,以後咱們就可使用listen將socket置入監聽模式,等待鏈接。
BluetoothSocket向外鏈接須要使用connect方法,須要傳遞一個元組參數,該傳輸是指定須要創建鏈接的地址和端口號。後面咱們將介紹使用動態端口號以及使用SDP服務查找端口
接下來咱們瞭解如何使用L2CAP做爲傳輸協議,L2CAP通訊方式和RFCOMM sockets通訊方式極其相像。惟一區別就是傳遞給BluetoothSocket構造參數更改成L2CAP,選擇一個(0x1001到0x8FFF)基數號碼,默認鏈接配置提供了可靠的數據包大小爲672bytes。
l2cap-server.py
import bluetooth server_sock = bluetooth.BluetoothSocket(bluetooth.L2CAP) port = 0x1001 server_sock.bind(("", port)) server_sock.listen(1) client_sock, address = server_sock.accept() print("Accepted connection from", address) data = client_sock.recv(1024) print("Received [{:r}]".format(data)) client_sock.close() server_sock.close()
l2cap-client.py
import bluetooth sock = bluetooth.BluetoothSocket(bluetooth.L2CAP) bd_addr = "9C:B6:D0:E8:F9:D4" port = 0x1001 sock.connect((bd_addr, port)) sock.send(b"hello!") sock.close()
L2CAP發送的數據包有最大限制,兩個設備端都維護了一個MTU來指定能夠收到的最大包大小。若是二者調整各自的MTU,那麼它們的默認672字節能夠調整到65535字節。但一般,都會使用默認的MTU值進行茶unshu。在PyBluez經過設置set_l2cap_mtu方法來調整該值
bluetooth.set_l2cap_mtu( l2cap_sock, 65535 )
該方法使用也很直觀,第一個參數是建立BluetoothSocket對象,第二個參數就是具體要調整的值大小了。
有時候鏈接不可靠,須要使用set_packet_timeout方法()
bluetooth.set_packet_timeout( bdaddr, timeout )
set_packet_timeout接收藍牙地址,和一個毫秒參數,做爲調整L2CAP和RFCOMM鏈接發送包的超時時間,須要有管理員權限,並且該操做是全局影響的。
當前咱們已經學習如何檢測到附近藍牙設備,而且進行兩種傳輸協議的鏈接,均使用的是固定的藍牙地址和端口號。在實踐中咱們並不推薦這麼作。
動態開闢端口和使用SDP(Service Discovery Protocol)進行查找,get_available_port方法來找到有效的L2CAP和RFCOMM端口,advertise_service經過本地SDP服務器發送服務,find_service找到特定設備的藍牙設備。
server_sock.bind(("", bluetooth.PORT_ANY))
bluetooth.PORT_ANY任意端口,後面我可使用server_sock.getsockname()[1]來獲取到實際端口號
bluetooth.advertise_service( sock, name, uuid ) bluetooth.stop_advertising( sock ) bluetooth.find_service( name = None, uuid = None, bdaddr = None )
這三個方法提供了一個在本地藍牙設備提供了通知服務的方式.advertise_service接收一個socket用來綁定監聽, service name和UUID做爲參數。socket打開的話通知功能也一直打開,直到調用stop_advertising來關閉該socket。
find_service能夠查找單個或者全部附近特定設備。經過匹配name和uuid進行service查找,必須至少指定其中一個。若是bdaddr是None,那麼全部附近的設備都會進行查找。若是提供了localhost做爲bdaddr參數,那麼會對本地的SDP進行查找。否着,就會指定的bdaddr藍牙設備進行查找。
find_service返回一個列表,每一個列表是個字典類型,包含了host, name, protocol, port。
rfcomm-server-sdp.py
import bluetooth server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) server_sock.bind(("", bluetooth.PORT_ANY)) server_sock.listen(1) port = server_sock.getsockname()[1] print("listening on port {:d}".format(port)) uuid = "1e0ca4ea-299d-4335-93eb-27fcfe7fa848" bluetooth.advertise_service(server_sock, "FooBar Service", uuid) client_sock, address = server_sock.accept() print("Accepted connection from ", address) data = client_sock.recv(1024) print("received [{:r}]".format(data)) client_sock.close() server_sock.close()
rfcomm-client-sdp.py
import sys import bluetooth uuid = "1e0ca4ea-299d-4335-93eb-27fcfe7fa848" service_matches = bluetooth.find_service(uuid=uuid) if len(service_matches) == 0: print("couldn't find the FooBar service") sys.exit(0) first_match = service_matches[0] port = first_match["port"] name = first_match["name"] host = first_match["host"] print("connecting to {} on {}".format(name, host)) sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM ) sock.connect((host, port)) sock.send(b"hello!!") sock.close()