本文将介绍如何在 python 程序中使用 hcitool 工具对周围低功耗蓝牙设备(BLE)进行扫描。
设备依赖:
树莓派3B及更新版本设备
其他带有蓝牙功能的开发板(需确认软件支持)
配备蓝牙功能的x86主机或已安装蓝牙适配器
系统及软件依赖:
树莓派OS:Raspbian、Ubuntu Core
X86主机:Ubuntu、或其他Linux发行版 (Window及MacOS无原生支持Gatttool及Hcitool,因此不适应,若需要相应功能,可参考pybluez库)
软件:bluetooth、rpi-bluetooth (树莓派)
参考开源库
penlin/pygatt
,以其中
/pygatt/backends/gatttool/gatttool.py
中 scan 方法为例,作相应修改完成。
首先需要了解 hcitool 工具的使用。对于该工具的详细功能和参数的使用方法,可以使用:
hcitool --help
在开始使用 hcitool 工具之前,需要先进行一些配置。默认情况下, hcitool 工具需要有 root 权限,但是我们在使用中或者python程序中调用,都希望不需要使用 root 权限或者使用 sudo 来提权,我们可以通过以下配置来实现不需要 root 权限而对工具进行调用:
sudo setcap 'cap_net_raw,cap_net_admin+eip' `which hcitool`
完成配置后,我们就可以开始来了解 hcitool这个工具。以下介绍几个最主要常用的功能。首先是获取当前蓝牙设备的信息:
hcitool dev
>
Devices:
hci0 00:1A:7D:DA:**:**
如果蓝牙设备正常,可以看到返回当前蓝牙的 Mac 地址,如果蓝牙设备不能正常使用,则不会有返回信息,我们可以通过手动关闭蓝牙来实验以下。
sudo hciconifg hci0 down
hcitool dev
>
Devices:
sudo hciconfig hci0 up
hcitool dev
>
Devices:
hci0 00:1A:7D:DA:**:**
这里还有一个技巧,如果使用蓝牙扫描是出现
Set scan parameters failed: Input/output errer
的错误信息,可以使用上述的方法,手动重启蓝牙,即可解决问题。
接下来是对周围的低功耗蓝牙设备进行搜索:
hcitool lescan
>
LE Scan ...
20:91:**:B9:0B:** (unknown)
20:91:48:**:0B:EE UPots
20:91:48:B9:**:89 (unknown)
20:91:**:B9:08:89 UPots
20:91:48:**:0B:C2 (unknown)
20:91:48:B9:**:C2 UPots
20:91:48:**:0B:EE (unknown)
执行命令后,可以看到会不断打印出周围蓝牙设备的名称和 MAC 地址。
到这里为止,我们就完成对 hcitool 工具的基本了解了,下面将进入 python 编程部分。
我们在刚才是试验中,已经了解到,当使用 hcitool 时,该工具不会自动停止,而是会不断打印搜索结果。这里我们需要使用
pexpect
的
timeout
来控制运行一定时间后就自动停止。
import pexpect
# 这里的 timeout 设置为 3 ,即 3 秒后就会停止
scan = pexpect.spawn('hcitoon lescan, timeout=3)
在运行后,正常情况下的输出结果应该和上面类似,以 LE Scan … 开头,下面接着就是搜索出来的设备和 MAC 地址,我们可以通过用
expect
来获取结果。这里有个技巧,因为我们的 hcitool 会不断打印到时间停止,所以我们可以通过
expect
捕获随便一个不存在的字符,这样程序就会直接进入
pexpect.TIMEOUT
,我们再通过
before
就能拿到所有的打印结果,接而进一步对结果处理即可。
scan.expect('foooooo') # 设置捕获一个不存在的字符串即可
scan.before
>
b'LE Scan ...\r\n20:91:48:**:0B:C2 (unknown)\r\n
20:91:48:**:08:89 (unknown)\r\n
20:91:48:**:0B:EE (unknown)\r\n
20:91:48:**:0B:EE UPots\r\n
20:91:48:**:0B:C2 (unknown)\r\n
......
这里可以看到我们已经拿到了返回的结果。还有一点需要注意的,上面的返回结果是正常的情况,而实际上也会存在其他的异常情况,包括有
No such device
Set scan parameters failed: Input/output errer
及其他错误等。所以需要做一个异常处理:
try:
scan.expect('foooo')
except pexpect.EOF:
before_eof = scan.before.decode('utf-8', 'replace')
if "No such device" in before_eof:
message = "No BLE adapter found"
elif "Set scan parameters failed: Input/output errer" in before_eof:
message = ("BLE adapter requires reset after a scan as root""- call adapter.reset()")
else:
message = "Unexpected error when scanning %s" % before_eof
raise Exception(message)
接下来,我们就开始对返回结果进行处理。
首先,我们可以对返回结果进行
decode
以及换行分割处理
split
,这样就可以得到一个MAC地址和设备名称的数组:
scan.before.decode('utf-8', 'replace').split('\r\n')
>
['LE Scan ...',
'20:91:48:**:0B:C2 (unknown)',
'20:91:48:**:08:89 (unknown)',
'20:91:48:**:0B:EE (unknown)',
'20:91:48:**:0B:EE UPots',
...
]
接下来只需要通过正则表达式将MAC地址和设备名称分开,然后在筛选掉 unknown 的设备和重复的 MAC地址 即可,这部分的操作在
expect pexpect.TIMEOUT
下完成:
... # 上面的代码忽略
except pexpect.TIMEOUT:
devices = {}
for line in scan.before.decode('utf-8', 'replace').split('\r\n'):
match = re.match(r'(([0-9A-Fa-f][0-9A-Fa-f]:?){6}) (\(?.+\)?)', line)
if match is not None:
address = match.group(1)
name = match.group(3)
# print(match.group(3))
if name == "(unknown)":
name = None
if address in devices:
if (name is not None) and (address not in devices.keys()):
# 这里筛选掉 unknown 设备 和 重复的MAC地址设备
# 如果需要处理,可以在这里完成相应的逻辑
# 由于这里不需要,所以就直接使用 pass
pass
else:
if (name is not None) and (address not in devices.keys()):
print("Discovered %s (%s)" % (address, name))
devices[address] = {
'address': address,
'name': name
}
print('Found %d BLE devices' % len(devices))
这样,我们就可以获取到所有非 unknown 的设备了。到这里,我们的功能基本已经完成了,但还需要在程序执行完成后执行关闭 hcitool 的工作,这部分代码将在
finally
中实现:
... #上述代码省略
finally:
try:
scan.kill(signal.SIGINT)
while True:
try:
scan.read_nonblocking(size=100)
except (pexpect.TIMEOUT, pexpect.EOF):
break
if scan.isalive():
scan.wait()
except OSError:
print("Unable to gracefully stop the scan - "
"BLE adapter may need to be reset")
这里的代码将会在执行完毕后关闭 hcitool 进程。
以下为完整的代码展示:
import pexpect
import re
import signal
scan = pexpect.spawn('hcitool lescan', timeout=3)
try:
scan.expect('foooo')
except pexpect.EOF:
before_eof = scan.before.decode('utf-8', 'replace')
if "No such device" in before_eof:
message = "No BLE adapter found"
elif "Set scan parameters failed: Input/output errer" in before_eof:
message = ("BLE adapter requires reset after a scan as root""- call adapter.reset()")
else:
message = "Unexpected error when scanning %s" % before_eof
raise Exception(message)
except pexpect.TIMEOUT:
devices = {}
for line in scan.before.decode('utf-8', 'replace').split('\r\n'):
match = re.match(r'(([0-9A-Fa-f][0-9A-Fa-f]:?){6}) (\(?.+\)?)', line)
if match is not None:
address = match.group(1)
name = match.group(3)
# print(match.group(3))
if name == "(unknown)":
name = None
if address in devices:
if (name is not None) and (address not in devices.keys()):
pass
else:
if (name is not None) and (address not in devices.keys()):
print("Discovered %s (%s)" % (address, name))
devices[address] = {
'address': address,
'name': name
}
print('Found %d BLE devices' % len(devices))
finally:
try:
scan.kill(signal.SIGINT)
while True:
try:
scan.read_nonblocking(size=100)
except (pexpect.TIMEOUT, pexpect.EOF):
break
if scan.isalive():
scan.wait()
except OSError:
print("Unable to gracefully stop the scan - "
"BLE adapter may need to be reset")
运行上面的代码,如果正常就能打印搜索到的设备:
Discovered 20:91:48:**:08:89 (UPots)
Discovered 20:91:48:**:0B:EE (UPots)
Discovered 20:91:48:**:0B:C2 (UPots)
Discovered 58:2D:34:**:**:1E (MJ_HT_V1)
Discovered 58:80:3C:**:**:03 (Amazfit Watch-E435)
Discovered 38:18:4C:**:**:AE (LE_WH-1000XM3)
Found 6 BLE devices
本文从开始介绍 hcitool 工具的简单使用,以及后面一步步分解介绍如何使用
pexpect
配合
hcitool
完成扫描周围蓝牙设备的功能代码。
这里的代码只是演示和研究代码的实践,我们最终可以根据实际的需求,将上述代码进行封装,来实现后续的蓝牙功能开发。
如果你觉得文章对你用,记得关注收藏。你的关注和收藏是继续更新的动力哦。
文章来源互联网,如有侵权,请联系管理员删除。邮箱:417803890@qq.com / QQ:417803890
Python Free
邮箱:417803890@qq.com
QQ:417803890
皖ICP备19001818号-4
© 2019 copyright www.pythonf.cn - All rights reserved
微信扫一扫关注公众号:
Python Free