Skip to content

Commit 2d5ef97

Browse files
authored
Merge pull request #30 from tannewt/api_rework
Rework the API to use descriptors.
2 parents d4fda3e + cd77315 commit 2d5ef97

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2516
-1628
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ _build
44
.env
55
build*
66
bundles
7+
.DS_Store

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ deploy:
1717
install:
1818
- pip install -r requirements.txt
1919
- pip install circuitpython-build-tools Sphinx sphinx-rtd-theme
20-
- pip install --force-reinstall pylint==1.9.2
20+
- pip install --force-reinstall "pylint<3"
2121

2222
script:
23-
- pylint adafruit_ble/*.py
23+
- pylint --disable=too-few-public-methods adafruit_ble/**/*.py adafruit_ble/*.py
2424
- ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace
2525
examples/*.py)
2626
- circuitpython-build-bundles --filename_prefix adafruit-circuitpython-ble

README.rst

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,18 @@ Usage Example
3131

3232
.. code-block:: python
3333
34-
from adafruit_ble.uart import UARTServer
35-
36-
uart_server = UARTServer()
37-
uart_server.start_advertising()
38-
39-
# Wait for a connection.
40-
while not uart_server.connected:
41-
pass
42-
43-
uart_server.write('abc')
34+
from adafruit_ble import SmartAdapter
35+
36+
adapter = SmartAdapter()
37+
print("scanning")
38+
found = set()
39+
for entry in adapter.start_scan(timeout=60, minimum_rssi=-80):
40+
addr = entry.address
41+
if addr not in found:
42+
print(entry)
43+
found.add(addr)
44+
45+
print("scan done")
4446
4547
4648
Contributing

adafruit_ble/__init__.py

100644100755
Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# The MIT License (MIT)
22
#
33
# Copyright (c) 2019 Dan Halbert for Adafruit Industries
4+
# Copyright (c) 2019 Scott Shawcroft for Adafruit Industries
45
#
56
# Permission is hereby granted, free of charge, to any person obtaining a copy
67
# of this software and associated documentation files (the "Software"), to deal
@@ -26,7 +27,7 @@
2627
This module provides higher-level BLE (Bluetooth Low Energy) functionality,
2728
building on the native `_bleio` module.
2829
29-
* Author(s): Dan Halbert for Adafruit Industries
30+
* Author(s): Dan Halbert and Scott Shawcroft for Adafruit Industries
3031
3132
Implementation Notes
3233
--------------------
@@ -42,7 +43,148 @@
4243
4344
"""
4445

45-
# imports
46+
import board
47+
import _bleio
48+
49+
from .services import Service
50+
from .advertising import Advertisement
4651

4752
__version__ = "0.0.0-auto.0"
4853
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
54+
55+
class BLEConnection:
56+
"""This represents a connection to a peer BLE device.
57+
58+
It acts as a map from a Service type to a Service instance for the connection.
59+
"""
60+
def __init__(self, connection):
61+
self._connection = connection
62+
self._discovered_services = {}
63+
"""These are the bare remote services from _bleio."""
64+
65+
self._constructed_services = {}
66+
"""These are the Service instances from the library that wrap the remote services."""
67+
68+
def _discover_remote(self, uuid):
69+
remote_service = None
70+
if uuid in self._discovered_services:
71+
remote_service = self._discovered_services[uuid]
72+
else:
73+
results = self._connection.discover_remote_services((uuid.bleio_uuid,))
74+
if results:
75+
remote_service = results[0]
76+
self._discovered_services[uuid] = remote_service
77+
return remote_service
78+
79+
def __contains__(self, key):
80+
uuid = key
81+
if hasattr(key, "uuid"):
82+
uuid = key.uuid
83+
return self._discover_remote(uuid) is not None
84+
85+
def __getitem__(self, key):
86+
uuid = key
87+
maybe_service = False
88+
if hasattr(key, "uuid"):
89+
uuid = key.uuid
90+
maybe_service = True
91+
92+
remote_service = self._discover_remote(uuid)
93+
94+
if uuid in self._constructed_services:
95+
return self._constructed_services[uuid]
96+
if remote_service:
97+
constructed_service = None
98+
if maybe_service:
99+
constructed_service = key(service=remote_service)
100+
self._constructed_services[uuid] = constructed_service
101+
return constructed_service
102+
103+
raise KeyError("{!r} object has no service {}".format(self, key))
104+
105+
@property
106+
def connected(self):
107+
"""True if the connection to the peer is still active."""
108+
return self._connection.connected
109+
110+
def disconnect(self):
111+
"""Disconnect from peer."""
112+
self._connection.disconnect()
113+
114+
class BLERadio:
115+
"""The BLERadio class enhances the normal `_bleio.Adapter`.
116+
117+
It uses the library's `Advertisement` classes and the `BLEConnection` class."""
118+
119+
def __init__(self, adapter=None):
120+
if not adapter:
121+
adapter = _bleio.adapter
122+
self._adapter = adapter
123+
self._current_advertisement = None
124+
self._connection_cache = {}
125+
126+
def start_advertising(self, advertisement, scan_response=None, **kwargs):
127+
"""Starts advertising the given advertisement.
128+
129+
It takes most kwargs of `_bleio.Adapter.start_advertising`."""
130+
scan_response_data = None
131+
if scan_response:
132+
scan_response_data = bytes(scan_response)
133+
self._adapter.start_advertising(bytes(advertisement),
134+
scan_response=scan_response_data,
135+
connectable=advertisement.connectable,
136+
**kwargs)
137+
138+
def stop_advertising(self):
139+
"""Stops advertising."""
140+
self._adapter.stop_advertising()
141+
142+
def start_scan(self, *advertisement_types, **kwargs):
143+
"""Starts scanning. Returns an iterator of advertisement objects of the types given in
144+
advertisement_types. The iterator will block until an advertisement is heard or the scan
145+
times out.
146+
147+
If any ``advertisement_types`` are given, only Advertisements of those types are produced
148+
by the returned iterator. If none are given then `Advertisement` objects will be
149+
returned."""
150+
prefixes = b""
151+
if advertisement_types:
152+
prefixes = b"".join(adv.prefix for adv in advertisement_types)
153+
for entry in self._adapter.start_scan(prefixes=prefixes, **kwargs):
154+
adv_type = Advertisement
155+
for possible_type in advertisement_types:
156+
if possible_type.matches(entry) and issubclass(possible_type, adv_type):
157+
adv_type = possible_type
158+
advertisement = adv_type.from_entry(entry)
159+
if advertisement:
160+
yield advertisement
161+
162+
def stop_scan(self):
163+
"""Stops any active scan.
164+
165+
The scan results iterator will return any buffered results and then raise StopIteration
166+
once empty."""
167+
self._adapter.stop_scan()
168+
169+
def connect(self, advertisement, *, timeout=4):
170+
"""Initiates a `BLEConnection` to the peer that advertised the given advertisement."""
171+
connection = self._adapter.connect(advertisement.address, timeout=timeout)
172+
self._connection_cache[connection] = BLEConnection(connection)
173+
return self._connection_cache[connection]
174+
175+
@property
176+
def connected(self):
177+
"""True if any peers are connected to the adapter."""
178+
return self._adapter.connected
179+
180+
@property
181+
def connections(self):
182+
"""A tuple of active `BLEConnection` objects."""
183+
connections = self._adapter.connections
184+
wrapped_connections = [None] * len(connections)
185+
for i, connection in enumerate(self._adapter.connections):
186+
if connection not in self._connection_cache:
187+
self._connection_cache[connection] = BLEConnection(connection)
188+
wrapped_connections[i] = self._connection_cache[connection]
189+
190+
return tuple(wrapped_connections)

0 commit comments

Comments
 (0)