一、树莓派中的蓝牙

Raspberry中已经安装了Bluez。我使用的版本是5.50。你可以通过以下命令检查自己的BlueZ版本:

bluetoothd -v

可以用下面的命令检查Bluez的运行状态:

systemctl status bluetooth

在这里插入图片描述
可以看到我的返回结果显示蓝牙已经打开并正在运行。
你可以用下面命令手动启动或关闭蓝牙服务:

sudo systemctl start bluetooth
sudo systemctl stop bluetooth

此外,你还可以让蓝牙服务随系统启动:

sudo systemctl enable bluetooth

在Raspberry中,基本的蓝牙操作可以通过bluez中的bluetoothctl命令进行。该命令运行后,将进入到一个新的Shell。在这个shell中输入:

list

将显示树莓派上可用的蓝牙模块,例如:
在这里插入图片描述
运行scan命令,开启扫描:

scan on

虽然树莓派中已经有了蓝牙,但是还是建议执行一下以下命令,以方便蓝牙的使用:
一、升级安装蓝牙相关软件包

sudo apt-get update 
sudo apt-get upgrade -y 
sudo apt-get dist-upgrade -y 
sudo apt-get install pi-bluetooth bluez bluez-firmware blueman

二、最关键一点:添加pi用户到蓝牙组

sudo usermod -G bluetooth -a pi

三、重启

sudo reboot

执行完毕之后你就会发现树莓派上面状态栏多了一个蓝牙的图标,方便使用蓝牙。
在这里插入图片描述

二、通过python使用树莓派蓝牙

python中有蓝牙对应的库可以使用,比如:bluetooth库。这个库对应的是pybluez蓝牙。
通过蓝牙名称来查找蓝牙:

import bluetooth
 
target_name = "Device Name"
target_address = None
 
nearby_devices = bluetooth.discover_devices()
 
for bdaddr in nearby_devices:
    if target_name == bluetooth.lookup_name( bdaddr ):
        target_address = bdaddr
        break
 
if target_address is not None:
    print("found target bluetooth device with address ", target_address)
else:
    print("could not find target bluetooth device nearby")

运行如上代码可能会发生如下报错:

ModuleNotFoundError: No module named ‘bluetooth’

原因可能是你安装bluez库时是使用python 2版本下载的,python 2版本不支持bluez。
解决方法:用python 3版本下载bluez。

python3 -m pip install pybluez

三、通过蓝牙进行通信

可采用类似于socket编程模型的方式进行蓝牙通信的编程,主要有两种协议:RFCOMM协议和L2CAP协议。
用这两种协议的用法没什么太大的区别,除了端口以外其他都差不多。
网上有些教程说RFCOMM支持的端口范围为1~30号端口,L2CAP端口是0x1001到0x8FFF之间的奇数端口。但是我使用L2CAP协议时,端口分别设置为0x1001到0x1009都无法成功通讯,设置为0x100b以及后面的奇数端口才成功连接通信,我不知道是L2CAP支持的端口不是从0x1001开始的问题还是我树莓派对应的端口已经被占用了的问题。使用命令 netstat -nultp 查看已经使用的端口发现上述端口并未被占用,所以我也搞不懂了,你们也可以自己测试一下哪些端口可以成功连接。

1、通过RFCOMM方式进行通信

1、服务端程序示例(蓝牙连接目的地址可以通过上一个代码借助蓝牙名称获取,也可以直接在终端通过本文刚开始介绍的方法获取):

import bluetooth

server_sock=bluetooth.BluetoothSocket(bluetooth.RFCOMM)
port = 1	#设置端口号
server_sock.bind(("", port))	#绑定地址和端口
server_sock.listen(1)	#绑定监听,最大挂起连接数为1
if __name__ =='__main__':
    try:
        while True:
            print('正在等待接收数据。。。')
            client_sock,address=server_sock.accept()  #阻塞等待连接
            print('连接成功')
            print("Accepted connection from ", address)
            while True:
                data =client_sock.recv(1024).decode() #不断接收数据,每次接收缓冲区1024字节
                print("received [%s]" % data)
    except:
        client_sock.close()
        server_sock.close()
        print('disconnect!')

2、客户端程序示例

import bluetooth
import time

target_address='DC:A6:32:88:BA:4B'	#目的蓝牙的地址
sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM )
port = 1
if __name__ =='__main__':
	try:
		sock.connect((target_address, port)) #连接蓝牙
		while True:
		    sock.send('hello!'.encode()) #每隔三秒发送一个字符串
		    time.sleep(3)
	except:
		sock.close()
2、通过L2CAP方式进行通信

1、服务端程序示例:

import bluetooth

server_sock=bluetooth.BluetoothSocket(bluetooth.L2CAP)
port = 0x1231	#设置端口号
server_sock.bind(("", port))	#绑定地址和端口
server_sock.listen(1)	#绑定监听,最大挂起连接数为1
if __name__ =='__main__':
    try:
        while True:
            print('正在等待接收数据。。。')
            client_sock,address=server_sock.accept()  #阻塞等待连接
            print('连接成功')
            print("Accepted connection from ", address)
            while True:
                data =client_sock.recv(1024).decode() #不断接收数据,每次接收缓冲区1024字节
                print("received [%s]" % data)
    except:
        client_sock.close()
        server_sock.close()
        print('disconnect!')

2、客户端程序示例

import bluetooth
import time

target_address='DC:A6:32:88:BA:4B'	#目的蓝牙的地址
sock=bluetooth.BluetoothSocket( bluetooth.L2CAP )
port = 0x1231
if __name__ =='__main__':
	try:
		sock.connect((target_address, port)) #连接蓝牙
		while True:
		    sock.send('hello!'.encode()) #每隔三秒发送一个字符串
		    time.sleep(3)
	except:
		sock.close()

提示: 通过python代码连接蓝牙时,树莓派上可能会弹出一个界面提示你有设备想连接你的蓝牙,要你确定是否连接,导致需要手动点击,不够自动化。你可以试试在蓝牙模块里面设置信任对应的蓝牙设备,如下图:
在这里插入图片描述
在这里插入图片描述

四、问题与改进

上述代码显然只能进行单次的连接,无法进行循环复用。在测试的过程中,我发现:当服务端和客户端已经连接好了,服务端正在接收数据,这时如果突然停止客户端代码,那么服务端会一直接收到空字符,即recv()函数会一直返回空字符,之后无论对面是关闭蓝牙或者是关机,recv()函数都会一直返回空字符;但是在服务端正在接收数据时,你突然关闭客户端蓝牙或者让客户端树莓派关机,服务端都会报错。
综上,我们可以通过如下方法让服务端遇到客户端断开连接时回到等待连接的状态:

import bluetooth

server_sock=bluetooth.BluetoothSocket(bluetooth.RFCOMM)
port = 1	#设置端口号
server_sock.bind(("", port))	#绑定地址和端口
server_sock.listen(1)	#绑定监听,最大挂起连接数为1
if __name__ =='__main__':
   while True:
   	   try:
	       print('正在等待接收数据。。。')
	       client_sock,address=server_sock.accept()  #阻塞等待连接
	       print('连接成功')
	       print("Accepted connection from ", address)
	       while True:
	           data =client_sock.recv(1024).decode() #不断接收数据,每次接收缓冲区1024字节
	           if data == '':
	           	  break		#接收到空字符表示客户端已经断开连接了
	           print("received [%s]" % data)
       except: 
		   print('disconnect!')
		   continue	#报错后返回阻塞等待连接的状态

因为服务端需要一直运行,这样当有客户端想要进行连接时才能收到请求,所以对代码的可长时运行要求比较高。但客户端对这样的要求却不高,客户端发生连接错误让客户再次手动点击连接即可,我们只要把错误返回给客户就行了。
我上面代码设置客户端循环发送只是为了方便查看现象而已。

结语:小萌新刚接触树莓派蓝牙,如果本文中有什么不对的,欢迎指出和讨论!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐