Skip to content

Commit dd0344e

Browse files
committed
Initial commit
0 parents  commit dd0344e

File tree

11 files changed

+1100
-0
lines changed

11 files changed

+1100
-0
lines changed

.gitignore

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
27+
# PyInstaller
28+
# Usually these files are written by a python script from a template
29+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
30+
*.manifest
31+
*.spec
32+
33+
# Installer logs
34+
pip-log.txt
35+
pip-delete-this-directory.txt
36+
37+
# Unit test / coverage reports
38+
htmlcov/
39+
.tox/
40+
.coverage
41+
.coverage.*
42+
.cache
43+
nosetests.xml
44+
coverage.xml
45+
*,cover
46+
.hypothesis/
47+
48+
# Translations
49+
*.mo
50+
*.pot
51+
52+
# Django stuff:
53+
*.log
54+
local_settings.py
55+
56+
# Flask stuff:
57+
instance/
58+
.webassets-cache
59+
60+
# Scrapy stuff:
61+
.scrapy
62+
63+
# Sphinx documentation
64+
docs/_build/
65+
66+
# PyBuilder
67+
target/
68+
69+
# IPython Notebook
70+
.ipynb_checkpoints
71+
72+
# pyenv
73+
.python-version
74+
75+
# celery beat schedule file
76+
celerybeat-schedule
77+
78+
# dotenv
79+
.env
80+
81+
# virtualenv
82+
venv/
83+
ENV/
84+
85+
# Spyder project settings
86+
.spyderproject
87+
88+
# Rope project settings
89+
.ropeproject

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Senic GmbH
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Bluetooth GATT SDK for Python
2+
The Bluetooth GATT SDK for Python helps you implementing and communicating with any Bluetooth Low Energy device that has a GATT profile. As of now it supports:
3+
4+
* Discovering nearby Bluetooth Low Energy devices
5+
* Connecting and disconnecting devices
6+
* Implementing your custom GATT profile
7+
* Accessing all GATT services
8+
* Accessing all GATT characteristics
9+
* Reading characteristic values
10+
* Writing characteristic values
11+
* Subscribing for characteristic value change notifications
12+
13+
Currently Linux is the only platform supported by this library. Unlike other libraries this GATT SDK is based directly on the mature and stable D-Bus API of BlueZ to interact with Bluetooth devices. In the future we would like to make this library platform-independent by integrating with more Bluetooth APIs of other operating systems such as MacOS and Windows.
14+
15+
## Prerequisites
16+
The GATT SDK requires [Python 3.4+](https://www.python.org). Currently Linux is the only supported operating system and therefor it needs a recent installation of [BlueZ](http://www.bluez.org/). It is tested to work fine with BlueZ 5.44, slightly older versions should however work, too.
17+
18+
## Installation
19+
These instructions assume a Debian-based Linux.
20+
21+
On Linux the [BlueZ](http://www.bluez.org/) library is necessary to access your built-in Bluetooth controller or Bluetooth USB dongle. Some Linux distributions provide a more up-to-date BlueZ package, some other distributions only install older versions that don't implement all Bluetooth features needed for this SDK. In those cases you want to either update BlueZ or build it from sources.
22+
23+
### Updating/installing BlueZ via apt-get
24+
25+
1. `bluetoothd --version` Obtains the version of the pre-installed BlueZ. `bluetoothd` daemon must run at startup to expose the Bluetooth API via D-Bus.
26+
2. `sudo apt-get install --no-install-recommends bluetooth` Installs BlueZ
27+
3. If the installed version is too old, proceed with next step: [Installing BlueZ from sources](#installing-bluez-from-sources)
28+
29+
### Installing BlueZ from sources
30+
31+
The `bluetoothd` daemon provides BlueZ's D-Bus interfaces that is accessed by the GATT SDK to communicate with Bluetooth devices. The following commands download BlueZ 5.44 sources, built them and replace any pre-installed `bluetoothd` daemon. It's not suggested to remove any pre-installed BlueZ package as its deinstallation might remove necessary Bluetooth drivers as well.
32+
33+
1. `sudo systemctl stop bluetooth`
34+
2. `sudo apt-get update`
35+
3. `sudo apt-get install libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev libdbus-glib-1-dev unzip`
36+
4. `cd`
37+
5. `mkdir bluez`
38+
6. `cd bluez`
39+
7. `wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.44.tar.xz`
40+
8. `tar xf bluez-5.44.tar.xz`
41+
9. `cd bluez-5.44`
42+
10. `./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-library`
43+
11. `make`
44+
12. `sudo make install`
45+
13. `sudo ln -svf /usr/libexec/bluetooth/bluetoothd /usr/sbin/`
46+
14. `sudo install -v -dm755 /etc/bluetooth`
47+
15. `sudo install -v -m644 src/main.conf /etc/bluetooth/main.conf`
48+
16. `sudo systemctl daemon-reload`
49+
17. `sudo systemctl start bluetooth`
50+
18. `bluetoothd --version` # should now print 5.44
51+
52+
Please note that some distributions might use a different directory for system deamons, apply step 13 only as needed.
53+
54+
### Enabling your Bluetooth adapter
55+
56+
1. `sudo hciconfig hci0 up` Enables your built-in Bluetooth adapter or external Bluetooth USB dongle
57+
58+
### Using BlueZ commandline tools
59+
BlueZ also provides an interactive commandline tool to interact with Bluetooth devices. You know that your BlueZ installation is working fine if it discovers any Bluetooth devices nearby.
60+
61+
`sudo bluetoothctl` Starts an interactive mode to talk to BlueZ
62+
* `power on` Enables the Bluetooth adapter
63+
* `scan on` Start Bluetooth device scanning and lists all found devices with MAC addresses
64+
* `connect AA:BB:CC:DD:EE:FF` Connects to a Bluetooth device with specified MAC address
65+
* `exit` Quits the interactive mode
66+
67+
### Installing GATT SDK for Python
68+
69+
To install this GATT module and the Python3 D-Bus dependency globally, run:
70+
71+
```
72+
sudo pip3 install gatt
73+
sudo apt-get install python3-dbus
74+
```
75+
76+
#### Running the GATT control script
77+
78+
To test if your setup is working, run the `gattctl` tool that is part of this SDK. Note that it must be run as root because on Linux, Bluetooth discovery by default is a restricted operation.
79+
80+
```
81+
sudo gattctl --discover
82+
sudo gattctl --connect AA:BB:CC:DD:EE:FF # Replace the MAC address with your Bluetooth device's MAC address
83+
sudo gattctl --help # To list all available commands
84+
```
85+
86+
## SDK Usage
87+
88+
This SDK requires you to create subclasses of `gatt.DeviceManager` and `gatt.Device`. The other two classes `gatt.Service` and `gatt.Characteristic` are not supposed to be subclassed.
89+
90+
`gatt.DeviceManager` manages all known Bluetooth devices and provides a device discovery to discover nearby Bluetooth Low Energy devices. You want to subclass this manager to access Bluetooth devices as they are discovered as well as to restrict the set of devices to those that you actually want to support by your manager implementation. By default `gatt.DeviceManager` discovers and returns all Bluetooth devices but you can restrict that by overriding `gatt.DeviceManager.make_device()`.
91+
92+
`gatt.Device` is the base class for your Bluetooth device. You will need to subclass it to implement the Bluetooth GATT profile of your choice. Override `gatt.Device.services_resolved()` to interact with the GATT profile, i.e. start reading from and writing to characteristics or subscribe to characteristic value change notifications.
93+
94+
### Discovering nearby Bluetooth Low Energy devices
95+
96+
The SDK entry point is the `DeviceManager` class. Check the following example to dicover any Bluetooth Low Energy device nearby.
97+
98+
```python
99+
import gatt
100+
101+
class AnyDeviceManager(gatt.DeviceManager):
102+
def device_discovered(self, device):
103+
print("Discovered [%s] %s" % (device.mac_address, device.alias()))
104+
105+
manager = AnyDeviceManager(adapter_name='hci0')
106+
manager.start_discovery()
107+
manager.run()
108+
```
109+
110+
Please note that communication with your Bluetooth adapter happens over BlueZ's D-Bus API, hence an event loop needs to be run in order to receive all Bluetooth related events. You can start and stop the event loop via `run()` and `stop()` calls to your `DeviceManager` instance.
111+
112+
### Connecting to a Bluetooth Low Energy device and printing all its information
113+
114+
Once `gatt.DeviceManager` has discovered a Bluetooth device you can use the `gatt.Device` instance that you retrieved from `gatt.DeviceManager.device_discovered()` to connect to it. Alternatively you can create a new instance of `gatt.Device` using the name of your Bluetooth adapter (typically `hci0`) and the device's MAC address.
115+
116+
The following implementation of `gatt.Device` connects to any Bluetooth device and prints all relevant events:
117+
118+
```python
119+
class AnyDevice(gatt.Device):
120+
def connect_succeeded(self):
121+
super().connect_succeeded()
122+
print("[%s] Connected" % (self.mac_address))
123+
124+
def connect_failed(self, error):
125+
super().connect_failed(error)
126+
print("[%s] Connection failed: %s" % (self.mac_address, str(error)))
127+
128+
def disconnect_succeeded(self):
129+
super().disconnect_succeeded()
130+
print("[%s] Disconnected" % (self.mac_address))
131+
132+
def services_resolved(self):
133+
super().services_resolved()
134+
135+
print("[%s] Resolved services" % (self.mac_address))
136+
for service in self.services:
137+
print("[%s] Service [%s]" % (self.mac_address, service.uuid))
138+
for characteristic in service.characteristics:
139+
print("[%s] Characteristic [%s]" % (self.mac_address, characteristic.uuid))
140+
141+
142+
device = AnyDevice(adapter_name='hci0', mac_address='AA:BB:CC:DD:EE:FF')
143+
device.connect()
144+
145+
manager = gatt.DeviceManager(adapter_name='hci0')
146+
manager.run()
147+
```
148+
149+
As with device discovery, remember to start the Bluetooth event loop with `gatt.DeviceManager.run()`.
150+
151+
### Reading and writing characteristic values
152+
153+
As soon as `gatt.Device.services_resolved()` has been called by the SDK, you can access all GATT services and characteristics. Services are stored in the `services` attribute of `gatt.Device` and each `gatt.Service` instance has a `characteristics` attribute.
154+
155+
To read a characteristic value first get the characteristic and then call `read_value()`. `gatt.Device.characteristic_value_updated()` will be called when the value has been retrieved.
156+
157+
The following example reads the device's firmware version after all services and characteristics have been resolved:
158+
159+
```python
160+
import gatt
161+
162+
class AnyDevice(gatt.Device):
163+
def services_resolved(self):
164+
super().services_resolved()
165+
166+
device_information_service = next(
167+
s for s in self.services
168+
if s.uuid == '0000180a-0000-1000-8000-00805f9b34fb')
169+
170+
firmware_version_characteristic = next(
171+
c for c in device_information_service.characteristics
172+
if c.uuid == '00002a26-0000-1000-8000-00805f9b34fb')
173+
174+
firmware_version_characteristic.read_value()
175+
176+
def characteristic_value_updated(self, characteristic, value):
177+
print("Firmware version:", value.decode("utf-8"))
178+
179+
180+
device = AnyDevice(adapter_name='hci0', mac_address='AA:BB:CC:DD:EE:FF')
181+
device.connect()
182+
183+
manager = gatt.DeviceManager(adapter_name='hci0')
184+
manager.run()
185+
```
186+
187+
To write a characteristic value simply call `write_value(value)` on the characteristic with `value` being an array of bytes. Then `characteristic_write_value_succeeded()` or `characteristic_write_value_failed(error)` will be called on your `gatt.Device` instance.
188+
189+
### Subscribing for characteristic value changes
190+
191+
To subscribe for characteristic value change notifications call `enable_notifications()` on the characteristic. Then, on your `gatt.Device` instance, `characteristic_enable_notification_succeeded()` or `characteristic_enable_notification_failed()` will be called. Every time the Bluetooth device sends a new value, `characteristic_value_updated()` will be called.
192+
193+
## Support
194+
195+
Please open an issue or drop us an email to [[email protected]](mailto:[email protected]).
196+
197+
## Contributing
198+
199+
Contributions are welcome via pull requests. Please open an issue first in case you want to discus your possible improvements to this SDK.
200+
201+
## License
202+
203+
The GATT SDK for Python is available under the MIT License.

examples/connect.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import gatt
2+
3+
from argparse import ArgumentParser
4+
5+
6+
class AnyDevice(gatt.Device):
7+
def connect_succeeded(self):
8+
super().connect_succeeded()
9+
print("[%s] Connected" % (self.mac_address))
10+
11+
def connect_failed(self, error):
12+
super().connect_failed(error)
13+
print("[%s] Connection failed: %s" % (self.mac_address, str(error)))
14+
15+
def disconnect_succeeded(self):
16+
super().disconnect_succeeded()
17+
print("[%s] Disconnected" % (self.mac_address))
18+
19+
def services_resolved(self):
20+
super().services_resolved()
21+
22+
print("[%s] Resolved services" % (self.mac_address))
23+
for service in self.services:
24+
print("[%s] Service [%s]" % (self.mac_address, service.uuid))
25+
for characteristic in service.characteristics:
26+
print("[%s] Characteristic [%s]" % (self.mac_address, characteristic.uuid))
27+
28+
29+
arg_parser = ArgumentParser(description="GATT Connect Demo")
30+
arg_parser.add_argument('mac_address', help="MAC address of device to connect")
31+
args = arg_parser.parse_args()
32+
33+
print("Connecting...")
34+
35+
device = AnyDevice(adapter_name='hci0', mac_address=args.mac_address)
36+
device.connect()
37+
38+
manager = gatt.DeviceManager(adapter_name='hci0')
39+
manager.run()

examples/discovery.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import gatt
2+
3+
class AnyDeviceManager(gatt.DeviceManager):
4+
def device_discovered(self, device):
5+
print("[%s] Discovered, alias = %s" % (device.mac_address, device.alias()))
6+
7+
manager = AnyDeviceManager(adapter_name='hci0')
8+
manager.start_discovery()
9+
manager.run()

examples/read_firmware_version.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import gatt
2+
3+
from argparse import ArgumentParser
4+
5+
class AnyDevice(gatt.Device):
6+
def services_resolved(self):
7+
super().services_resolved()
8+
9+
device_information_service = next(
10+
s for s in self.services
11+
if s.uuid == '0000180a-0000-1000-8000-00805f9b34fb')
12+
13+
firmware_version_characteristic = next(
14+
c for c in device_information_service.characteristics
15+
if c.uuid == '00002a26-0000-1000-8000-00805f9b34fb')
16+
17+
firmware_version_characteristic.read_value()
18+
19+
def characteristic_value_updated(self, characteristic, value):
20+
print("Firmware version:", value.decode("utf-8"))
21+
22+
23+
arg_parser = ArgumentParser(description="GATT Read Firmware Version Demo")
24+
arg_parser.add_argument('mac_address', help="MAC address of device to connect")
25+
args = arg_parser.parse_args()
26+
27+
device = AnyDevice(adapter_name='hci0', mac_address=args.mac_address)
28+
device.connect()
29+
30+
manager = gatt.DeviceManager(adapter_name='hci0')
31+
manager.run()

gatt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .gatt import DeviceManager, Device, Service, Characteristic

0 commit comments

Comments
 (0)