Skip to content

prevent unwanted lazy advertising object instantiation #63

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 24 additions & 21 deletions adafruit_ble/advertising/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,31 +116,24 @@ class AdvertisingFlags(AdvertisingDataField):
def __init__(self, advertisement, advertising_data_type):
self._advertisement = advertisement
self._adt = advertising_data_type
self.flags = None
self.flags = 0
if self._adt in self._advertisement.data_dict:
self.flags = self._advertisement.data_dict[self._adt][0]
elif self._advertisement.mutable:
self.flags = 0b110 # Default to General discovery and LE Only
else:
self.flags = 0

def __len__(self):
return 1

def __bytes__(self):
encoded = bytearray(1)
encoded[0] = self.flags
return encoded
return bytes([self.flags])

def __str__(self):
parts = ["<AdvertisingFlags"]
parts = []
for attr in dir(self.__class__):
attribute_instance = getattr(self.__class__, attr)
if issubclass(attribute_instance.__class__, AdvertisingFlag):
if getattr(self, attr):
parts.append(attr)
parts.append(">")
return " ".join(parts)
return "<AdvertisingFlags {} >".format(" ".join(parts))

class String(AdvertisingDataField):
"""UTF-8 encoded string in an Advertisement.
Expand Down Expand Up @@ -172,7 +165,7 @@ def __set__(self, obj, value):
obj.data_dict[self._adt] = struct.pack(self._format, value)


class LazyField(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
Expand All @@ -184,18 +177,24 @@ def __get__(self, obj, cls):
# Return None if our object is immutable and the data is not present.
if not obj.mutable and self._adt not in obj.data_dict:
return None
bound_class = self._cls(obj, advertising_data_type=self._adt, **self._kwargs)
setattr(obj, self._attribute_name, bound_class)
obj.data_dict[self._adt] = bound_class
return bound_class
# Instantiate the object.
bound_obj = self._cls(obj, advertising_data_type=self._adt, **self._kwargs)
setattr(obj, self._attribute_name, bound_obj)
obj.data_dict[self._adt] = bound_obj
return bound_obj

@property
def advertising_data_type(self):
"""Return the data type value used to indicate this field."""
return self._adt

# 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.
flags = LazyField(AdvertisingFlags, "flags", advertising_data_type=0x01)
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)
Expand Down Expand Up @@ -263,15 +262,19 @@ def __bytes__(self):
return encode_data(self.data_dict)

def __str__(self):
parts = ["<" + self.__class__.__name__]
parts = []
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):
# Skip uninstantiated lazy objects; if we get
# their value, they will be be instantiated.
continue
value = getattr(self, attr)
if value is not None:
parts.append(attr + "=" + str(value))
parts.append(">")
return " ".join(parts)
parts.append("{}={}".format(attr, str(value)))
return "<{} {} >".format(self.__class__.__name__, " ".join(parts))

def __len__(self):
return compute_length(self.data_dict)
Expand Down
22 changes: 11 additions & 11 deletions adafruit_ble/advertising/adafruit.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import struct
from micropython import const

from . import Advertisement, LazyField
from . import Advertisement, LazyObjectField
from .standard import ManufacturerData, ManufacturerDataField

__version__ = "0.0.0-auto.0"
Expand All @@ -55,11 +55,11 @@ class AdafruitColor(Advertisement):
_ADAFRUIT_COMPANY_ID,
struct.calcsize("<HI"),
_COLOR_DATA_ID)
manufacturer_data = LazyField(ManufacturerData,
"manufacturer_data",
advertising_data_type=_MANUFACTURING_DATA_ADT,
company_id=_ADAFRUIT_COMPANY_ID,
key_encoding="<H")
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."""

Expand All @@ -71,9 +71,9 @@ class AdafruitRadio(Advertisement):
_MANUFACTURING_DATA_ADT,
_ADAFRUIT_COMPANY_ID,
_RADIO_DATA_ID)
manufacturer_data = LazyField(ManufacturerData,
"manufacturer_data",
advertising_data_type=_MANUFACTURING_DATA_ADT,
company_id=_ADAFRUIT_COMPANY_ID,
key_encoding="<H")
manufacturer_data = LazyObjectField(ManufacturerData,
"manufacturer_data",
advertising_data_type=_MANUFACTURING_DATA_ADT,
company_id=_ADAFRUIT_COMPANY_ID,
key_encoding="<H")
msg = ManufacturerDataField(_RADIO_DATA_ID, "<248s") # 255 byte ads
6 changes: 5 additions & 1 deletion adafruit_ble/advertising/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ def __init__(self, *services):
if services:
self.services.extend(services)
self.connectable = True
self.flags.general_discovery = True
self.flags.le_only = True

@classmethod
def matches(cls, entry):
Expand All @@ -171,9 +173,11 @@ def __init__(self, *services):
super().__init__()
self.solicited_services.extend(services)
self.connectable = True
self.flags.general_discovery = True
self.flags.le_only = True


class ManufacturerData:
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."""
Expand Down