Skip to content

Commit

Permalink
Merge pull request #63 from dhalbert/fix-lazy-advertising-fields
Browse files Browse the repository at this point in the history
prevent unwanted lazy advertising object instantiation
  • Loading branch information
tannewt committed Jan 21, 2020
2 parents 14f9064 + 92f75d3 commit 40dc09e
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 33 deletions.
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

0 comments on commit 40dc09e

Please sign in to comment.