8000 doc and cleanup pass by dhalbert · Pull Request #32 · adafruit/Adafruit_CircuitPython_BLE · GitHub
[go: up one dir, main page]

Skip to content

doc and cleanup pass #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ Usage Example

.. code-block:: python

from adafruit_ble import SmartAdapter
from adafruit_ble import BLERadio

adapter = SmartAdapter()
radio = BLERadio()
print("scanning")
found = set()
for entry in adapter.start_scan(timeout=60, minimum_rssi=-80):
for entry in radio.start_scan(timeout=60, minimum_rssi=-80):
addr = entry.address
if addr not in found:
print(entry)
Expand Down
119 changes: 87 additions & 32 deletions adafruit_ble/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
**Hardware:**

Adafruit Feather nRF52840 Express <https://www.adafruit.com/product/4062>
Adafruit Circuit Playground Bluefruit <https://www.adafruit.com/product/4333>

**Software and Dependencies:**

Expand All @@ -53,46 +54,61 @@
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"

class BLEConnection:
"""This represents a connection to a peer BLE device.

It acts as a map from a Service type to a Service instance for the connection.
"""
def __init__(self, connection):
self._connection = connection
self._discovered_services = {}
"""These are the bare remote services from _bleio."""
Represents a connection to a peer BLE device.
It acts as a map from a `Service` type to a `Service` instance for the connection.

:param bleio_connection _bleio.Connection: the native `_bleio.Connection` object to wrap

"""
def __init__(self, bleio_connection):
self._bleio_connection = bleio_connection
# _bleio.Service objects representing services found during discovery.
self._discovered_bleio_services = {}
# Service objects that wrap remote services.
self._constructed_services = {}
"""These are the Service instances from the library that wrap the remote services."""

def _discover_remote(self, uuid):
remote_service = None
if uuid in self._discovered_services:
remote_service = self._discovered_services[uuid]
if uuid in self._discovered_bleio_services:
remote_service = self._discovered_bleio_services[uuid]
else:
results = self._connection.discover_remote_services((uuid.bleio_uuid,))
results = self._bleio_connection.discover_remote_services((uuid.bleio_uuid,))
if results:
remote_service = results[0]
self._discovered_services[uuid] = remote_service
self._discovered_bleio_services[uuid] = remote_service
return remote_service

def __contains__(self, key):
"""
Allows easy testing for a particular Service class or a particular UUID
associated with this connection.

Example::

if UARTService in connection:
# do something

if StandardUUID(0x1234) in connection:
# do something
"""
uuid = key
if hasattr(key, "uuid"):
uuid = key.uuid
return self._discover_remote(uuid) is not None

def __getitem__(self, key):
"""Return the Service for the given Service class or uuid, if any."""
uuid = key
maybe_service = False
if hasattr(key, "uuid"):
uuid = key.uuid
maybe_service = True

remote_service = self._discover_remote(uuid)

if uuid in self._constructed_services:
return self._constructed_services[uuid]

remote_service = self._discover_remote(uuid)
if remote_service:
constructed_service = None
if maybe_service:
Expand All @@ -105,16 +121,19 @@ def __getitem__(self, key):
@property
def connected(self):
"""True if the connection to the peer is still active."""
return self._connection.connected
return self._bleio_connection.connected

def disconnect(self):
"""Disconnect from peer."""
self._connection.disconnect()
self._bleio_connection.disconnect()

class BLERadio:
"""The BLERadio class enhances the normal `_bleio.Adapter`.
"""
BLERadio provides the interfaces for BLE advertising,
scanning for advertisements, and connecting to peers. There may be
multiple connections active at once.

It uses the library's `Advertisement` classes and the `BLEConnection` class."""
It uses this library's `Advertisement` classes and the `BLEConnection` class."""

def __init__(self, adapter=None):
if not adapter:
Expand All @@ -123,34 +142,63 @@ def __init__(self, adapter=None):
self._current_advertisement = None
self._connection_cache = {}

def start_advertising(self, advertisement, scan_response=None, **kwargs):
"""Starts advertising the given advertisement.
def start_advertising(self, advertisement, scan_response=None, interval=0.1):
"""
Starts advertising the given advertisement.

It takes most kwargs of `_bleio.Adapter.start_advertising`."""
:param buf scan_response: scan response data packet bytes.
``None`` if no scan response is needed.
:param float interval: advertising interval, in seconds
"""
scan_response_data = None
if scan_response:
scan_response_data = bytes(scan_response)
self._adapter.start_advertising(bytes(advertisement),
scan_response=scan_response_data,
connectable=advertisement.connectable,
**kwargs)
interval=interval)

def stop_advertising(self):
"""Stops advertising."""
self._adapter.stop_advertising()

def start_scan(self, *advertisement_types, **kwargs):
"""Starts scanning. Returns an iterator of advertisement objects of the types given in
advertisement_types. The iterator will block until an advertisement is heard or the scan
times out.

If any ``advertisement_types`` are given, only Advertisements of those types are produced
by the returned iterator. If none are given then `Advertisement` objects will be
returned."""
def start_scan(self, *advertisement_types, buffer_size=512, extended=False, timeout=None,
interval=0.1, window=0.1, minimum_rssi=-80, active=True):
"""
Starts scanning. Returns an iterator of advertisement objects of the types given in
advertisement_types. The iterator will block until an advertisement is heard or the scan
times out.

If any ``advertisement_types`` are given, only Advertisements of those types are produced
by the returned iterator. If none are given then `Advertisement` objects will be
returned.

Advertisements and scan responses are filtered and returned separately.

:param int buffer_size: the maximum number of advertising bytes to buffer.
:param bool extended: When True, support extended advertising packets.
Increasing buffer_size is recommended when this is set.
:param float timeout: the scan timeout in seconds.
If None, will scan until `stop_scan` is called.
:param float interval: the interval (in seconds) between the start
of two consecutive scan windows
Must be in the range 0.0025 - 40.959375 seconds.
:param float window: the duration (in seconds) to scan a single BLE channel.
window must be <= interval.
:param int minimum_rssi: the minimum rssi of entries to return.
:param bool active: request and retrieve scan responses for scannable advertisements.
:return: If any ``advertisement_types`` are given,
only Advertisements of those types are produced by the returned iterator.
If none are given then `Advertisement` objects will be returned.
:rtype: iterable
"""
prefixes = b""
if advertisement_types:
prefixes = b"".join(adv.prefix for adv in advertisement_types)
for entry in self._adapter.start_scan(prefixes=prefixes, **kwargs):
for entry in self._adapter.start_scan(prefixes=prefixes, buffer_size=buffer_size,
extended=extended, timeout=timeout,
interval=interval, window=window,
minimum_rssi=minimum_rssi, active=active):
adv_type = Advertisement
for possible_type in advertisement_types:
if possible_type.matches(entry) and issubclass(possible_type, adv_type):
Expand All @@ -167,7 +215,14 @@ def stop_scan(self):
self._adapter.stop_scan()

def connect(self, advertisement, *, timeout=4):
"""Initiates a `BLEConnection` to the peer that advertised the given advertisement."""
"""
Initiates a `BLEConnection` to the peer that advertised the given advertisement.

:param advertisement Advertisement: An `Advertisement` or a subclass of `Advertisement`
:param timeout float: how long to wait for a connection
:return: the connection to the peer
:rtype: BLEConnection
"""
connection = self._adapter.connect(adver 9E88 tisement.address, timeout=timeout)
self._connection_cache[connection] = BLEConnection(connection)
return self._connection_cache[connection]
Expand Down
10 changes: 4 additions & 6 deletions adafruit_ble/advertising/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@

import struct

def to_hex(b):
def to_hex(seq):
"""Pretty prints a byte sequence as hex values."""
# pylint: disable=invalid-name
return " ".join(["{:02x}".format(v) for v in b])
return " ".join("{:02x}".format(v) for v in seq)

def to_bytes_literal(b):
def to_bytes_literal(seq):
"""Prints a byte sequence as a Python bytes literal that only uses hex encoding."""
# pylint: disable=invalid-name
return "b\"" + "".join(["\\x{:02x}".format(v) for v in b]) + "\""
return "b\"" + "".join("\\x{:02x}".format(v) for v in seq) + "\""

def decode_data(data, *, key_encoding="B"):
"""Helper which decodes length encoded structures into a dictionary with the given key
Expand Down
13 changes: 7 additions & 6 deletions adafruit_ble/advertising/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def __contains__(self, key):
return uuid in self._vendor_services or uuid in self._standard_services

def _update(self, adt, uuids):
if len(uuids) == 0:
if not uuids:
# uuids is empty
del self._advertisement.data_dict[adt]
uuid_length = uuids[0].size // 8
b = bytearray(len(uuids) * uuid_length)
Expand Down Expand Up @@ -131,12 +132,12 @@ def _present(self, obj):
def __get__(self, obj, cls):
if not self._present(obj) and not obj.mutable:
return None
if not hasattr(obj, "_service_lists"):
obj._service_lists = {}
if not hasattr(obj, "adv_service_lists"):
obj.adv_service_lists = {}
first_adt = self.standard_services[0]
if first_adt not in obj._service_lists:
obj._service_lists[first_adt] = BoundServiceList(obj, **self.__dict__)
return obj._service_lists[first_adt]
if first_adt not in obj.adv_service_lists:
obj.adv_service_lists[first_adt] = BoundServiceList(obj, **self.__dict__)
return obj.adv_service_lists[first_adt]

class ProvideServicesAdvertisement(Advertisement):
"""Advertise what services that the device makes available upon connection."""
Expand Down
72 changes: 72 additions & 0 deletions adafruit_ble/attributes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# The MIT License (MIT)
#
# Copyright (c) 2019 Dan Halbert for Adafruit Industries
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
:py:mod:`~adafruit_ble.attributes`
====================================================

This module provides definitions common to all kinds of BLE attributes,
specifically characteristics and descriptors.

"""
import _bleio

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"

class Attribute:
"""Constants describing security levels.

.. data:: NO_ACCESS

security mode: access not allowed

.. data:: OPEN

security_mode: no security (link is not encrypted)

.. data:: ENCRYPT_NO_MITM

security_mode: unauthenticated encryption, without man-in-the-middle protection

.. data:: ENCRYPT_WITH_MITM

security_mode: authenticated encryption, with man-in-the-middle protection

.. data:: LESC_ENCRYPT_WITH_MITM

security_mode: LESC encryption, with man-in-the-middle protection

.. data:: SIGNED_NO_MITM

security_mode: unauthenticated data signing, without man-in-the-middle protection

.. data:: SIGNED_WITH_MITM

security_mode: authenticated data signing, without man-in-the-middle protection
"""
NO_ACCESS = _bleio.Attribute.NO_ACCESS
OPEN = _bleio.Attribute.OPEN
ENCRYPT_NO_MITM = _bleio.Attribute.ENCRYPT_NO_MITM
ENCRYPT_WITH_MITM = _bleio.Attribute.ENCRYPT_WITH_MITM
LESC_ENCRYPT_WITH_MITM = _bleio.Attribute.LESC_ENCRYPT_WITH_MITM
SIGNED_NO_MITM = _bleio.Attribute.SIGNED_NO_MITM
SIGNED_WITH_MITM = _bleio.Attribute.SIGNED_NO_MITM
Loading
0