bluepy_helper_cap.py: Check bluepy-helper capability at scratch-link run

To run bluepy-scratch-link as a normal user bluepy-helper must have
cap_net_admin and cap_net_raw capabilities to connect to BLE devices
using bluepy. When it lacks these capabilities, bluepy-scratch-link
fails to discover BLE devices without message about the failure reason.

To inform users the reason of BLE device detection failure, check
capabilities of bluepy-helper when discovery of BLE device is requested.
If the capabilities are not set, print error messages and request users
to run script to set the capabilities. Implement this features in
bluepy_helper_cap.py.

In addition, re-implement bash script setcap.sh feature as python and
added to bluepy_helper_cap.py to simplify the code set.

Signed-off-by: Shin'ichiro Kawasaki <kawasaki@juno.dti.ne.jp>
This commit is contained in:
Shin'ichiro Kawasaki
2020-09-20 20:51:44 +09:00
parent 708a8a6f28
commit e706263fea
4 changed files with 73 additions and 9 deletions

View File

@@ -76,9 +76,8 @@ Installation
5. Set bluepy-helper capability
```
./setcap.sh
Set up bluepy-helper capability to allow use by normal users
/usr/lib/python3.8/site-packages/bluepy-1.3.0-py3.8.egg/bluepy/bluepy-helper = cap_net_admin,cap_net_raw+eip
$ sudo ./bluepy_helper_cap.py
Set capacbility 'cap_net_raw,cap_net_admin' to /usr/lib/python3.8/site-packages/bluepy-1.3.0-py3.8.egg/bluepy/bluepy-helper
```
6. If using a micro:bit, install Scratch-link hex on your device.

65
bluepy_helper_cap.py Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python
import sys
import os
import shutil
import bluepy
import subprocess
import logging
logLevel = logging.INFO
# for logging
logger = logging.getLogger(__name__)
formatter = logging.Formatter(fmt='%(asctime)s %(message)s')
handler = logging.StreamHandler()
handler.setLevel(logLevel)
handler.setFormatter(formatter)
logger.setLevel(logLevel)
logger.addHandler(handler)
logger.propagate = False
# Check dependent tools
DEPENDENT_TOOLS = {
"setcap": "libcap2-bin (Ubuntu) or libcap (Arch)",
}
for cmd in DEPENDENT_TOOLS:
if not shutil.which(cmd):
print(f"'{cmd}' not found. Install package {DEPENDENT_TOOLS[cmd]}.")
sys.exit(1)
def helper_path():
path = os.path.abspath(bluepy.__file__)
if not path:
logger.error("Bluepy module not found")
sys.exit(1)
if path.find("__init__.py") < 0:
logger.error(f"Unexpected bluepy module path: {path}")
sys.exit(1)
path = path.replace("__init__.py", "bluepy-helper")
return path
def is_set():
path = helper_path()
p = subprocess.run(["getcap", path], capture_output=True)
if p.returncode !=0:
logger.error(f"Failed to get capability of {path}")
return False
out = str(p.stdout)
return out.find("cap_net_admin") >= 0 and out.find("cap_net_raw") >= 0
def setcap():
path = helper_path()
if is_set():
return True
p = subprocess.run(["setcap", "cap_net_raw,cap_net_admin+eip", path], \
capture_output=True)
if p.returncode !=0:
logger.error(f"Failed to set capability to {path}")
return False
print(f"Set capacbility 'cap_net_raw,cap_net_admin' to {path}")
return True
if __name__ == "__main__":
setcap()

View File

@@ -21,6 +21,7 @@ import bluetooth
# for BLESession (e.g. BBC micro:bit)
from bluepy.btle import Scanner, UUID, Peripheral, DefaultDelegate
from bluepy.btle import BTLEDisconnectError, BTLEManagementError
import bluepy_helper_cap
import threading
import time
@@ -518,6 +519,11 @@ class BLESession(Session):
err_msg = None
if self.status == self.INITIAL and method == 'discover':
if not bluepy_helper_cap.is_set():
logger.error("Capability is not set to bluepy helper.")
logger.error("Run bluepy_setcap.py with root privilege.")
logger.error("e.g. $ sudo bluepy_helper_cap.py")
sys.exit(1)
found_ifaces = 0
for i in range(self.MAX_SCANNER_IF):
scanner = Scanner(iface=i)

View File

@@ -1,6 +0,0 @@
#!/bin/sh
echo "Set up bluepy-helper capability to allow use by normal users"
find /usr -name bluepy-helper -exec sudo setcap \
'cap_net_raw,cap_net_admin+eip' {} \; 2> /dev/null
find /usr -name bluepy-helper -exec sudo getcap {} \; 2> /dev/null