Merge pull request #74 from adafruit/pylint-update
Ran black, updated to pylint 2.x
This commit is contained in:
commit
2052ff6da3
28 changed files with 779 additions and 427 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -40,7 +40,7 @@ jobs:
|
|||
source actions-ci/install.sh
|
||||
- name: Pip install pylint, black, & Sphinx
|
||||
run: |
|
||||
pip install --force-reinstall pylint==1.9.2 black==19.10b0 Sphinx sphinx-rtd-theme
|
||||
pip install --force-reinstall pylint black==19.10b0 Sphinx sphinx-rtd-theme
|
||||
- name: Library version
|
||||
run: git describe --dirty --always --tags
|
||||
- name: PyLint
|
||||
|
|
|
|||
|
|
@ -26,12 +26,14 @@ This module provides higher-level BLE (Bluetooth Low Energy) functionality,
|
|||
building on the native `_bleio` module.
|
||||
|
||||
"""
|
||||
#pylint: disable=wrong-import-position
|
||||
# pylint: disable=wrong-import-position
|
||||
import sys
|
||||
if sys.implementation.name == 'circuitpython' and sys.implementation.version[0] <= 4:
|
||||
|
||||
if sys.implementation.name == "circuitpython" and sys.implementation.version[0] <= 4:
|
||||
raise ImportError(
|
||||
"This release is not compatible with CircuitPython 4.x; use library release 1.x.x")
|
||||
#pylint: enable=wrong-import-position
|
||||
"This release is not compatible with CircuitPython 4.x; use library release 1.x.x"
|
||||
)
|
||||
# pylint: enable=wrong-import-position
|
||||
|
||||
import _bleio
|
||||
|
||||
|
|
@ -41,6 +43,7 @@ from .advertising import Advertisement
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class BLEConnection:
|
||||
"""
|
||||
Represents a connection to a peer BLE device.
|
||||
|
|
@ -49,6 +52,7 @@ class BLEConnection:
|
|||
: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.
|
||||
|
|
@ -61,7 +65,9 @@ class BLEConnection:
|
|||
if uuid in self._discovered_bleio_services:
|
||||
remote_service = self._discovered_bleio_services[uuid]
|
||||
else:
|
||||
results = self._bleio_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_bleio_services[uuid] = remote_service
|
||||
|
|
@ -140,6 +146,7 @@ class BLEConnection:
|
|||
"""Disconnect from peer."""
|
||||
self._bleio_connection.disconnect()
|
||||
|
||||
|
||||
class BLERadio:
|
||||
"""
|
||||
BLERadio provides the interfaces for BLE advertising,
|
||||
|
|
@ -172,17 +179,28 @@ class BLERadio:
|
|||
scan_response.tx_power = self.tx_power
|
||||
if scan_response:
|
||||
scan_response_bytes = bytes(scan_response)
|
||||
self._adapter.start_advertising(advertisement_bytes,
|
||||
scan_response=scan_response_bytes,
|
||||
connectable=advertisement.connectable,
|
||||
interval=interval)
|
||||
self._adapter.start_advertising(
|
||||
advertisement_bytes,
|
||||
scan_response=scan_response_bytes,
|
||||
connectable=advertisement.connectable,
|
||||
interval=interval,
|
||||
)
|
||||
|
||||
def stop_advertising(self):
|
||||
"""Stops advertising."""
|
||||
self._adapter.stop_advertising()
|
||||
|
||||
def start_scan(self, *advertisement_types, buffer_size=512, extended=False, timeout=None,
|
||||
interval=0.1, window=0.1, minimum_rssi=-80, active=True):
|
||||
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
|
||||
|
|
@ -214,10 +232,16 @@ class BLERadio:
|
|||
prefixes = b""
|
||||
if advertisement_types:
|
||||
prefixes = b"".join(adv.prefix for adv in advertisement_types)
|
||||
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):
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -25,13 +25,16 @@ Advertising is the first phase of BLE where devices can broadcast
|
|||
|
||||
import struct
|
||||
|
||||
|
||||
def to_hex(seq):
|
||||
"""Pretty prints a byte sequence as hex values."""
|
||||
return " ".join("{:02x}".format(v) for v in seq)
|
||||
|
||||
|
||||
def to_bytes_literal(seq):
|
||||
"""Prints a byte sequence as a Python bytes literal that only uses hex encoding."""
|
||||
return "b\"" + "".join("\\x{:02x}".format(v) for v in seq) + "\""
|
||||
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
|
||||
|
|
@ -45,7 +48,7 @@ def decode_data(data, *, key_encoding="B"):
|
|||
if item_length == 0:
|
||||
break
|
||||
key = struct.unpack_from(key_encoding, data, i)[0]
|
||||
value = data[i + key_size:i + item_length]
|
||||
value = data[i + key_size : i + item_length]
|
||||
if key in data_dict:
|
||||
if not isinstance(data_dict[key], list):
|
||||
data_dict[key] = [data_dict[key]]
|
||||
|
|
@ -55,6 +58,7 @@ def decode_data(data, *, key_encoding="B"):
|
|||
i += item_length
|
||||
return data_dict
|
||||
|
||||
|
||||
def compute_length(data_dict, *, key_encoding="B"):
|
||||
"""Computes the length of the encoded data dictionary."""
|
||||
value_size = 0
|
||||
|
|
@ -66,6 +70,7 @@ def compute_length(data_dict, *, key_encoding="B"):
|
|||
value_size += len(value)
|
||||
return len(data_dict) + len(data_dict) * struct.calcsize(key_encoding) + value_size
|
||||
|
||||
|
||||
def encode_data(data_dict, *, key_encoding="B"):
|
||||
"""Helper which encodes dictionaries into length encoded structures with the given key
|
||||
encoding."""
|
||||
|
|
@ -79,17 +84,21 @@ def encode_data(data_dict, *, key_encoding="B"):
|
|||
item_length = key_size + len(value)
|
||||
struct.pack_into("B", data, i, item_length)
|
||||
struct.pack_into(key_encoding, data, i + 1, key)
|
||||
data[i + 1 + key_size: i + 1 + item_length] = bytes(value)
|
||||
data[i + 1 + key_size : i + 1 + item_length] = bytes(value)
|
||||
i += 1 + item_length
|
||||
return data
|
||||
|
||||
|
||||
class AdvertisingDataField:
|
||||
"""Top level class for any descriptor classes that live in Advertisement or its subclasses."""
|
||||
|
||||
# pylint: disable=too-few-public-methods,unnecessary-pass
|
||||
pass
|
||||
|
||||
|
||||
class AdvertisingFlag:
|
||||
"""A single bit flag within an AdvertisingFlags object."""
|
||||
|
||||
def __init__(self, bit_position):
|
||||
self._bitmask = 1 << bit_position
|
||||
|
||||
|
|
@ -102,6 +111,7 @@ class AdvertisingFlag:
|
|||
else:
|
||||
obj.flags &= ~self._bitmask
|
||||
|
||||
|
||||
class AdvertisingFlags(AdvertisingDataField):
|
||||
"""Standard advertising flags"""
|
||||
|
||||
|
|
@ -135,10 +145,12 @@ class AdvertisingFlags(AdvertisingDataField):
|
|||
parts.append(attr)
|
||||
return "<AdvertisingFlags {} >".format(" ".join(parts))
|
||||
|
||||
|
||||
class String(AdvertisingDataField):
|
||||
"""UTF-8 encoded string in an Advertisement.
|
||||
|
||||
Not null terminated once encoded because length is always transmitted."""
|
||||
|
||||
def __init__(self, *, advertising_data_type):
|
||||
self._adt = advertising_data_type
|
||||
|
||||
|
|
@ -152,8 +164,10 @@ class String(AdvertisingDataField):
|
|||
def __set__(self, obj, value):
|
||||
obj.data_dict[self._adt] = value.encode("utf-8")
|
||||
|
||||
|
||||
class Struct(AdvertisingDataField):
|
||||
"""`struct` encoded data in an Advertisement."""
|
||||
|
||||
def __init__(self, struct_format, *, advertising_data_type):
|
||||
self._format = struct_format
|
||||
self._adt = advertising_data_type
|
||||
|
|
@ -171,6 +185,7 @@ class Struct(AdvertisingDataField):
|
|||
|
||||
class LazyObjectField(AdvertisingDataField):
|
||||
"""Non-data descriptor useful for lazily binding a complex object to an advertisement object."""
|
||||
|
||||
def __init__(self, cls, attribute_name, *, advertising_data_type, **kwargs):
|
||||
self._cls = cls
|
||||
self._attribute_name = attribute_name
|
||||
|
|
@ -197,15 +212,17 @@ class LazyObjectField(AdvertisingDataField):
|
|||
# TODO: Add __set_name__ support to CircuitPython so that we automatically tell the descriptor
|
||||
# instance the attribute name it has and the class it is on.
|
||||
|
||||
|
||||
class Advertisement:
|
||||
"""Core Advertisement type"""
|
||||
prefix = b"\x00" # This is an empty prefix and will match everything.
|
||||
|
||||
prefix = b"\x00" # This is an empty prefix and will match everything.
|
||||
flags = LazyObjectField(AdvertisingFlags, "flags", advertising_data_type=0x01)
|
||||
short_name = String(advertising_data_type=0x08)
|
||||
"""Short local device name (shortened to fit)."""
|
||||
complete_name = String(advertising_data_type=0x09)
|
||||
"""Complete local device name."""
|
||||
tx_power = Struct("<b", advertising_data_type=0x0a)
|
||||
tx_power = Struct("<b", advertising_data_type=0x0A)
|
||||
"""Transmit power level"""
|
||||
# DEVICE_ID = 0x10
|
||||
# """Device identifier."""
|
||||
|
|
@ -242,7 +259,7 @@ class Advertisement:
|
|||
self = cls()
|
||||
self.data_dict = decode_data(entry.advertisement_bytes)
|
||||
self.address = entry.address
|
||||
self._rssi = entry.rssi # pylint: disable=protected-access
|
||||
self._rssi = entry.rssi # pylint: disable=protected-access
|
||||
self.connectable = entry.connectable
|
||||
self.scan_response = entry.scan_response
|
||||
self.mutable = False
|
||||
|
|
@ -272,8 +289,10 @@ class Advertisement:
|
|||
for attr in dir(self.__class__):
|
||||
attribute_instance = getattr(self.__class__, attr)
|
||||
if issubclass(attribute_instance.__class__, AdvertisingDataField):
|
||||
if (issubclass(attribute_instance.__class__, LazyObjectField) and
|
||||
not attribute_instance.advertising_data_type in self.data_dict):
|
||||
if (
|
||||
issubclass(attribute_instance.__class__, LazyObjectField)
|
||||
and not attribute_instance.advertising_data_type in self.data_dict
|
||||
):
|
||||
# Skip uninstantiated lazy objects; if we get
|
||||
# their value, they will be be instantiated.
|
||||
continue
|
||||
|
|
@ -286,4 +305,6 @@ class Advertisement:
|
|||
return compute_length(self.data_dict)
|
||||
|
||||
def __repr__(self):
|
||||
return "Advertisement(data={})".format(to_bytes_literal(encode_data(self.data_dict)))
|
||||
return "Advertisement(data={})".format(
|
||||
to_bytes_literal(encode_data(self.data_dict))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,24 +40,29 @@ from .standard import ManufacturerData, ManufacturerDataField
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
_MANUFACTURING_DATA_ADT = const(0xff)
|
||||
_MANUFACTURING_DATA_ADT = const(0xFF)
|
||||
_ADAFRUIT_COMPANY_ID = const(0x0822)
|
||||
_COLOR_DATA_ID = const(0x0000)
|
||||
|
||||
|
||||
class AdafruitColor(Advertisement):
|
||||
"""Broadcast a single RGB color."""
|
||||
|
||||
# This prefix matches all
|
||||
prefix = struct.pack("<BBHBH",
|
||||
0x6,
|
||||
_MANUFACTURING_DATA_ADT,
|
||||
_ADAFRUIT_COMPANY_ID,
|
||||
struct.calcsize("<HI"),
|
||||
_COLOR_DATA_ID)
|
||||
manufacturer_data = LazyObjectField(ManufacturerData,
|
||||
"manufacturer_data",
|
||||
advertising_data_type=_MANUFACTURING_DATA_ADT,
|
||||
company_id=_ADAFRUIT_COMPANY_ID,
|
||||
key_encoding="<H")
|
||||
prefix = struct.pack(
|
||||
"<BBHBH",
|
||||
0x6,
|
||||
_MANUFACTURING_DATA_ADT,
|
||||
_ADAFRUIT_COMPANY_ID,
|
||||
struct.calcsize("<HI"),
|
||||
_COLOR_DATA_ID,
|
||||
)
|
||||
manufacturer_data = LazyObjectField(
|
||||
ManufacturerData,
|
||||
"manufacturer_data",
|
||||
advertising_data_type=_MANUFACTURING_DATA_ADT,
|
||||
company_id=_ADAFRUIT_COMPANY_ID,
|
||||
key_encoding="<H",
|
||||
)
|
||||
color = ManufacturerDataField(_COLOR_DATA_ID, "<I")
|
||||
"""Color to broadcast as RGB integer."""
|
||||
|
|
|
|||
|
|
@ -30,14 +30,23 @@ even though multiple purposes may actually be present in a single packet.
|
|||
|
||||
import struct
|
||||
|
||||
from . import Advertisement, AdvertisingDataField, encode_data, decode_data, to_hex, compute_length
|
||||
from . import (
|
||||
Advertisement,
|
||||
AdvertisingDataField,
|
||||
encode_data,
|
||||
decode_data,
|
||||
to_hex,
|
||||
compute_length,
|
||||
)
|
||||
from ..uuid import StandardUUID, VendorUUID
|
||||
|
||||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class BoundServiceList:
|
||||
"""Sequence-like object of Service UUID objects. It stores both standard and vendor UUIDs."""
|
||||
|
||||
def __init__(self, advertisement, *, standard_services, vendor_services):
|
||||
self._advertisement = advertisement
|
||||
self._standard_service_fields = standard_services
|
||||
|
|
@ -48,13 +57,13 @@ class BoundServiceList:
|
|||
if adt in self._advertisement.data_dict:
|
||||
data = self._advertisement.data_dict[adt]
|
||||
for i in range(len(data) // 2):
|
||||
uuid = StandardUUID(data[2*i:2*(i+1)])
|
||||
uuid = StandardUUID(data[2 * i : 2 * (i + 1)])
|
||||
self._standard_services.append(uuid)
|
||||
for adt in vendor_services:
|
||||
if adt in self._advertisement.data_dict:
|
||||
data = self._advertisement.data_dict[adt]
|
||||
for i in range(len(data) // 16):
|
||||
uuid = VendorUUID(data[16*i:16*(i+1)])
|
||||
uuid = VendorUUID(data[16 * i : 16 * (i + 1)])
|
||||
self._vendor_services.append(uuid)
|
||||
|
||||
def __contains__(self, key):
|
||||
|
|
@ -83,10 +92,16 @@ class BoundServiceList:
|
|||
# TODO: Differentiate between complete and incomplete lists.
|
||||
def append(self, service):
|
||||
"""Append a service to the list."""
|
||||
if isinstance(service.uuid, StandardUUID) and service not in self._standard_services:
|
||||
if (
|
||||
isinstance(service.uuid, StandardUUID)
|
||||
and service not in self._standard_services
|
||||
):
|
||||
self._standard_services.append(service.uuid)
|
||||
self._update(self._standard_service_fields[0], self._standard_services)
|
||||
elif isinstance(service.uuid, VendorUUID) and service not in self._vendor_services:
|
||||
elif (
|
||||
isinstance(service.uuid, VendorUUID)
|
||||
and service not in self._vendor_services
|
||||
):
|
||||
self._vendor_services.append(service.uuid)
|
||||
self._update(self._vendor_service_fields[0], self._vendor_services)
|
||||
|
||||
|
|
@ -96,11 +111,16 @@ class BoundServiceList:
|
|||
standard = False
|
||||
vendor = False
|
||||
for service in services:
|
||||
if (isinstance(service.uuid, StandardUUID) and
|
||||
service.uuid not in self._standard_services):
|
||||
if (
|
||||
isinstance(service.uuid, StandardUUID)
|
||||
and service.uuid not in self._standard_services
|
||||
):
|
||||
self._standard_services.append(service.uuid)
|
||||
standard = True
|
||||
elif isinstance(service.uuid, VendorUUID) and service.uuid not in self._vendor_services:
|
||||
elif (
|
||||
isinstance(service.uuid, VendorUUID)
|
||||
and service.uuid not in self._vendor_services
|
||||
):
|
||||
self._vendor_services.append(service.uuid)
|
||||
vendor = True
|
||||
|
||||
|
|
@ -117,8 +137,10 @@ class BoundServiceList:
|
|||
data.append(str(service_uuid))
|
||||
return " ".join(data)
|
||||
|
||||
|
||||
class ServiceList(AdvertisingDataField):
|
||||
"""Descriptor for a list of Service UUIDs that lazily binds a corresponding BoundServiceList."""
|
||||
|
||||
def __init__(self, *, standard_services, vendor_services):
|
||||
self.standard_services = standard_services
|
||||
self.vendor_services = vendor_services
|
||||
|
|
@ -142,8 +164,10 @@ class ServiceList(AdvertisingDataField):
|
|||
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."""
|
||||
|
||||
# This is four prefixes, one for each ADT that can carry service UUIDs.
|
||||
prefix = b"\x01\x02\x01\x03\x01\x06\x01\x07"
|
||||
services = ServiceList(standard_services=[0x02, 0x03], vendor_services=[0x06, 0x07])
|
||||
|
|
@ -161,8 +185,10 @@ class ProvideServicesAdvertisement(Advertisement):
|
|||
def matches(cls, entry):
|
||||
return entry.matches(cls.prefix, all=False)
|
||||
|
||||
|
||||
class SolicitServicesAdvertisement(Advertisement):
|
||||
"""Advertise what services the device would like to use over a connection."""
|
||||
|
||||
# This is two prefixes, one for each ADT that can carry solicited service UUIDs.
|
||||
prefix = b"\x01\x14\x01\x15"
|
||||
|
||||
|
|
@ -181,16 +207,19 @@ class ManufacturerData(AdvertisingDataField):
|
|||
"""Encapsulates manufacturer specific keyed data bytes. The manufacturer is identified by the
|
||||
company_id and the data is structured like an advertisement with a configurable key
|
||||
format."""
|
||||
def __init__(self, obj, *, advertising_data_type=0xff, company_id, key_encoding="B"):
|
||||
|
||||
def __init__(
|
||||
self, obj, *, advertising_data_type=0xFF, company_id, key_encoding="B"
|
||||
):
|
||||
self._obj = obj
|
||||
self._company_id = company_id
|
||||
self._adt = advertising_data_type
|
||||
|
||||
self.data = {}
|
||||
self.company_id = company_id
|
||||
encoded_company = struct.pack('<H', company_id)
|
||||
if 0xff in obj.data_dict:
|
||||
existing_data = obj.data_dict[0xff]
|
||||
encoded_company = struct.pack("<H", company_id)
|
||||
if 0xFF in obj.data_dict:
|
||||
existing_data = obj.data_dict[0xFF]
|
||||
if isinstance(existing_data, list):
|
||||
for existing in existing_data:
|
||||
if existing.startswith(encoded_company):
|
||||
|
|
@ -203,23 +232,32 @@ class ManufacturerData(AdvertisingDataField):
|
|||
return 2 + compute_length(self.data, key_encoding=self._key_encoding)
|
||||
|
||||
def __bytes__(self):
|
||||
return (struct.pack('<H', self.company_id) +
|
||||
encode_data(self.data, key_encoding=self._key_encoding))
|
||||
return struct.pack("<H", self.company_id) + encode_data(
|
||||
self.data, key_encoding=self._key_encoding
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
hex_data = to_hex(encode_data(self.data, key_encoding=self._key_encoding))
|
||||
return "<ManufacturerData company_id={:04x} data={} >".format(self.company_id, hex_data)
|
||||
return "<ManufacturerData company_id={:04x} data={} >".format(
|
||||
self.company_id, hex_data
|
||||
)
|
||||
|
||||
|
||||
class ManufacturerDataField:
|
||||
"""A single piece of data within the manufacturer specific data. The format can be repeated."""
|
||||
|
||||
def __init__(self, key, value_format, field_names=None):
|
||||
self._key = key
|
||||
self._format = value_format
|
||||
# TODO: Support format strings that use numbers to repeat a given type. For now, we strip
|
||||
# numbers because Radio specifies string length with it.
|
||||
self.element_count = len(value_format.strip("><!=@0123456789").replace("x", ""))
|
||||
if self.element_count > 1 and (not field_names or len(field_names) != self.element_count):
|
||||
raise ValueError("Provide field_names when multiple values are in the format")
|
||||
if self.element_count > 1 and (
|
||||
not field_names or len(field_names) != self.element_count
|
||||
):
|
||||
raise ValueError(
|
||||
"Provide field_names when multiple values are in the format"
|
||||
)
|
||||
self._entry_length = struct.calcsize(value_format)
|
||||
self.field_names = field_names
|
||||
|
||||
|
|
@ -248,7 +286,9 @@ class ManufacturerDataField:
|
|||
def __set__(self, obj, value):
|
||||
if not obj.mutable:
|
||||
raise AttributeError()
|
||||
if isinstance(value, tuple) and (self.element_count == 1 or isinstance(value[0], tuple)):
|
||||
if isinstance(value, tuple) and (
|
||||
self.element_count == 1 or isinstance(value[0], tuple)
|
||||
):
|
||||
packed = bytearray(self._entry_length * len(value))
|
||||
for i, entry in enumerate(value):
|
||||
offset = i * self._entry_length
|
||||
|
|
@ -262,9 +302,11 @@ class ManufacturerDataField:
|
|||
else:
|
||||
obj.manufacturer_data.data[self._key] = struct.pack(self._format, *value)
|
||||
|
||||
|
||||
class ServiceData(AdvertisingDataField):
|
||||
"""Encapsulates service data. It is read as a memoryview which can be manipulated or set as a
|
||||
bytearray to change the size."""
|
||||
|
||||
def __init__(self, service):
|
||||
if isinstance(service.uuid, StandardUUID):
|
||||
self._adt = 0x16
|
||||
|
|
@ -289,11 +331,11 @@ class ServiceData(AdvertisingDataField):
|
|||
if not isinstance(service_data, bytearray):
|
||||
service_data = bytearray(service_data)
|
||||
all_service_data[i] = service_data
|
||||
return memoryview(service_data)[len(self._prefix):]
|
||||
return memoryview(service_data)[len(self._prefix) :]
|
||||
if obj.mutable:
|
||||
service_data = bytearray(self._prefix)
|
||||
all_service_data.append(service_data)
|
||||
return memoryview(service_data)[len(self._prefix):]
|
||||
return memoryview(service_data)[len(self._prefix) :]
|
||||
# Existing data is a single set of bytes.
|
||||
elif isinstance(all_service_data, (bytes, bytearray)):
|
||||
service_data = all_service_data
|
||||
|
|
@ -306,11 +348,10 @@ class ServiceData(AdvertisingDataField):
|
|||
if not isinstance(service_data, bytearray):
|
||||
service_data = bytearray(service_data)
|
||||
obj.data_dict[self._adt] = service_data
|
||||
return memoryview(service_data)[len(self._prefix):]
|
||||
return memoryview(service_data)[len(self._prefix) :]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if not obj.mutable:
|
||||
raise RuntimeError("Advertisement immutable")
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import _bleio
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class Attribute:
|
||||
"""Constants describing security levels.
|
||||
|
||||
|
|
@ -63,6 +64,7 @@ class Attribute:
|
|||
|
||||
security_mode: authenticated data signing, without man-in-the-middle protection
|
||||
"""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
NO_ACCESS = _bleio.Attribute.NO_ACCESS
|
||||
OPEN = _bleio.Attribute.OPEN
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class Characteristic:
|
|||
|
||||
property: clients may write this characteristic; no response will be sent back
|
||||
"""
|
||||
|
||||
BROADCAST = _bleio.Characteristic.BROADCAST
|
||||
INDICATE = _bleio.Characteristic.INDICATE
|
||||
NOTIFY = _bleio.Characteristic.NOTIFY
|
||||
|
|
@ -88,10 +89,18 @@ class Characteristic:
|
|||
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=None, fixed_length=False, initial_value=None):
|
||||
self.field_name = None # Set by Service during basic binding
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
uuid=None,
|
||||
properties=0,
|
||||
read_perm=Attribute.OPEN,
|
||||
write_perm=Attribute.OPEN,
|
||||
max_length=None,
|
||||
fixed_length=False,
|
||||
initial_value=None
|
||||
):
|
||||
self.field_name = None # Set by Service during basic binding
|
||||
|
||||
if uuid:
|
||||
self.uuid = uuid
|
||||
|
|
@ -130,9 +139,15 @@ class Characteristic:
|
|||
elif 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, fixed_length=self.fixed_length,
|
||||
properties=self.properties, read_perm=self.read_perm, write_perm=self.write_perm)
|
||||
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)
|
||||
|
|
@ -146,16 +161,26 @@ class Characteristic:
|
|||
bleio_characteristic = service.bleio_characteristics[self.field_name]
|
||||
bleio_characteristic.value = value
|
||||
|
||||
|
||||
class ComplexCharacteristic:
|
||||
"""
|
||||
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, 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
|
||||
|
||||
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
|
||||
|
|
@ -174,15 +199,21 @@ class ComplexCharacteristic:
|
|||
return characteristic
|
||||
raise AttributeError("Characteristic not available on remote service")
|
||||
return _bleio.Characteristic.add_to_service(
|
||||
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)
|
||||
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)
|
||||
setattr(service, self.field_name, bound_object)
|
||||
return bound_object
|
||||
|
||||
|
||||
class StructCharacteristic(Characteristic):
|
||||
"""
|
||||
Data descriptor for a structure with a fixed format.
|
||||
|
|
@ -195,16 +226,30 @@ class StructCharacteristic(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):
|
||||
|
||||
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:
|
||||
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)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -33,16 +33,29 @@ from . import StructCharacteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class FloatCharacteristic(StructCharacteristic):
|
||||
"""32-bit float"""
|
||||
def __init__(self, *, uuid=None, properties=0,
|
||||
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN,
|
||||
initial_value=None):
|
||||
|
||||
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)
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -33,11 +33,22 @@ from . import StructCharacteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
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):
|
||||
|
||||
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:
|
||||
|
|
@ -45,10 +56,14 @@ class IntCharacteristic(StructCharacteristic):
|
|||
raise ValueError("initial_value out of range")
|
||||
initial_value = (initial_value,)
|
||||
|
||||
|
||||
super().__init__(format_string, uuid=uuid, properties=properties,
|
||||
read_perm=read_perm, write_perm=write_perm,
|
||||
initial_value=initial_value)
|
||||
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]
|
||||
|
|
@ -58,38 +73,50 @@ class IntCharacteristic(StructCharacteristic):
|
|||
raise ValueError("out of range")
|
||||
super().__set__(obj, (value,))
|
||||
|
||||
|
||||
class Int8Characteristic(IntCharacteristic):
|
||||
"""Int8 number."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, *, min_value=-128, max_value=127, **kwargs):
|
||||
super().__init__("<b", min_value, max_value, **kwargs)
|
||||
|
||||
|
||||
class Uint8Characteristic(IntCharacteristic):
|
||||
"""Uint8 number."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, *, min_value=0, max_value=0xff, **kwargs):
|
||||
def __init__(self, *, min_value=0, max_value=0xFF, **kwargs):
|
||||
super().__init__("<B", min_value, max_value, **kwargs)
|
||||
|
||||
|
||||
class Int16Characteristic(IntCharacteristic):
|
||||
"""Int16 number."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, *, min_value=-32768, max_value=32767, **kwargs):
|
||||
super().__init__("<h", min_value, max_value, **kwargs)
|
||||
|
||||
|
||||
class Uint16Characteristic(IntCharacteristic):
|
||||
"""Uint16 number."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, *, min_value=0, max_value=0xffff, **kwargs):
|
||||
def __init__(self, *, min_value=0, max_value=0xFFFF, **kwargs):
|
||||
super().__init__("<H", min_value, max_value, **kwargs)
|
||||
|
||||
|
||||
class Int32Characteristic(IntCharacteristic):
|
||||
"""Int32 number."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, *, min_value=-2147483648, max_value=2147483647, **kwargs):
|
||||
super().__init__("<i", min_value, max_value, **kwargs)
|
||||
|
||||
|
||||
class Uint32Characteristic(IntCharacteristic):
|
||||
"""Uint32 number."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, *, min_value=0, max_value=0xffffffff, **kwargs):
|
||||
def __init__(self, *, min_value=0, max_value=0xFFFFFFFF, **kwargs):
|
||||
super().__init__("<I", min_value, max_value, **kwargs)
|
||||
|
|
|
|||
|
|
@ -36,8 +36,10 @@ from . import ComplexCharacteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class BoundWriteStream:
|
||||
"""Writes data out to the peer."""
|
||||
|
||||
def __init__(self, bound_characteristic):
|
||||
self.bound_characteristic = bound_characteristic
|
||||
|
||||
|
|
@ -46,18 +48,28 @@ class BoundWriteStream:
|
|||
# We can only write 20 bytes at a time.
|
||||
offset = 0
|
||||
while offset < len(buf):
|
||||
self.bound_characteristic.value = buf[offset:offset+20]
|
||||
self.bound_characteristic.value = buf[offset : offset + 20]
|
||||
offset += 20
|
||||
|
||||
|
||||
class StreamOut(ComplexCharacteristic):
|
||||
"""Output stream from the Service server."""
|
||||
def __init__(self, *, uuid=None, timeout=1.0, buffer_size=64,
|
||||
properties=Characteristic.NOTIFY,
|
||||
read_perm=Attribute.OPEN, write_perm=Attribute.OPEN):
|
||||
|
||||
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__(uuid=uuid, properties=properties,
|
||||
read_perm=read_perm, write_perm=write_perm)
|
||||
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."""
|
||||
|
|
@ -65,21 +77,34 @@ class StreamOut(ComplexCharacteristic):
|
|||
# If we're given a remote service then we're the client and need to buffer in.
|
||||
if service.remote:
|
||||
bound_characteristic.set_cccd(notify=True)
|
||||
return _bleio.CharacteristicBuffer(bound_characteristic,
|
||||
timeout=self._timeout,
|
||||
buffer_size=self._buffer_size)
|
||||
return _bleio.CharacteristicBuffer(
|
||||
bound_characteristic,
|
||||
timeout=self._timeout,
|
||||
buffer_size=self._buffer_size,
|
||||
)
|
||||
return BoundWriteStream(bound_characteristic)
|
||||
|
||||
|
||||
class StreamIn(ComplexCharacteristic):
|
||||
"""Input stream into the Service server."""
|
||||
def __init__(self, *, uuid=None, timeout=1.0, buffer_size=64,
|
||||
properties=(Characteristic.WRITE | Characteristic.WRITE_NO_RESPONSE),
|
||||
write_perm=Attribute.OPEN):
|
||||
|
||||
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__(uuid=uuid, properties=properties,
|
||||
read_perm=Attribute.NO_ACCESS,
|
||||
write_perm=write_perm)
|
||||
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."""
|
||||
|
|
@ -88,6 +113,6 @@ class StreamIn(ComplexCharacteristic):
|
|||
if service.remote:
|
||||
return BoundWriteStream(bound_characteristic)
|
||||
# We're the server so buffer incoming writes.
|
||||
return _bleio.CharacteristicBuffer(bound_characteristic,
|
||||
timeout=self._timeout,
|
||||
buffer_size=self._buffer_size)
|
||||
return _bleio.CharacteristicBuffer(
|
||||
bound_characteristic, timeout=self._timeout, buffer_size=self._buffer_size
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,16 +33,28 @@ from . import Characteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class StringCharacteristic(Characteristic):
|
||||
"""UTF-8 Encoded string characteristic."""
|
||||
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,
|
||||
initial_value=initial_value)
|
||||
|
||||
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,
|
||||
initial_value=initial_value,
|
||||
)
|
||||
|
||||
def __get__(self, obj, cls=None):
|
||||
return str(super().__get__(obj, cls), "utf-8")
|
||||
|
|
@ -50,12 +62,18 @@ class StringCharacteristic(Characteristic):
|
|||
def __set__(self, obj, 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, read_perm=Attribute.OPEN):
|
||||
super().__init__(uuid=uuid, properties=Characteristic.READ,
|
||||
read_perm=read_perm, write_perm=Attribute.NO_ACCESS,
|
||||
fixed_length=True)
|
||||
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):
|
||||
return str(super().__get__(obj, cls), "utf-8")
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ from ..characteristics import Characteristic, ComplexCharacteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class Service:
|
||||
"""Top level Service class that handles the hard work of binding to a local or remote service.
|
||||
|
||||
|
|
@ -44,10 +45,13 @@ class Service:
|
|||
`BLEConnection`. For example, ``connection[UartService]`` will return the UartService
|
||||
instance for the connection's peer.
|
||||
"""
|
||||
|
||||
def __init__(self, *, service=None, secondary=False, **initial_values):
|
||||
if service is None:
|
||||
# pylint: disable=no-member
|
||||
self.bleio_service = _bleio.Service(self.uuid.bleio_uuid, secondary=secondary)
|
||||
self.bleio_service = _bleio.Service(
|
||||
self.uuid.bleio_uuid, secondary=secondary
|
||||
)
|
||||
elif not service.remote:
|
||||
raise ValueError("Can only create services with a remote service or None")
|
||||
else:
|
||||
|
|
@ -65,8 +69,9 @@ class Service:
|
|||
if class_attr.startswith("__"):
|
||||
continue
|
||||
value = getattr(self.__class__, class_attr)
|
||||
if (not isinstance(value, Characteristic) and
|
||||
not isinstance(value, ComplexCharacteristic)):
|
||||
if not isinstance(value, Characteristic) and not isinstance(
|
||||
value, ComplexCharacteristic
|
||||
):
|
||||
continue
|
||||
|
||||
value.field_name = class_attr
|
||||
|
|
|
|||
|
|
@ -36,18 +36,24 @@ from ..uuid import VendorUUID
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class CircuitPythonUUID(VendorUUID):
|
||||
"""UUIDs with the CircuitPython base UUID."""
|
||||
|
||||
def __init__(self, uuid16):
|
||||
uuid128 = bytearray("nhtyPtiucriC".encode("utf-8") + b"\x00\x00\xaf\xad")
|
||||
uuid128[-3] = uuid16 >> 8
|
||||
uuid128[-4] = uuid16 & 0xff
|
||||
uuid128[-4] = uuid16 & 0xFF
|
||||
super().__init__(uuid128)
|
||||
|
||||
|
||||
class CircuitPythonService(Service):
|
||||
"""Core CircuitPython service that allows for file modification and REPL access.
|
||||
Unimplemented."""
|
||||
|
||||
uuid = CircuitPythonUUID(0x0100)
|
||||
filename = StringCharacteristic(uuid=CircuitPythonUUID(0x0200),
|
||||
properties=(Characteristic.READ | Characteristic.WRITE))
|
||||
filename = StringCharacteristic(
|
||||
uuid=CircuitPythonUUID(0x0200),
|
||||
properties=(Characteristic.READ | Characteristic.WRITE),
|
||||
)
|
||||
contents = StreamOut(uuid=CircuitPythonUUID(0x0201))
|
||||
|
|
|
|||
|
|
@ -34,20 +34,30 @@ from ..characteristics import Characteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class MidiIOCharacteristic(Characteristic):
|
||||
"""Workhorse MIDI Characteristic that carries midi messages both directions. Unimplemented."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
uuid = VendorUUID("7772E5DB-3868-4112-A1A9-F2669D106BF3")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(properties=(Characteristic.NOTIFY |
|
||||
Characteristic.READ |
|
||||
Characteristic.WRITE |
|
||||
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."""
|
||||
|
||||
uuid = VendorUUID("03B80E5A-EDE8-4B33-A751-6CE34EC4C700")
|
||||
io = MidiIOCharacteristic() # pylint: disable=invalid-name
|
||||
io = MidiIOCharacteristic() # pylint: disable=invalid-name
|
||||
|
||||
# pylint: disable=unnecessary-pass
|
||||
def write(self):
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ from ..characteristics.stream import StreamOut, StreamIn
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class UARTService(Service):
|
||||
"""
|
||||
Provide UART-like functionality via the Nordic NUS service.
|
||||
|
|
@ -46,12 +47,19 @@ class UARTService(Service):
|
|||
|
||||
See ``examples/ble_uart_echo_test.py`` for a usage example.
|
||||
"""
|
||||
|
||||
# pylint: disable=no-member
|
||||
uuid = VendorUUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
_server_tx = StreamOut(uuid=VendorUUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
|
||||
timeout=1.0, buffer_size=64)
|
||||
_server_rx = StreamIn(uuid=VendorUUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
|
||||
timeout=1.0, buffer_size=64)
|
||||
_server_tx = StreamOut(
|
||||
uuid=VendorUUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
|
||||
timeout=1.0,
|
||||
buffer_size=64,
|
||||
)
|
||||
_server_rx = StreamIn(
|
||||
uuid=VendorUUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
|
||||
timeout=1.0,
|
||||
buffer_size=64,
|
||||
)
|
||||
|
||||
def __init__(self, service=None):
|
||||
super().__init__(service=service)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ from ..uuid import VendorUUID
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class SpheroService(Service):
|
||||
"""Core Sphero Service. Unimplemented."""
|
||||
|
||||
uuid = VendorUUID("!!orehpS OOW\x01\x00\x01\x00")
|
||||
|
|
|
|||
|
|
@ -36,41 +36,51 @@ from ...characteristics.int import Uint8Characteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class AppearanceCharacteristic(StructCharacteristic):
|
||||
"""What type of device it is"""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
uuid = StandardUUID(0x2a01)
|
||||
uuid = StandardUUID(0x2A01)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__("<H", **kwargs)
|
||||
|
||||
|
||||
class GenericAccess(Service):
|
||||
"""Required service that provides basic device information"""
|
||||
|
||||
uuid = StandardUUID(0x1800)
|
||||
device_name = StringCharacteristic(uuid=StandardUUID(0x2a00))
|
||||
device_name = StringCharacteristic(uuid=StandardUUID(0x2A00))
|
||||
appearance = AppearanceCharacteristic()
|
||||
# privacy_flag
|
||||
# reconnection_address
|
||||
# preferred_connection_parameters
|
||||
|
||||
|
||||
class GenericAttribute(Service):
|
||||
"""Required service that provides notifications when Services change"""
|
||||
|
||||
uuid = StandardUUID(0x1801)
|
||||
# service_changed - indicate only
|
||||
|
||||
|
||||
class BatteryService(Service):
|
||||
"""Provides battery level information"""
|
||||
uuid = StandardUUID(0x180f)
|
||||
|
||||
uuid = StandardUUID(0x180F)
|
||||
level = Uint8Characteristic(max_value=100, uuid=StandardUUID(0x2A19))
|
||||
|
||||
|
||||
class CurrentTimeService(Service):
|
||||
"""Provides the current time."""
|
||||
|
||||
uuid = StandardUUID(0x1805)
|
||||
current_time = StructCharacteristic('<HBBBBBBBB', uuid=StandardUUID(0x2a2b))
|
||||
current_time = StructCharacteristic("<HBBBBBBBB", uuid=StandardUUID(0x2A2B))
|
||||
"""A tuple describing the current time:
|
||||
(year, month, day, hour, minute, second, weekday, subsecond, adjust_reason)"""
|
||||
|
||||
local_time_info = StructCharacteristic('<bB', uuid=StandardUUID(0x2a0f))
|
||||
local_time_info = StructCharacteristic("<bB", uuid=StandardUUID(0x2A0F))
|
||||
"""A tuple of location information: (timezone, dst_offset)"""
|
||||
|
||||
@property
|
||||
|
|
@ -80,4 +90,6 @@ class CurrentTimeService(Service):
|
|||
"""
|
||||
year, month, day, hour, minute, second, weekday, _, _ = self.current_time
|
||||
# Bluetooth weekdays count from 1. struct_time counts from 0.
|
||||
return time.struct_time((year, month, day, hour, minute, second, weekday - 1, -1, -1))
|
||||
return time.struct_time(
|
||||
(year, month, day, hour, minute, second, weekday - 1, -1, -1)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -38,35 +38,44 @@ from ...characteristics.string import FixedStringCharacteristic
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class DeviceInfoService(Service):
|
||||
"""Device information"""
|
||||
uuid = StandardUUID(0x180a)
|
||||
model_number = FixedStringCharacteristic(uuid=StandardUUID(0x2a24))
|
||||
serial_number = FixedStringCharacteristic(uuid=StandardUUID(0x2a25))
|
||||
firmware_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2a26))
|
||||
hardware_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2a27))
|
||||
software_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2a28))
|
||||
manufacturer = FixedStringCharacteristic(uuid=StandardUUID(0x2a29))
|
||||
|
||||
def __init__(self, *,
|
||||
manufacturer=None,
|
||||
software_revision=None,
|
||||
model_number=None,
|
||||
serial_number=None,
|
||||
firmware_revision=None,
|
||||
hardware_revision=None,
|
||||
service=None):
|
||||
uuid = StandardUUID(0x180A)
|
||||
model_number = FixedStringCharacteristic(uuid=StandardUUID(0x2A24))
|
||||
serial_number = FixedStringCharacteristic(uuid=StandardUUID(0x2A25))
|
||||
firmware_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2A26))
|
||||
hardware_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2A27))
|
||||
software_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2A28))
|
||||
manufacturer = FixedStringCharacteristic(uuid=StandardUUID(0x2A29))
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
manufacturer=None,
|
||||
software_revision=None,
|
||||
model_number=None,
|
||||
serial_number=None,
|
||||
firmware_revision=None,
|
||||
hardware_revision=None,
|
||||
service=None
|
||||
):
|
||||
if not service:
|
||||
if model_number is None:
|
||||
model_number = sys.platform
|
||||
if serial_number is None:
|
||||
serial_number = binascii.hexlify(microcontroller.cpu.uid).decode('utf-8') # pylint: disable=no-member
|
||||
serial_number = binascii.hexlify(
|
||||
microcontroller.cpu.uid # pylint: disable=no-member
|
||||
).decode("utf-8")
|
||||
if firmware_revision is None:
|
||||
firmware_revision = os.uname().version
|
||||
super().__init__(manufacturer=manufacturer,
|
||||
software_revision=software_revision,
|
||||
model_number=model_number,
|
||||
serial_number=serial_number,
|
||||
firmware_revision=firmware_revision,
|
||||
hardware_revision=hardware_revision,
|
||||
service=service)
|
||||
super().__init__(
|
||||
manufacturer=manufacturer,
|
||||
software_revision=software_revision,
|
||||
model_number=model_number,
|
||||
serial_number=serial_number,
|
||||
firmware_revision=firmware_revision,
|
||||
hardware_revision=hardware_revision,
|
||||
service=service,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -57,84 +57,84 @@ _APPEARANCE_HID_MOUSE = const(962)
|
|||
_APPEARANCE_HID_JOYSTICK = const(963)
|
||||
_APPEARANCE_HID_GAMEPAD = const(964)
|
||||
|
||||
#pylint: disable=line-too-long
|
||||
# pylint: disable=line-too-long
|
||||
DEFAULT_HID_DESCRIPTOR = (
|
||||
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
|
||||
b'\x09\x06' # Usage (Keyboard)
|
||||
b'\xA1\x01' # Collection (Application)
|
||||
b'\x85\x01' # Report ID (1)
|
||||
b'\x05\x07' # Usage Page (Kbrd/Keypad)
|
||||
b'\x19\xE0' # Usage Minimum (\xE0)
|
||||
b'\x29\xE7' # Usage Maximum (\xE7)
|
||||
b'\x15\x00' # Logical Minimum (0)
|
||||
b'\x25\x01' # Logical Maximum (1)
|
||||
b'\x75\x01' # Report Size (1)
|
||||
b'\x95\x08' # Report Count (8)
|
||||
b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\x19\x00' # Usage Minimum (\x00)
|
||||
b'\x29\x65' # Usage Maximum (\x65)
|
||||
b'\x15\x00' # Logical Minimum (0)
|
||||
b'\x25\x65' # Logical Maximum (101)
|
||||
b'\x75\x08' # Report Size (8)
|
||||
b'\x95\x06' # Report Count (6)
|
||||
b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\x05\x08' # Usage Page (LEDs)
|
||||
b'\x19\x01' # Usage Minimum (Num Lock)
|
||||
b'\x29\x05' # Usage Maximum (Kana)
|
||||
b'\x15\x00' # Logical Minimum (0)
|
||||
b'\x25\x01' # Logical Maximum (1)
|
||||
b'\x75\x01' # Report Size (1)
|
||||
b'\x95\x05' # Report Count (5)
|
||||
b'\x91\x02' # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
b'\x95\x03' # Report Count (3)
|
||||
b'\x91\x01' # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
b'\xC0' # End Collection
|
||||
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
|
||||
b'\x09\x02' # Usage (Mouse)
|
||||
b'\xA1\x01' # Collection (Application)
|
||||
b'\x09\x01' # Usage (Pointer)
|
||||
b'\xA1\x00' # Collection (Physical)
|
||||
b'\x85\x02' # Report ID (2)
|
||||
b'\x05\x09' # Usage Page (Button)
|
||||
b'\x19\x01' # Usage Minimum (\x01)
|
||||
b'\x29\x05' # Usage Maximum (\x05)
|
||||
b'\x15\x00' # Logical Minimum (0)
|
||||
b'\x25\x01' # Logical Maximum (1)
|
||||
b'\x95\x05' # Report Count (5)
|
||||
b'\x75\x01' # Report Size (1)
|
||||
b'\x81\x02' # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\x95\x01' # Report Count (1)
|
||||
b'\x75\x03' # Report Size (3)
|
||||
b'\x81\x01' # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
|
||||
b'\x09\x30' # Usage (X)
|
||||
b'\x09\x31' # Usage (Y)
|
||||
b'\x15\x81' # Logical Minimum (-127)
|
||||
b'\x25\x7F' # Logical Maximum (127)
|
||||
b'\x75\x08' # Report Size (8)
|
||||
b'\x95\x02' # Report Count (2)
|
||||
b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\x09\x38' # Usage (Wheel)
|
||||
b'\x15\x81' # Logical Minimum (-127)
|
||||
b'\x25\x7F' # Logical Maximum (127)
|
||||
b'\x75\x08' # Report Size (8)
|
||||
b'\x95\x01' # Report Count (1)
|
||||
b'\x81\x06' # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\xC0' # End Collection
|
||||
b'\xC0' # End Collection
|
||||
b'\x05\x0C' # Usage Page (Consumer)
|
||||
b'\x09\x01' # Usage (Consumer Control)
|
||||
b'\xA1\x01' # Collection (Application)
|
||||
b'\x85\x03' # Report ID (3)
|
||||
b'\x75\x10' # Report Size (16)
|
||||
b'\x95\x01' # Report Count (1)
|
||||
b'\x15\x01' # Logical Minimum (1)
|
||||
b'\x26\x8C\x02' # Logical Maximum (652)
|
||||
b'\x19\x01' # Usage Minimum (Consumer Control)
|
||||
b'\x2A\x8C\x02' # Usage Maximum (AC Send)
|
||||
b'\x81\x00' # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b'\xC0' # End Collection
|
||||
b"\x05\x01" # Usage Page (Generic Desktop Ctrls)
|
||||
b"\x09\x06" # Usage (Keyboard)
|
||||
b"\xA1\x01" # Collection (Application)
|
||||
b"\x85\x01" # Report ID (1)
|
||||
b"\x05\x07" # Usage Page (Kbrd/Keypad)
|
||||
b"\x19\xE0" # Usage Minimum (\xE0)
|
||||
b"\x29\xE7" # Usage Maximum (\xE7)
|
||||
b"\x15\x00" # Logical Minimum (0)
|
||||
b"\x25\x01" # Logical Maximum (1)
|
||||
b"\x75\x01" # Report Size (1)
|
||||
b"\x95\x08" # Report Count (8)
|
||||
b"\x81\x02" # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\x81\x01" # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\x19\x00" # Usage Minimum (\x00)
|
||||
b"\x29\x65" # Usage Maximum (\x65)
|
||||
b"\x15\x00" # Logical Minimum (0)
|
||||
b"\x25\x65" # Logical Maximum (101)
|
||||
b"\x75\x08" # Report Size (8)
|
||||
b"\x95\x06" # Report Count (6)
|
||||
b"\x81\x00" # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\x05\x08" # Usage Page (LEDs)
|
||||
b"\x19\x01" # Usage Minimum (Num Lock)
|
||||
b"\x29\x05" # Usage Maximum (Kana)
|
||||
b"\x15\x00" # Logical Minimum (0)
|
||||
b"\x25\x01" # Logical Maximum (1)
|
||||
b"\x75\x01" # Report Size (1)
|
||||
b"\x95\x05" # Report Count (5)
|
||||
b"\x91\x02" # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
b"\x95\x03" # Report Count (3)
|
||||
b"\x91\x01" # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
b"\xC0" # End Collection
|
||||
b"\x05\x01" # Usage Page (Generic Desktop Ctrls)
|
||||
b"\x09\x02" # Usage (Mouse)
|
||||
b"\xA1\x01" # Collection (Application)
|
||||
b"\x09\x01" # Usage (Pointer)
|
||||
b"\xA1\x00" # Collection (Physical)
|
||||
b"\x85\x02" # Report ID (2)
|
||||
b"\x05\x09" # Usage Page (Button)
|
||||
b"\x19\x01" # Usage Minimum (\x01)
|
||||
b"\x29\x05" # Usage Maximum (\x05)
|
||||
b"\x15\x00" # Logical Minimum (0)
|
||||
b"\x25\x01" # Logical Maximum (1)
|
||||
b"\x95\x05" # Report Count (5)
|
||||
b"\x75\x01" # Report Size (1)
|
||||
b"\x81\x02" # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\x95\x01" # Report Count (1)
|
||||
b"\x75\x03" # Report Size (3)
|
||||
b"\x81\x01" # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\x05\x01" # Usage Page (Generic Desktop Ctrls)
|
||||
b"\x09\x30" # Usage (X)
|
||||
b"\x09\x31" # Usage (Y)
|
||||
b"\x15\x81" # Logical Minimum (-127)
|
||||
b"\x25\x7F" # Logical Maximum (127)
|
||||
b"\x75\x08" # Report Size (8)
|
||||
b"\x95\x02" # Report Count (2)
|
||||
b"\x81\x06" # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\x09\x38" # Usage (Wheel)
|
||||
b"\x15\x81" # Logical Minimum (-127)
|
||||
b"\x25\x7F" # Logical Maximum (127)
|
||||
b"\x75\x08" # Report Size (8)
|
||||
b"\x95\x01" # Report Count (1)
|
||||
b"\x81\x06" # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\xC0" # End Collection
|
||||
b"\xC0" # End Collection
|
||||
b"\x05\x0C" # Usage Page (Consumer)
|
||||
b"\x09\x01" # Usage (Consumer Control)
|
||||
b"\xA1\x01" # Collection (Application)
|
||||
b"\x85\x03" # Report ID (3)
|
||||
b"\x75\x10" # Report Size (16)
|
||||
b"\x95\x01" # Report Count (1)
|
||||
b"\x15\x01" # Logical Minimum (1)
|
||||
b"\x26\x8C\x02" # Logical Maximum (652)
|
||||
b"\x19\x01" # Usage Minimum (Consumer Control)
|
||||
b"\x2A\x8C\x02" # Usage Maximum (AC Send)
|
||||
b"\x81\x00" # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
b"\xC0" # End Collection
|
||||
# b'\x05\x01' # Usage Page (Generic Desktop Ctrls)
|
||||
# b'\x09\x05' # Usage (Game Pad)
|
||||
# b'\xA1\x01' # Collection (Application)
|
||||
|
|
@ -160,7 +160,7 @@ DEFAULT_HID_DESCRIPTOR = (
|
|||
# b'\xC0' # End Collection
|
||||
)
|
||||
"""Default HID descriptor: provides mouse, keyboard, and consumer control devices."""
|
||||
#pylint: enable=line-too-long
|
||||
# pylint: enable=line-too-long
|
||||
|
||||
|
||||
# Boot keyboard and mouse not currently supported.
|
||||
|
|
@ -173,54 +173,74 @@ _REPORT_TYPE_INPUT = const(1)
|
|||
_REPORT_TYPE_OUTPUT = const(2)
|
||||
|
||||
# Boot Protocol mode not currently implemented
|
||||
_PROTOCOL_MODE_BOOT = b'\x00'
|
||||
_PROTOCOL_MODE_REPORT = b'\x01'
|
||||
_PROTOCOL_MODE_BOOT = b"\x00"
|
||||
_PROTOCOL_MODE_REPORT = b"\x01"
|
||||
|
||||
|
||||
class ReportIn:
|
||||
"""A single HID report that transmits HID data into a client."""
|
||||
|
||||
uuid = StandardUUID(_REPORT_UUID_NUM)
|
||||
|
||||
def __init__(self, service, report_id, usage_page, usage, *, max_length):
|
||||
self._characteristic = _bleio.Characteristic.add_to_service(
|
||||
service.bleio_service,
|
||||
self.uuid.bleio_uuid,
|
||||
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
|
||||
self.usage = usage
|
||||
|
||||
_bleio.Descriptor.add_to_characteristic(
|
||||
self._characteristic, _REPORT_REF_DESCR_UUID,
|
||||
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):
|
||||
"""Send a report to the peers"""
|
||||
self._characteristic.value = report
|
||||
|
||||
class ReportOut:
|
||||
"""A single HID report that receives HID data from a client."""
|
||||
# pylint: disable=too-few-public-methods
|
||||
uuid = StandardUUID(_REPORT_UUID_NUM)
|
||||
def __init__(self, service, report_id, usage_page, usage, *, max_length):
|
||||
self._characteristic = _bleio.Characteristic.add_to_service(
|
||||
service.bleio_service,
|
||||
self.uuid.bleio_uuid,
|
||||
read_perm=Attribute.ENCRYPT_NO_MITM,
|
||||
write_perm=Attribute.NO_ACCESS,
|
||||
max_length=max_length,
|
||||
fixed_length=True,
|
||||
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
|
||||
self.usage = usage
|
||||
|
||||
_bleio.Descriptor.add_to_characteristic(
|
||||
self._characteristic, _REPORT_REF_DESCR_UUID,
|
||||
read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS,
|
||||
initial_value=struct.pack('<BB', self._report_id, _REPORT_TYPE_OUTPUT))
|
||||
self._characteristic,
|
||||
_REPORT_REF_DESCR_UUID,
|
||||
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):
|
||||
"""Send a report to the peers"""
|
||||
self._characteristic.value = report
|
||||
|
||||
|
||||
class ReportOut:
|
||||
"""A single HID report that receives HID data from a client."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
uuid = StandardUUID(_REPORT_UUID_NUM)
|
||||
|
||||
def __init__(self, service, report_id, usage_page, usage, *, max_length):
|
||||
self._characteristic = _bleio.Characteristic.add_to_service(
|
||||
service.bleio_service,
|
||||
self.uuid.bleio_uuid,
|
||||
max_length=max_length,
|
||||
fixed_length=True,
|
||||
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
|
||||
self.usage = usage
|
||||
|
||||
_bleio.Descriptor.add_to_characteristic(
|
||||
self._characteristic,
|
||||
_REPORT_REF_DESCR_UUID,
|
||||
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)
|
||||
_ITEM_TYPE_GLOBAL = const(1)
|
||||
|
|
@ -232,6 +252,7 @@ _MAIN_ITEM_TAG_INPUT = const(0b1000)
|
|||
_MAIN_ITEM_TAG_OUTPUT = const(0b1001)
|
||||
_MAIN_ITEM_TAG_FEATURE = const(0b1011)
|
||||
|
||||
|
||||
class HIDService(Service):
|
||||
"""
|
||||
Provide devices for HID over BLE.
|
||||
|
|
@ -245,55 +266,70 @@ class HIDService(Service):
|
|||
|
||||
hid = HIDServer()
|
||||
"""
|
||||
|
||||
uuid = StandardUUID(0x1812)
|
||||
|
||||
boot_keyboard_in = Characteristic(uuid=StandardUUID(0x2A22),
|
||||
properties=(Characteristic.READ |
|
||||
Characteristic.NOTIFY),
|
||||
read_perm=Attribute.ENCRYPT_NO_MITM,
|
||||
write_perm=Attribute.NO_ACCESS,
|
||||
max_length=8, fixed_length=True)
|
||||
boot_keyboard_in = Characteristic(
|
||||
uuid=StandardUUID(0x2A22),
|
||||
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=(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)
|
||||
boot_keyboard_out = Characteristic(
|
||||
uuid=StandardUUID(0x2A32),
|
||||
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=(Characteristic.READ |
|
||||
Characteristic.WRITE_NO_RESPONSE),
|
||||
read_perm=Attribute.OPEN,
|
||||
write_perm=Attribute.OPEN,
|
||||
initial_value=1, max_value=1)
|
||||
protocol_mode = Uint8Characteristic(
|
||||
uuid=StandardUUID(0x2A4E),
|
||||
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)"""
|
||||
|
||||
|
||||
# bcdHID (version), bCountryCode (0 not localized), Flags: RemoteWake, NormallyConnectable
|
||||
# bcd1.1, country = 0, flag = normal connect
|
||||
# TODO: Make this a struct.
|
||||
hid_information = Characteristic(uuid=StandardUUID(0x2A4A),
|
||||
properties=Characteristic.READ,
|
||||
read_perm=Attribute.ENCRYPT_NO_MITM,
|
||||
write_perm=Attribute.NO_ACCESS,
|
||||
initial_value=b'\x01\x01\x00\x02')
|
||||
hid_information = Characteristic(
|
||||
uuid=StandardUUID(0x2A4A),
|
||||
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=Characteristic.READ,
|
||||
read_perm=Attribute.ENCRYPT_NO_MITM,
|
||||
write_perm=Attribute.NO_ACCESS,
|
||||
fixed_length=True)
|
||||
report_map = Characteristic(
|
||||
uuid=StandardUUID(0x2A4B),
|
||||
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=Characteristic.WRITE_NO_RESPONSE,
|
||||
read_perm=Attribute.NO_ACCESS,
|
||||
write_perm=Attribute.ENCRYPT_NO_MITM,
|
||||
max_value=1)
|
||||
suspended = Uint8Characteristic(
|
||||
uuid=StandardUUID(0x2A4C),
|
||||
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)."""
|
||||
|
||||
def __init__(self, hid_descriptor=DEFAULT_HID_DESCRIPTOR, service=None):
|
||||
|
|
@ -316,20 +352,24 @@ class HIDService(Service):
|
|||
i = 0
|
||||
while i < len(hid_descriptor):
|
||||
b = hid_descriptor[i]
|
||||
tag = (b & 0xf0) >> 4
|
||||
tag = (b & 0xF0) >> 4
|
||||
_type = (b & 0b1100) >> 2
|
||||
size = b & 0b11
|
||||
size = 4 if size == 3 else size
|
||||
i += 1
|
||||
data = hid_descriptor[i:i+size]
|
||||
data = hid_descriptor[i : i + size]
|
||||
if _type == _ITEM_TYPE_GLOBAL:
|
||||
global_table[tag] = data
|
||||
elif _type == _ITEM_TYPE_MAIN:
|
||||
if tag == _MAIN_ITEM_TAG_START_COLLECTION:
|
||||
collections.append({"type": data,
|
||||
"locals": list(local_table),
|
||||
"globals": list(global_table),
|
||||
"mains": []})
|
||||
collections.append(
|
||||
{
|
||||
"type": data,
|
||||
"locals": list(local_table),
|
||||
"globals": list(global_table),
|
||||
"mains": [],
|
||||
}
|
||||
)
|
||||
elif tag == _MAIN_ITEM_TAG_END_COLLECTION:
|
||||
collection = collections.pop()
|
||||
# This is a top level collection if the collections list is now empty.
|
||||
|
|
@ -338,13 +378,21 @@ class HIDService(Service):
|
|||
else:
|
||||
collections[-1]["mains"].append(collection)
|
||||
elif tag == _MAIN_ITEM_TAG_INPUT:
|
||||
collections[-1]["mains"].append({"tag": "input",
|
||||
"locals": list(local_table),
|
||||
"globals": list(global_table)})
|
||||
collections[-1]["mains"].append(
|
||||
{
|
||||
"tag": "input",
|
||||
"locals": list(local_table),
|
||||
"globals": list(global_table),
|
||||
}
|
||||
)
|
||||
elif tag == _MAIN_ITEM_TAG_OUTPUT:
|
||||
collections[-1]["mains"].append({"tag": "output",
|
||||
"locals": list(local_table),
|
||||
"globals": list(global_table)})
|
||||
collections[-1]["mains"].append(
|
||||
{
|
||||
"tag": "output",
|
||||
"locals": list(local_table),
|
||||
"globals": list(global_table),
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise RuntimeError("Unsupported main item in HID descriptor")
|
||||
local_table = [None] * 3
|
||||
|
|
@ -359,7 +407,9 @@ class HIDService(Service):
|
|||
if "type" in main:
|
||||
get_report_info(main, reports)
|
||||
else:
|
||||
report_size, report_id, report_count = [x[0] for x in main["globals"][7:10]]
|
||||
report_size, report_id, report_count = [
|
||||
x[0] for x in main["globals"][7:10]
|
||||
]
|
||||
if report_id not in reports:
|
||||
reports[report_id] = {"input_size": 0, "output_size": 0}
|
||||
if main["tag"] == "input":
|
||||
|
|
@ -367,24 +417,33 @@ class HIDService(Service):
|
|||
elif main["tag"] == "output":
|
||||
reports[report_id]["output_size"] += report_size * report_count
|
||||
|
||||
|
||||
for collection in top_level_collections:
|
||||
if collection["type"][0] != 1:
|
||||
raise NotImplementedError("Only Application top level collections supported.")
|
||||
raise NotImplementedError(
|
||||
"Only Application top level collections supported."
|
||||
)
|
||||
usage_page = collection["globals"][0][0]
|
||||
usage = collection["locals"][0][0]
|
||||
reports = {}
|
||||
get_report_info(collection, reports)
|
||||
if len(reports) > 1:
|
||||
raise NotImplementedError("Only one report id per Application collection supported")
|
||||
raise NotImplementedError(
|
||||
"Only one report id per Application collection supported"
|
||||
)
|
||||
|
||||
report_id, report = list(reports.items())[0]
|
||||
output_size = report["output_size"]
|
||||
if output_size > 0:
|
||||
self.devices.append(ReportOut(self, report_id, usage_page, usage,
|
||||
max_length=output_size // 8))
|
||||
self.devices.append(
|
||||
ReportOut(
|
||||
self, report_id, usage_page, usage, max_length=output_size // 8
|
||||
)
|
||||
)
|
||||
|
||||
input_size = reports[report_id]["input_size"]
|
||||
if input_size > 0:
|
||||
self.devices.append(ReportIn(self, report_id, usage_page, usage,
|
||||
max_length=input_size // 8))
|
||||
self.devices.append(
|
||||
ReportIn(
|
||||
self, report_id, usage_page, usage, max_length=input_size // 8
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ import _bleio
|
|||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git"
|
||||
|
||||
|
||||
class UUID:
|
||||
"""Top level UUID"""
|
||||
|
||||
# TODO: Make subclassing _bleio.UUID work so we can just use it directly.
|
||||
# pylint: disable=no-member
|
||||
def __hash__(self):
|
||||
|
|
@ -60,16 +62,20 @@ class UUID:
|
|||
"""Packs the UUID into the buffer at the given offset."""
|
||||
self.bleio_uuid.pack_into(buffer, offset=offset)
|
||||
|
||||
|
||||
class StandardUUID(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]
|
||||
self.bleio_uuid = _bleio.UUID(uuid16)
|
||||
self.size = 16
|
||||
|
||||
|
||||
class VendorUUID(UUID):
|
||||
"""Vendor defined, 128-bit UUID."""
|
||||
|
||||
def __init__(self, uuid128):
|
||||
self.bleio_uuid = _bleio.UUID(uuid128)
|
||||
self.size = 128
|
||||
|
|
|
|||
113
docs/conf.py
113
docs/conf.py
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
|
|
@ -10,10 +11,10 @@ sys.path.insert(0, os.path.abspath('..'))
|
|||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinx.ext.todo',
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx.ext.todo",
|
||||
]
|
||||
|
||||
# TODO: Please Read!
|
||||
|
|
@ -24,30 +25,32 @@ autodoc_mock_imports = ["board", "microcontroller"]
|
|||
autodoc_member_order = "bysource"
|
||||
add_module_names = False
|
||||
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),
|
||||
'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)}
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3.4", None),
|
||||
"CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None),
|
||||
}
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'Adafruit ble Library'
|
||||
copyright = u'2019 Dan Halbert for Adafruit Industries'
|
||||
author = u'Dan Halbert'
|
||||
project = u"Adafruit ble Library"
|
||||
copyright = u"2019 Dan Halbert for Adafruit Industries"
|
||||
author = u"Dan Halbert"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'1.0'
|
||||
version = u"1.0"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'1.0'
|
||||
release = u"1.0"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
@ -59,7 +62,7 @@ language = None
|
|||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.env', 'CODE_OF_CONDUCT.md']
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".env", "CODE_OF_CONDUCT.md"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
|
|
@ -71,7 +74,7 @@ default_role = "any"
|
|||
add_function_parentheses = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
|
@ -86,59 +89,62 @@ napoleon_numpy_docstring = False
|
|||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
|
||||
|
||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
||||
try:
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.']
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."]
|
||||
except:
|
||||
html_theme = 'default'
|
||||
html_theme_path = ['.']
|
||||
html_theme = "default"
|
||||
html_theme_path = ["."]
|
||||
else:
|
||||
html_theme_path = ['.']
|
||||
html_theme_path = ["."]
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# The name of an image file (relative to this directory) to use as a favicon of
|
||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#
|
||||
html_favicon = '_static/favicon.ico'
|
||||
html_favicon = "_static/favicon.ico"
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'AdafruitBleLibrarydoc'
|
||||
htmlhelp_basename = "AdafruitBleLibrarydoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'AdafruitbleLibrary.tex', u'Adafruit BLE Library Documentation',
|
||||
author, 'manual'),
|
||||
(
|
||||
master_doc,
|
||||
"AdafruitbleLibrary.tex",
|
||||
u"Adafruit BLE Library Documentation",
|
||||
author,
|
||||
"manual",
|
||||
),
|
||||
]
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
|
@ -146,8 +152,13 @@ latex_documents = [
|
|||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'Adafruitblelibrary', u'Adafruit BLE Library Documentation',
|
||||
[author], 1)
|
||||
(
|
||||
master_doc,
|
||||
"Adafruitblelibrary",
|
||||
u"Adafruit BLE Library Documentation",
|
||||
[author],
|
||||
1,
|
||||
)
|
||||
]
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
|
@ -156,7 +167,13 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'AdafruitbleLibrary', u'Adafruit BLE Library Documentation',
|
||||
author, 'AdafruitbleLibrary', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
"AdafruitbleLibrary",
|
||||
u"Adafruit BLE Library Documentation",
|
||||
author,
|
||||
"AdafruitbleLibrary",
|
||||
"One line description of project.",
|
||||
"Miscellaneous",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -31,5 +31,5 @@ while True:
|
|||
|
||||
while ble.connected:
|
||||
print((scale(light.value), thermistor.temperature))
|
||||
uart_server.write('{},{}\n'.format(scale(light.value), thermistor.temperature))
|
||||
uart_server.write("{},{}\n".format(scale(light.value), thermistor.temperature))
|
||||
time.sleep(0.1)
|
||||
|
|
|
|||
|
|
@ -14,16 +14,18 @@ from adafruit_ble import BLERadio
|
|||
from adafruit_ble.advertising.adafruit import AdafruitColor
|
||||
|
||||
# The color pickers will cycle through this list with buttons A and B.
|
||||
color_options = [0x110000,
|
||||
0x111100,
|
||||
0x001100,
|
||||
0x001111,
|
||||
0x000011,
|
||||
0x110011,
|
||||
0x111111,
|
||||
0x221111,
|
||||
0x112211,
|
||||
0x111122]
|
||||
color_options = [
|
||||
0x110000,
|
||||
0x111100,
|
||||
0x001100,
|
||||
0x001111,
|
||||
0x000011,
|
||||
0x110011,
|
||||
0x111111,
|
||||
0x221111,
|
||||
0x112211,
|
||||
0x111122,
|
||||
]
|
||||
|
||||
ble = BLERadio()
|
||||
|
||||
|
|
@ -85,7 +87,7 @@ while True:
|
|||
closest_rssi = entry.rssi
|
||||
closest_last_time = now
|
||||
discrete_strength = min((100 + entry.rssi) // 5, 10)
|
||||
#print(entry.rssi, discrete_strength)
|
||||
# print(entry.rssi, discrete_strength)
|
||||
neopixels.fill(0x000000)
|
||||
for i in range(0, discrete_strength):
|
||||
neopixels[i] = entry.color
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ import neopixel
|
|||
|
||||
from adafruit_bluefruit_connect.color_packet import ColorPacket
|
||||
|
||||
|
||||
def scale(value):
|
||||
"""Scale an value from (acceleration range) to 0-255 (RGB range)"""
|
||||
value = abs(value)
|
||||
value = max(min(19.6, value), 0)
|
||||
return int(value / 19.6 * 255)
|
||||
|
||||
|
||||
i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
|
||||
int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT)
|
||||
accelerometer = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19, int1=int1)
|
||||
|
|
@ -62,7 +64,7 @@ while True:
|
|||
except OSError:
|
||||
try:
|
||||
uart_connection.disconnect()
|
||||
except: # pylint: disable=bare-except
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
uart_connection = None
|
||||
time.sleep(0.3)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ NUM_PIXELS = 10
|
|||
np = neopixel.NeoPixel(board.NEOPIXEL, NUM_PIXELS, brightness=0.1)
|
||||
next_pixel = 0
|
||||
|
||||
|
||||
def mod(i):
|
||||
"""Wrap i to modulus NUM_PIXELS."""
|
||||
return i % NUM_PIXELS
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
|
|||
|
||||
# Use default HID descriptor
|
||||
hid = HIDService()
|
||||
device_info = DeviceInfoService(software_revision=adafruit_ble.__version__,
|
||||
manufacturer="Adafruit Industries")
|
||||
device_info = DeviceInfoService(
|
||||
software_revision=adafruit_ble.__version__, manufacturer="Adafruit Industries"
|
||||
)
|
||||
advertisement = ProvideServicesAdvertisement(hid)
|
||||
advertisement.appearance = 961
|
||||
scan_response = Advertisement()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ from adafruit_ble.services.nordic import UARTService
|
|||
|
||||
ble = BLERadio()
|
||||
while True:
|
||||
while ble.connected and any(UARTService in connection for connection in ble.connections):
|
||||
while ble.connected and any(
|
||||
UARTService in connection for connection in ble.connections
|
||||
):
|
||||
for connection in ble.connections:
|
||||
if UARTService not in connection:
|
||||
continue
|
||||
|
|
|
|||
51
setup.py
51
setup.py
|
|
@ -6,6 +6,7 @@ https://github.com/pypa/sampleproject
|
|||
"""
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
# To use a consistent encoding
|
||||
from codecs import open
|
||||
from os import path
|
||||
|
|
@ -13,48 +14,36 @@ from os import path
|
|||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
# Get the long description from the README file
|
||||
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||
with open(path.join(here, "README.rst"), encoding="utf-8") as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='adafruit-circuitpython-ble',
|
||||
|
||||
name="adafruit-circuitpython-ble",
|
||||
use_scm_version=True,
|
||||
setup_requires=['setuptools_scm'],
|
||||
|
||||
description='Bluetooth Low Energy (BLE) library for CircuitPython',
|
||||
setup_requires=["setuptools_scm"],
|
||||
description="Bluetooth Low Energy (BLE) library for CircuitPython",
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/x-rst',
|
||||
|
||||
long_description_content_type="text/x-rst",
|
||||
# The project's main homepage.
|
||||
url='https://github.com/adafruit/Adafruit_CircuitPython_BLE',
|
||||
|
||||
url="https://github.com/adafruit/Adafruit_CircuitPython_BLE",
|
||||
# Author details
|
||||
author='Adafruit Industries',
|
||||
author_email='circuitpython@adafruit.com',
|
||||
|
||||
install_requires=[
|
||||
'adafruit-blinka',
|
||||
'adafruit-blinka-bleio'
|
||||
],
|
||||
|
||||
author="Adafruit Industries",
|
||||
author_email="circuitpython@adafruit.com",
|
||||
install_requires=["adafruit-blinka", "adafruit-blinka-bleio"],
|
||||
# Choose your license
|
||||
license='MIT',
|
||||
|
||||
license="MIT",
|
||||
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Intended Audience :: Developers',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: System :: Hardware',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: System :: Hardware",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
],
|
||||
|
||||
# What does your project relate to?
|
||||
keywords='adafruit blinka circuitpython micropython ble bluetooth',
|
||||
|
||||
keywords="adafruit blinka circuitpython micropython ble bluetooth",
|
||||
packages=find_packages(include=["adafruit_ble", "adafruit_ble.*"]),
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue