Merge pull request #32 from dhalbert/doc-and-cleanup

doc and cleanup pass
This commit is contained in:
Dan Halbert 2019-11-14 20:05:06 -05:00 committed by GitHub
commit e6679abe54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 430 additions and 186 deletions

View file

@ -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)

View file

@ -35,6 +35,7 @@ Implementation Notes
**Hardware:**
Adafruit Feather nRF52840 Express <https://www.adafruit.com/product/4062>
Adafruit Circuit Playground Bluefruit <https://www.adafruit.com/product/4333>
**Software and Dependencies:**
@ -53,46 +54,61 @@ __version__ = "0.0.0-auto.0"
__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:
@ -105,16 +121,19 @@ class BLEConnection:
@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:
@ -123,34 +142,63 @@ class BLERadio:
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
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."""
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):
@ -167,7 +215,14 @@ class BLERadio:
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(advertisement.address, timeout=timeout)
self._connection_cache[connection] = BLEConnection(connection)
return self._connection_cache[connection]

View file

@ -25,15 +25,13 @@ Advertising is the first phase of BLE where devices can broadcast
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

View file

@ -64,7 +64,8 @@ class BoundServiceList:
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)
@ -131,12 +132,12 @@ class ServiceList(AdvertisingDataField):
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."""

View file

@ -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

View file

@ -30,32 +30,90 @@ This module provides core BLE characteristic classes that are used within Servic
import struct
import _bleio
from ..attributes import Attribute
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
class Characteristic:
"""Top level Characteristic class that does basic binding."""
def __init__(self, *, uuid=None, initial_value=None, max_length=None, **kwargs):
"""
Top level Characteristic class that does basic binding.
:param UUID uuid: The uuid of the characteristic
:param int properties: The properties of the characteristic,
specified as a bitmask of these values bitwise-or'd together:
`BROADCAST`, `INDICATE`, `NOTIFY`, `READ`, `WRITE`, `WRITE_NO_RESPONSE`.
:param int read_perm: Specifies whether the characteristic can be read by a client,
and if so, which security mode is required.
Must be one of the integer values `Attribute.NO_ACCESS`, `Attribute.OPEN`,
`Attribute.ENCRYPT_NO_MITM`, `Attribute.ENCRYPT_WITH_MITM`,
`Attribute.LESC_ENCRYPT_WITH_MITM`,
`Attribute.SIGNED_NO_MITM`, or `Attribute.SIGNED_WITH_MITM`.
:param int write_perm: Specifies whether the characteristic can be written by a client,
and if so, which security mode is required. Values allowed are the same as ``read_perm``.
:param int max_length: Maximum length in bytes of the characteristic value. The maximum allowed
is 512, or possibly 510 if ``fixed_length`` is False. The default, 20, is the maximum
number of data bytes that fit in a single BLE 4.x ATT packet.
:param bool fixed_length: True if the characteristic value is of fixed length.
:param buf initial_value: The initial value for this characteristic. If not given, will be
filled with zeros.
.. data:: BROADCAST
property: allowed in advertising packets
.. data:: INDICATE
property: server will indicate to the client when the value is set and wait for a response
.. data:: NOTIFY
property: server will notify the client when the value is set
.. data:: READ
property: clients may read this characteristic
.. data:: WRITE
property: clients may write this characteristic; a response will be sent back
.. data:: WRITE_NO_RESPONSE
property: clients may write this characteristic; no response will be sent back
"""
BROADCAST = _bleio.Characteristic.BROADCAST
INDICATE = _bleio.Characteristic.INDICATE
NOTIFY = _bleio.Characteristic.NOTIFY
READ = _bleio.Characteristic.READ
WRITE = _bleio.Characteristic.WRITE
WRITE_NO_RESPONSE = _bleio.Characteristic.WRITE_NO_RESPONSE
def __init__(self, *, uuid=None, properties=0,
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN,
max_length=20, fixed_length=False, initial_value=None):
self.field_name = None # Set by Service during basic binding
if uuid:
self.uuid = uuid
self.kwargs = kwargs
self.initial_value = initial_value
self.properties = properties
self.read_perm = read_perm
self.write_perm = write_perm
self.max_length = max_length
self.field_name = None # Set by Service during basic binding
self.fixed_length = fixed_length
self.initial_value = initial_value
def _ensure_bound(self, service, initial_value=None):
"""Binds the characteristic to the local Service or remote Characteristic object given."""
if self.field_name in service.bleio_characteristics:
return
if service.remote:
bleio_characteristic = None
remote_characteristics = service.bleio_service.characteristics
for characteristic in remote_characteristics:
for characteristic in service.bleio_service.characteristics:
if characteristic.uuid == self.uuid.bleio_uuid:
bleio_characteristic = characteristic
break
if not bleio_characteristic:
else:
raise AttributeError("Characteristic not available on remote service")
else:
bleio_characteristic = self.__bind_locally(service, initial_value)
@ -74,18 +132,14 @@ class Characteristic:
if max_length is None:
max_length = len(initial_value)
return _bleio.Characteristic.add_to_service(
service.bleio_service,
self.uuid.bleio_uuid,
initial_value=initial_value,
max_length=max_length,
**self.kwargs
)
service.bleio_service, self.uuid.bleio_uuid, initial_value=initial_value,
max_length=max_length, fixed_length=self.fixed_length,
properties=self.properties, read_perm=self.read_perm, write_perm=self.write_perm)
def __get__(self, service, cls=None):
self._ensure_bound(service)
bleio_characteristic = service.bleio_characteristics[self.field_name]
raw_data = bleio_characteristic.value
return raw_data
return bleio_characteristic.value
def __set__(self, service, value):
self._ensure_bound(service, value)
@ -93,28 +147,36 @@ class Characteristic:
bleio_characteristic.value = value
class ComplexCharacteristic:
"""Characteristic class that does complex binding where the subclass returns a full object for
"""
Characteristic class that does complex binding where the subclass returns a full object for
interacting with the characteristic data. The Characteristic itself will be shadowed once it
has been bound to the corresponding instance attribute."""
def __init__(self, *, uuid=None, **kwargs):
has been bound to the corresponding instance attribute.
"""
def __init__(self, *, uuid=None, properties=0,
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN,
max_length=20, fixed_length=False, initial_value=None):
self.field_name = None # Set by Service during basic binding
if uuid:
self.uuid = uuid
self.kwargs = kwargs
self.field_name = None # Set by Service
self.properties = properties
self.read_perm = read_perm
self.write_perm = write_perm
self.max_length = max_length
self.fixed_length = fixed_length
self.initial_value = initial_value
def bind(self, service):
"""Binds the characteristic to the local Service or remote Characteristic object given."""
if service.remote:
remote_characteristics = service.bleio_service.characteristics
for characteristic in remote_characteristics:
for characteristic in service.bleio_service.characteristics:
if characteristic.uuid == self.uuid.bleio_uuid:
return characteristic
raise AttributeError("Characteristic not available on remote service")
return _bleio.Characteristic.add_to_service(
service.bleio_service,
self.uuid.bleio_uuid,
**self.kwargs
)
service.bleio_service, self.uuid.bleio_uuid,
initial_value=self.initial_value, max_length=self.max_length,
properties=self.properties, read_perm=self.read_perm, write_perm=self.write_perm)
def __get__(self, service, cls=None):
bound_object = self.bind(service)
@ -122,13 +184,27 @@ class ComplexCharacteristic:
return bound_object
class StructCharacteristic(Characteristic):
"""Data descriptor for a structure with a fixed format."""
def __init__(self, struct_format, **kwargs):
"""
Data descriptor for a structure with a fixed format.
:param struct_format: a `struct` format string describing how to pack multiple values
into the characteristic bytestring
:param UUID uuid: The uuid of the characteristic
:param int properties: see `Characteristic`
:param int read_perm: see `Characteristic`
:param int write_perm: see `Characteristic`
:param buf initial_value: see `Characteristic`
"""
def __init__(self, struct_format, *, uuid=None, properties=0,
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN,
initial_value=None):
self._struct_format = struct_format
self._expected_size = struct.calcsize(struct_format)
if "initial_value" in kwargs:
kwargs["initial_value"] = struct.pack(self._struct_format, *kwargs["initial_value"])
super().__init__(**kwargs, max_length=self._expected_size, fixed_length=True)
if initial_value:
initial_value = struct.pack(self._struct_format, initial_value)
super().__init__(uuid=uuid, initial_value=initial_value,
max_length=self._expected_size, fixed_length=True,
properties=properties, read_perm=read_perm, write_perm=write_perm)
def __get__(self, obj, cls=None):
raw_data = super().__get__(obj, cls)

View file

@ -27,6 +27,7 @@ This module provides float characteristics that are usable directly as attribute
"""
from . import Attribute
from . import StructCharacteristic
__version__ = "0.0.0-auto.0"
@ -34,11 +35,14 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
class FloatCharacteristic(StructCharacteristic):
"""32-bit float"""
# TODO: Valid set values as within range.
def __init__(self, **kwargs):
if "initial_value" in kwargs:
kwargs["initial_value"] = (kwargs["initial_value"],)
super().__init__("<f", **kwargs)
def __init__(self, *, uuid=None, properties=0,
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN,
initial_value=None):
if initial_value:
initial_value = (initial_value,)
super().__init__("<f", uuid=uuid, properties=properties,
read_perm=read_perm, write_perm=write_perm,
initial_value=initial_value)
def __get__(self, obj, cls=None):
return super().__get__(obj)[0]

View file

@ -27,20 +27,28 @@ This module provides integer characteristics that are usable directly as attribu
"""
from . import Attribute
from . import StructCharacteristic
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
class Uint8Characteristic(StructCharacteristic):
"""Uint8 number."""
# TODO: Valid set values as within range.
def __init__(self, *, min_value=0, max_value=255, **kwargs):
class IntCharacteristic(StructCharacteristic):
"""Superclass for different kinds of integer fields."""
def __init__(self, format_string, min_value, max_value, *, uuid=None, properties=0,
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN,
initial_value=None):
self._min_value = min_value
self._max_value = max_value
if "initial_value" in kwargs:
kwargs["initial_value"] = (kwargs["initial_value"],)
super().__init__("<B", **kwargs)
if initial_value:
initial_value = (initial_value,)
if not self._min_value <= initial_value <= self._max_value:
raise ValueError("initial_value out of range")
super().__init__(format_string, uuid=uuid, properties=properties,
read_perm=read_perm, write_perm=write_perm,
initial_value=initial_value)
def __get__(self, obj, cls=None):
return super().__get__(obj)[0]
@ -49,3 +57,33 @@ class Uint8Characteristic(StructCharacteristic):
if not self._min_value <= value <= self._max_value:
raise ValueError("out of range")
super().__set__(obj, (value,))
class Int8Characteristic(IntCharacteristic):
"""Int8 number."""
def __init__(self, *, min_value=-128, max_value=127, **kwargs):
super().__init__("<b", min_value, max_value, **kwargs)
class Uint8Characteristic(IntCharacteristic):
"""Uint8 number."""
def __init__(self, *, min_value=0, max_value=0xff, **kwargs):
super().__init__("<B", min_value, max_value, **kwargs)
class Int16Characteristic(IntCharacteristic):
"""Int16 number."""
def __init__(self, *, min_value=-32768, max_value=32767, **kwargs):
super().__init__("<h", min_value, max_value, **kwargs)
class Uint16Characteristic(IntCharacteristic):
"""Uint16 number."""
def __init__(self, *, min_value=0, max_value=0xffff, **kwargs):
super().__init__("<H", min_value, max_value, **kwargs)
class Int32Characteristic(IntCharacteristic):
"""Int32 number."""
def __init__(self, *, min_value=-2147483648, max_value=2147483647, **kwargs):
super().__init__("<i", min_value, max_value, **kwargs)
class Uint32Characteristic(IntCharacteristic):
"""Uint32 number."""
def __init__(self, *, min_value=0, max_value=0xffffffff, **kwargs):
super().__init__("<I", min_value, max_value, **kwargs)

View file

@ -27,8 +27,10 @@ This module provides stream characteristics that bind readable or writable objec
object they are on.
"""
import _bleio
from . import Attribute
from . import Characteristic
from . import ComplexCharacteristic
__version__ = "0.0.0-auto.0"
@ -49,12 +51,13 @@ class BoundWriteStream:
class StreamOut(ComplexCharacteristic):
"""Output stream from the Service server."""
def __init__(self, *, timeout=1.0, buffer_size=64, **kwargs):
def __init__(self, *, uuid=None, timeout=1.0, buffer_size=64,
properties=Characteristic.NOTIFY,
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN):
self._timeout = timeout
self._buffer_size = buffer_size
super().__init__(properties=_bleio.Characteristic.NOTIFY,
read_perm=_bleio.Attribute.OPEN,
**kwargs)
super().__init__(uuid=uuid, properties=properties,
read_perm=read_perm, write_perm=write_perm)
def bind(self, service):
"""Binds the characteristic to the given Service."""
@ -69,14 +72,14 @@ class StreamOut(ComplexCharacteristic):
class StreamIn(ComplexCharacteristic):
"""Input stream into the Service server."""
def __init__(self, *, timeout=1.0, buffer_size=64, **kwargs):
def __init__(self, *, uuid=None, timeout=1.0, buffer_size=64,
properties=(Characteristic.WRITE | Characteristic.WRITE_NO_RESPONSE),
write_perm=Attribute.OPEN):
self._timeout = timeout
self._buffer_size = buffer_size
super().__init__(properties=(_bleio.Characteristic.WRITE |
_bleio.Characteristic.WRITE_NO_RESPONSE),
read_perm=_bleio.Attribute.NO_ACCESS,
write_perm=_bleio.Attribute.OPEN,
**kwargs)
super().__init__(uuid=uuid, properties=properties,
read_perm=Attribute.NO_ACCESS,
write_perm=write_perm)
def bind(self, service):
"""Binds the characteristic to the given Service."""

View file

@ -27,7 +27,7 @@ This module provides string characteristics.
"""
import _bleio
from . import Attribute
from . import Characteristic
__version__ = "0.0.0-auto.0"
@ -35,30 +35,27 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
class StringCharacteristic(Characteristic):
"""UTF-8 Encoded string characteristic."""
def __init__(self, *, properties=_bleio.Characteristic.READ, uuid=None):
super().__init__(properties=properties,
read_perm=_bleio.Attribute.OPEN,
max_length=512,
def __init__(self, *, uuid=None, properties=Characteristic.READ,
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN,
initial_value=None):
super().__init__(uuid=uuid, properties=properties,
read_perm=read_perm, write_perm=write_perm,
max_length=510, # shorter than 512 due to fixed_length==False
fixed_length=False,
uuid=uuid)
initial_value=initial_value)
def __get__(self, obj, cls=None):
raw_data = super().__get__(obj, cls)
return str(raw_data, "utf-8")
return str(super().__get__(obj, cls), "utf-8")
def __set__(self, obj, value):
encoded_value = value.encode("utf-8")
super().__set__(obj, encoded_value)
super().__set__(obj, value.encode("utf-8"))
class FixedStringCharacteristic(Characteristic):
"""Fixed strings are set once when bound and unchanged after."""
def __init__(self, *, uuid=None):
super().__init__(properties=_bleio.Characteristic.READ,
read_perm=_bleio.Attribute.OPEN,
write_perm=_bleio.Attribute.NO_ACCESS,
fixed_length=True,
uuid=uuid)
def __init__(self, *, uuid=None, read_perm=Attribute.OPEN):
super().__init__(uuid=uuid, properties=Characteristic.READ,
read_perm=read_perm, write_perm=Attribute.NO_ACCESS,
fixed_length=True)
def __get__(self, obj, cls=None):
raw_data = super().__get__(obj, cls)
return str(raw_data, "utf-8")
return str(super().__get__(obj, cls), "utf-8")

View file

@ -70,6 +70,7 @@ class Service:
if (not isinstance(value, Characteristic) and
not isinstance(value, ComplexCharacteristic)):
continue
value.field_name = class_attr
# Get or set every attribute to ensure that they are all bound up front. We could lazily

View file

@ -27,11 +27,10 @@ This module provides Services defined by CircuitPython. **Out of date.**
"""
import _bleio
from . import Service
from ..characteristics.string import StringCharacteristic
from ..characteristics import Characteristic
from ..characteristics.stream import StreamOut
from ..characteristics.string import StringCharacteristic
from ..uuid import VendorUUID
__version__ = "0.0.0-auto.0"
@ -50,6 +49,5 @@ class CircuitPythonService(Service):
Unimplemented."""
uuid = CircuitPythonUUID(0x0100)
filename = StringCharacteristic(uuid=CircuitPythonUUID(0x0200),
properties=(_bleio.Characteristic.READ |
_bleio.Characteristic.WRITE))
properties=(Characteristic.READ | Characteristic.WRITE))
contents = StreamOut(uuid=CircuitPythonUUID(0x0201))

View file

@ -27,8 +27,6 @@ This module provides Services defined by the MIDI group.
"""
import _bleio
from . import Service
from ..uuid import VendorUUID
from ..characteristics import Characteristic
@ -40,10 +38,10 @@ class MidiIOCharacteristic(Characteristic):
"""Workhorse MIDI Characteristic that carries midi messages both directions. Unimplemented."""
uuid = VendorUUID("7772E5DB-3868-4112-A1A9-F2669D106BF3")
def __init__(self, **kwargs):
super().__init__(properties=(_bleio.Characteristic.NOTIFY |
_bleio.Characteristic.READ |
_bleio.Characteristic.WRITE |
_bleio.Characteristic.WRITE_NO_RESPONSE), **kwargs)
super().__init__(properties=(Characteristic.NOTIFY |
Characteristic.READ |
Characteristic.WRITE |
Characteristic.WRITE_NO_RESPONSE), **kwargs)
class MidiService(Service):
"""BLE Service that transports MIDI messages. Unimplemented."""

View file

@ -44,19 +44,7 @@ class UARTService(Service):
:param int buffer_size: buffer up to this many bytes.
If more bytes are received, older bytes will be discarded.
Example::
from adafruit_ble.uart_client import UARTClient
uart_client = UARTClient()
uart_addresses = uart_client.scan()
if uart_addresses:
uart_client.connect(uarts[0].address, 5,
service_uuids_whitelist=(UART.NUS_SERVICE_UUID,))
else:
raise Error("No UART servers found.")
uart_client.write('abc')
See ``examples/ble_uart_echo_test.py`` for a usage example.
"""
# pylint: disable=no-member
uuid = VendorUUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
@ -102,7 +90,7 @@ class UARTService(Service):
Read a line, ending in a newline character.
:return: the line read
:rtype: int or None
:rtype: bytes or None
"""
return self._rx.readline()

View file

@ -48,8 +48,8 @@ import os
import sys
import microcontroller
from ..core import Service
from ...core.uuid import StandardUUID
from .. import Service
from ...uuid import StandardUUID
from ...characteristics.string import FixedStringCharacteristic
__version__ = "0.0.0-auto.0"

View file

@ -33,6 +33,7 @@ import struct
from micropython import const
import _bleio
from adafruit_ble.characteristics import Attribute
from adafruit_ble.characteristics import Characteristic
from adafruit_ble.characteristics.int import Uint8Characteristic
from adafruit_ble.uuid import StandardUUID
@ -77,8 +78,8 @@ class ReportIn:
self._characteristic = _bleio.Characteristic.add_to_service(
service.bleio_service,
self.uuid.bleio_uuid,
properties=_bleio.Characteristic.READ | _bleio.Characteristic.NOTIFY,
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM, write_perm=_bleio.Attribute.NO_ACCESS,
properties=Characteristic.READ | Characteristic.NOTIFY,
read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS,
max_length=max_length, fixed_length=True)
self._report_id = report_id
self.usage_page = usage_page
@ -86,7 +87,7 @@ class ReportIn:
_bleio.Descriptor.add_to_characteristic(
self._characteristic, _REPORT_REF_DESCR_UUID,
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM, write_perm=_bleio.Attribute.NO_ACCESS,
read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS,
initial_value=struct.pack('<BB', self._report_id, _REPORT_TYPE_INPUT))
def send_report(self, report):
@ -97,14 +98,14 @@ class ReportOut:
"""A single HID report that receives HID data from a client."""
uuid = StandardUUID(0x24ad)
def __init__(self, service, report_id, usage_page, usage, *, max_length):
self._characteristic = _bleio.Characteristic.add_to_service(
self._characteristic = Characteristic.add_to_service(
service.bleio_service,
self.uuid.bleio_uuid,
max_length=max_length,
fixed_length=True,
properties=(_bleio.Characteristic.READ | _bleio.Characteristic.WRITE |
_bleio.Characteristic.WRITE_NO_RESPONSE),
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM, write_perm=_bleio.Attribute.ENCRYPT_NO_MITM
properties=(Characteristic.READ | Characteristic.WRITE |
Characteristic.WRITE_NO_RESPONSE),
read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.ENCRYPT_NO_MITM
)
self._report_id = report_id
self.usage_page = usage_page
@ -112,7 +113,7 @@ class ReportOut:
_bleio.Descriptor.add_to_characteristic(
self._characteristic, _REPORT_REF_DESCR_UUID,
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM, write_perm=_bleio.Attribute.NO_ACCESS,
read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS,
initial_value=struct.pack('<BB', self._report_id, _REPORT_TYPE_OUTPUT))
_ITEM_TYPE_MAIN = const(0)
@ -142,25 +143,25 @@ class HIDService(Service):
default_field_name = "hid"
boot_keyboard_in = Characteristic(uuid=StandardUUID(0x2A22),
properties=(_bleio.Characteristic.READ |
_bleio.Characteristic.NOTIFY),
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM,
write_perm=_bleio.Attribute.NO_ACCESS,
properties=(Characteristic.READ |
Characteristic.NOTIFY),
read_perm=Attribute.ENCRYPT_NO_MITM,
write_perm=Attribute.NO_ACCESS,
max_length=8, fixed_length=True)
boot_keyboard_out = Characteristic(uuid=StandardUUID(0x2A32),
properties=(_bleio.Characteristic.READ |
_bleio.Characteristic.WRITE |
_bleio.Characteristic.WRITE_NO_RESPONSE),
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM,
write_perm=_bleio.Attribute.ENCRYPT_NO_MITM,
properties=(Characteristic.READ |
Characteristic.WRITE |
Characteristic.WRITE_NO_RESPONSE),
read_perm=Attribute.ENCRYPT_NO_MITM,
write_perm=Attribute.ENCRYPT_NO_MITM,
max_length=1, fixed_length=True)
protocol_mode = Uint8Characteristic(uuid=StandardUUID(0x2A4E),
properties=(_bleio.Characteristic.READ |
_bleio.Characteristic.WRITE_NO_RESPONSE),
read_perm=_bleio.Attribute.OPEN,
write_perm=_bleio.Attribute.OPEN,
properties=(Characteristic.READ |
Characteristic.WRITE_NO_RESPONSE),
read_perm=Attribute.OPEN,
write_perm=Attribute.OPEN,
initial_value=1, max_value=1)
"""Protocol mode: boot (0) or report (1)"""
@ -169,24 +170,24 @@ class HIDService(Service):
# bcd1.1, country = 0, flag = normal connect
# TODO: Make this a struct.
hid_information = Characteristic(uuid=StandardUUID(0x2A4A),
properties=_bleio.Characteristic.READ,
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM,
write_perm=_bleio.Attribute.NO_ACCESS,
properties=Characteristic.READ,
read_perm=Attribute.ENCRYPT_NO_MITM,
write_perm=Attribute.NO_ACCESS,
initial_value=b'\x01\x01\x00\x02')
"""Hid information including version, country code and flags."""
report_map = Characteristic(uuid=StandardUUID(0x2A4B),
properties=_bleio.Characteristic.READ,
read_perm=_bleio.Attribute.ENCRYPT_NO_MITM,
write_perm=_bleio.Attribute.NO_ACCESS,
properties=Characteristic.READ,
read_perm=Attribute.ENCRYPT_NO_MITM,
write_perm=Attribute.NO_ACCESS,
fixed_length=True)
"""This is the USB HID descriptor (not to be confused with a BLE Descriptor). It describes
which report characteristic are what."""
suspended = Uint8Characteristic(uuid=StandardUUID(0x2A4C),
properties=_bleio.Characteristic.WRITE_NO_RESPONSE,
read_perm=_bleio.Attribute.NO_ACCESS,
write_perm=_bleio.Attribute.ENCRYPT_NO_MITM,
properties=Characteristic.WRITE_NO_RESPONSE,
read_perm=Attribute.NO_ACCESS,
write_perm=Attribute.ENCRYPT_NO_MITM,
max_value=1)
"""Controls whether the device should be suspended (0) or not (1)."""

View file

@ -29,11 +29,11 @@ This module provides Service classes for BLE defined standard services.
import time
from ..core import Service
from ...core.uuid import StandardUUID
from ..characteristics.string import StringCharacteristic
from ..characteristics.core import StructCharacteristic
from ..characteristics.int import Uint8Characteristic
from .. import Service
from ...uuid import StandardUUID
from ...characteristics.string import StringCharacteristic
from ...characteristics import StructCharacteristic
from ...characteristics.int import Uint8Characteristic
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
@ -83,6 +83,6 @@ class CurrentTimeService(Service):
"""The current time as a `time.struct_time`. Day of year and whether DST is in effect
are always -1.
"""
_, month, day, hour, minute, second, weekday, _, _ = self.current_time
year, month, day, hour, minute, second, weekday, _, _ = self.current_time
# Bluetooth weekdays count from 1. struct_time counts from 0.
return time.struct_time((month, day, hour, minute, second, weekday - 1, -1))
return time.struct_time((year, month, day, hour, minute, second, weekday - 1, -1, -1))

View file

@ -56,7 +56,7 @@ class UUID:
self.bleio_uuid.pack_into(buffer, offset=offset)
class StandardUUID(UUID):
"""Bluetooth defined, 16-bit UUID."""
"""Standard 16-bit UUID defined by the Bluetooth SIG."""
def __init__(self, uuid16):
if not isinstance(uuid16, int):
uuid16 = struct.unpack("<H", uuid16)[0]

View file

@ -8,6 +8,7 @@
top_level
advertising
attributes
characteristics
services
uuid

5
docs/attributes.rst Normal file
View file

@ -0,0 +1,5 @@
`adafruit_ble.attributes`
====================================================
.. automodule:: adafruit_ble.attributes
:members:

View file

@ -1,12 +1,20 @@
class Attribute:
NO_ACCESS = 0
OPEN = 0
ENCRYPT_NO_MITM = 0
ENCRYPT_WITH_MITM = 0
LESC_ENCRYPT_WITH_MITM = 0
SIGNED_NO_MITM = 0
SIGNED_WITH_MITM = 0
class UUID:
def __init__(self, uuid):
pass
class Characteristic:
BROADCAST = 0
READ = 0
WRITE = 0
NOTIFY = 0
INDICATE = 0
WRITE_NO_RESPONSE = 0