class AdafruitServerAdvertisement( Advertisement ): # pylint: disable=too-few-public-methods """Advertise the Adafruit company ID and the board USB PID.""" match_prefixes = ( struct.pack( "<BHBH", _MANUFACTURING_DATA_ADT, _ADAFRUIT_COMPANY_ID, struct.calcsize("<HH"), _PID_DATA_ID, ), ) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=_MANUFACTURING_DATA_ADT, company_id=_ADAFRUIT_COMPANY_ID, key_encoding="<H", ) pid = ManufacturerDataField(_PID_DATA_ID, "<H") """The USB PID (product id) for this board.""" def __init__(self): super().__init__() self.connectable = True self.flags.general_discovery = True self.flags.le_only = True
class _RadioAdvertisement(Advertisement): """Broadcast arbitrary bytes as a radio message.""" prefix = struct.pack("<BBH", 0x3, 0xFF, _ADAFRUIT_COMPANY_ID) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=_MANUFACTURING_DATA_ADT, company_id=_ADAFRUIT_COMPANY_ID, key_encoding="<H", ) @classmethod def matches(cls, entry): if len(entry.advertisement_bytes) < 6: return False # Check the key position within the manufacturer data. We already know # prefix matches so we don't need to check it twice. return struct.unpack_from("<H", entry.advertisement_bytes, 5)[0] == _RADIO_DATA_ID @property def msg(self): """Raw radio data""" if _RADIO_DATA_ID not in self.manufacturer_data.data: return b"" return self.manufacturer_data.data[_RADIO_DATA_ID] @msg.setter def msg(self, value): self.manufacturer_data.data[_RADIO_DATA_ID] = value
class RpsAdvertisement(Advertisement): """Broadcast an RPS message. This is not connectable and elicits no scan_response based on defaults in Advertisement parent class.""" flags = None _PREFIX_FMT = "<BHBH" _DATA_FMT = "8s" # this NUL pads if necessary # match_prefixes tuple replaces deprecated prefix # comma for 1 element is very important! match_prefixes = (struct.pack(_PREFIX_FMT, MANUFACTURING_DATA_ADT, ADAFRUIT_COMPANY_ID, struct.calcsize("<H" + _DATA_FMT), RPS_DATA_ID), ) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=MANUFACTURING_DATA_ADT, company_id=ADAFRUIT_COMPANY_ID, key_encoding="<H", ) test_string = ManufacturerDataField(RPS_DATA_ID, "<" + _DATA_FMT) """RPS choice."""
class JoinGameAdvertisement(Advertisement): """A join game (broadcast) message used as the first message to work out who is playing. This is not connectable and does not elicit a scan response based on defaults in Advertisement parent class. """ flags = None _PREFIX_FMT = "<BHBH" _DATA_FMT = "8s" ### this NUL pads for 8s if necessary ### match_prefixes tuple replaces deprecated prefix ### comma for 1 element is very important! match_prefixes = (struct.pack(_PREFIX_FMT, MANUFACTURING_DATA_ADT, ADAFRUIT_COMPANY_ID, struct.calcsize("<H" + _DATA_FMT), GM_JOIN_ID), ) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=MANUFACTURING_DATA_ADT, company_id=ADAFRUIT_COMPANY_ID, key_encoding="<H") game = ManufacturerDataField(GM_JOIN_ID, "<" + _DATA_FMT) """The name of the game, limited to eight characters.""" def __init__(self, *, game=None): super().__init__() if game is not None: self.game = game
class RpsTestAdvertisement(Advertisement): """Broadcast a test RPS message.""" flags = None _PREFIX_FMT = "<B" "BHBH" _DATA_FMT = "8s" ### this NUL pads if necessary prefix = struct.pack(_PREFIX_FMT, struct.calcsize(_PREFIX_FMT) - 1, MANUFACTURING_DATA_ADT, ADAFRUIT_COMPANY_ID, struct.calcsize("<H" + _DATA_FMT), RPS_DATA_ID) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=MANUFACTURING_DATA_ADT, company_id=ADAFRUIT_COMPANY_ID, key_encoding="<H", ) ### https://github.com/adafruit/Adafruit_CircuitPython_BLE_BroadcastNet/blob/c6328d5c7edf8a99ff719c3b1798cb4111bab397/adafruit_ble_broadcastnet.py#L66-L67 ### has a sequence_number - this will be use for for Complex Game ### sequence_number = ManufacturerDataField(0x0003, "<B") ### """Sequence number of the measurement. Used to detect missed packets.""" test_string = ManufacturerDataField(RPS_DATA_ID, "<" + _DATA_FMT) """RPS choice."""
class AdafruitSensorMeasurement(Advertisement): """Broadcast a single RGB color.""" # This prefix matches all prefix = struct.pack("<BBH", 3, _MANUFACTURING_DATA_ADT, _ADAFRUIT_COMPANY_ID) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=_MANUFACTURING_DATA_ADT, company_id=_ADAFRUIT_COMPANY_ID, key_encoding="<H") sequence_number = ManufacturerDataField(0x0003, "<B") """Color to broadcast as RGB integer.""" temperature = ManufacturerDataField(0x0a00, "<f") """Color to broadcast as RGB integer.""" def __init__(self, *, sensor_device_id=0xff, sequence_number=0xff): super().__init__() # self.sensor_device_id = sensor_device_id # self.sequence_number = sequence_number def __str__(self): parts = [] for attr in dir(self.__class__): attribute_instance = getattr(self.__class__, attr) if issubclass(attribute_instance.__class__, ManufacturerDataField): value = getattr(self, attr) if value is not None: parts.append("{}={}".format(attr, str(value))) return "<{} {} >".format(self.__class__.__name__, " ".join(parts))
class RpsEncDataAdvertisement(Advertisement): """An RPS (broadcast) message. This sends the encrypted choice of the player. This is not connectable and does not elicit a scan response based on defaults in Advertisement parent class. """ flags = None _PREFIX_FMT = "<BHBH" _DATA_FMT_ENC_DATA = "8s" # match_prefixes tuple replaces deprecated prefix # comma for 1 element is very important! match_prefixes = ( struct.pack( _PREFIX_FMT, MANUFACTURING_DATA_ADT, ADAFRUIT_COMPANY_ID, struct.calcsize("<H" + _DATA_FMT_ENC_DATA), RPS_ENC_DATA_ID ), ) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=MANUFACTURING_DATA_ADT, company_id=ADAFRUIT_COMPANY_ID, key_encoding="<H" ) enc_data = ManufacturerDataField(RPS_ENC_DATA_ID, "<" + _DATA_FMT_ENC_DATA) round_no = ManufacturerDataField(RPS_ROUND_ID, "<" + _DATA_FMT_ROUND) sequence_number = ManufacturerDataField(ADAFRUIT_SEQ_ID, "<" + _SEQ_FMT) """Sequence number of the data. Used in acknowledgements.""" ack = ManufacturerDataField(RPS_ACK_ID, "<" + _DATA_FMT_ACK) """Round number starting at 1.""" def __init__(self, *, enc_data=None, round_no=None, sequence_number=None, ack=None): """enc_data must be either set here in the constructor or set first.""" super().__init__() if enc_data is not None: self.enc_data = enc_data if round_no is not None: self.round_no = round_no if sequence_number is not None: self.sequence_number = sequence_number if ack is not None: self.ack = ack
class IOTGAdvertisement(Advertisement): flags = None match_prefixes = ( struct.pack( "<BHBH", # prefix format 0xFF, # 0xFF is "Manufacturer Specific Data" as per BLE spec 0x0822, # 2 byte company ID struct.calcsize("<H9s"), # data format 0xabcd # our ID ), # comma required - a tuple is expected ) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type= 0xFF, # 0xFF is "Manufacturer Specific Data" as per BLE spec company_id=0x0822, # 2 byte company ID key_encoding="<H", ) # set manufacturer data field md_field = ManufacturerDataField(0xabcd, "<9s")
class Creation(Advertisement): """Advertise what services that the device makes available upon connection.""" # Prefixes that match each ADT that can carry service UUIDs. # This single prefix matches all color advertisements. match_prefixes = (struct.pack( "<BHBH", MANUFACTURING_DATA_ADT, ADAFRUIT_COMPANY_ID, struct.calcsize("<HII"), _DEVICE_FRIEND_DATA_ID, ), ) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=MANUFACTURING_DATA_ADT, company_id=ADAFRUIT_COMPANY_ID, key_encoding="<H", ) creation_id = ManufacturerDataField(_DEVICE_FRIEND_DATA_ID, "<II", field_names=("creator", "creation")) services = ServiceList(standard_services=[0x02, 0x03], vendor_services=[0x06, 0x07]) """List of services the device can provide.""" # pylint: disable=dangerous-default-value def __init__(self, *, creation_id=None, services=[], entry=None): super().__init__() if entry: return if services: self.services.extend(services) self.connectable = True self.flags.general_discovery = True self.flags.le_only = True if creation_id: self.creation_id = creation_id
class AdafruitSensorMeasurement(Advertisement): """Broadcast a single RGB color.""" # This prefix matches all prefix = struct.pack("<BBH", 3, _MANUFACTURING_DATA_ADT, _ADAFRUIT_COMPANY_ID) manufacturer_data = LazyObjectField(ManufacturerData, "manufacturer_data", advertising_data_type=_MANUFACTURING_DATA_ADT, company_id=_ADAFRUIT_COMPANY_ID, key_encoding="<H") sequence_number = ManufacturerDataField(0x0003, "<B") """Sequence number of the measurement. Used to detect missed packets.""" acceleration = ManufacturerDataField(0x0a00, "<fff") """Acceleration as (x, y, z) tuple of floats in meters per second per second.""" magnetic = ManufacturerDataField(0x0a01, "<fff") """Magnetism as (x, y, z) tuple of floats in micro-Tesla.""" orientation = ManufacturerDataField(0x0a02, "<fff") """Absolution orientation as (x, y, z) tuple of floats in degrees.""" gyro = ManufacturerDataField(0x0a03, "<fff") """Gyro motion as (x, y, z) tuple of floats in radians per second.""" temperature = ManufacturerDataField(0x0a04, "<f") """Temperature as a float in degrees centigrade.""" eCO2 = ManufacturerDataField(0x0a05, "<f") """Equivalent CO2 as a float in parts per million.""" TVOC = ManufacturerDataField(0x0a06, "<f") """Total Volatile Organic Compounds as a float in parts per billion.""" distance = ManufacturerDataField(0x0a07, "<f") """Distance as a float in centimeters.""" light = ManufacturerDataField(0x0a08, "<f") """Brightness as a float without units.""" lux = ManufacturerDataField(0x0a09, "<f") """Brightness as a float in SI lux.""" pressure = ManufacturerDataField(0x0a0a, "<f") """Pressure as a float in hectopascals.""" relative_humidity = ManufacturerDataField(0x0a0b, "<f") """Relative humidity as a float percentage.""" current = ManufacturerDataField(0x0a0c, "<f") """Current as a float in milliamps.""" voltage = ManufacturerDataField(0x0a0d, "<f") """Voltage as a float in Volts.""" color = ManufacturerDataField(0x0a0e, "<f") """Color as RGB integer.""" # alarm = ManufacturerDataField(0x0a0f, "<f") """Alarm as a start date and time and recurrence period. Not supported.""" # datetime = ManufacturerDataField(0x0a10, "<f") """Date and time as a struct. Not supported.""" duty_cycle = ManufacturerDataField(0x0a11, "<f") """16-bit PWM duty cycle. Independent of frequency.""" frequency = ManufacturerDataField(0x0a12, "<f") """As integer Hertz""" value = ManufacturerDataField(0x0a13, "<f") """16-bit unit-less value. Used for analog values and for booleans.""" weight = ManufacturerDataField(0x0a14, "<f") """Weight as a float in grams.""" def __init__(self, *, sequence_number=None): super().__init__() if sequence_number: self.sequence_number = sequence_number def __str__(self): parts = [] for attr in dir(self.__class__): attribute_instance = getattr(self.__class__, attr) if issubclass(attribute_instance.__class__, ManufacturerDataField): value = getattr(self, attr) if value is not None: parts.append("{}={}".format(attr, str(value))) return "<{} {} >".format(self.__class__.__name__, " ".join(parts))
class AdafruitSensorMeasurement(Advertisement): """A collection of sensor measurements.""" # This prefix matches all match_prefixes = ( # Matches the sequence number field header (length+ID) struct.pack( "<BHBH", _MANUFACTURING_DATA_ADT, _ADAFRUIT_COMPANY_ID, 0x03, 0x0003 ), ) manufacturer_data = LazyObjectField( ManufacturerData, "manufacturer_data", advertising_data_type=_MANUFACTURING_DATA_ADT, company_id=_ADAFRUIT_COMPANY_ID, key_encoding="<H", ) sequence_number = ManufacturerDataField(0x0003, "<B") """Sequence number of the measurement. Used to detect missed packets.""" acceleration = ManufacturerDataField(0x0A00, "<fff", ("x", "y", "z")) """Acceleration as (x, y, z) tuple of floats in meters per second per second.""" magnetic = ManufacturerDataField(0x0A01, "<fff", ("x", "y", "z")) """Magnetism as (x, y, z) tuple of floats in micro-Tesla.""" orientation = ManufacturerDataField(0x0A02, "<fff", ("x", "y", "z")) """Absolution orientation as (x, y, z) tuple of floats in degrees.""" gyro = ManufacturerDataField(0x0A03, "<fff", ("x", "y", "z")) """Gyro motion as (x, y, z) tuple of floats in radians per second.""" temperature = ManufacturerDataField(0x0A04, "<f") """Temperature as a float in degrees centigrade.""" eCO2 = ManufacturerDataField(0x0A05, "<f") """Equivalent CO2 as a float in parts per million.""" TVOC = ManufacturerDataField(0x0A06, "<f") """Total Volatile Organic Compounds as a float in parts per billion.""" distance = ManufacturerDataField(0x0A07, "<f") """Distance as a float in centimeters.""" light = ManufacturerDataField(0x0A08, "<f") """Brightness as a float without units.""" lux = ManufacturerDataField(0x0A09, "<f") """Brightness as a float in SI lux.""" pressure = ManufacturerDataField(0x0A0A, "<f") """Pressure as a float in hectopascals.""" relative_humidity = ManufacturerDataField(0x0A0B, "<f") """Relative humidity as a float percentage.""" current = ManufacturerDataField(0x0A0C, "<f") """Current as a float in milliamps.""" voltage = ManufacturerDataField(0x0A0D, "<f") """Voltage as a float in Volts.""" color = ManufacturerDataField(0x0A0E, "<f") """Color as RGB integer.""" # alarm = ManufacturerDataField(0x0a0f, "<f") # """Alarm as a start date and time and recurrence period. Not supported.""" # datetime = ManufacturerDataField(0x0a10, "<f") # """Date and time as a struct. Not supported.""" duty_cycle = ManufacturerDataField(0x0A11, "<f") """16-bit PWM duty cycle. Independent of frequency.""" frequency = ManufacturerDataField(0x0A12, "<f") """As integer Hertz""" value = ManufacturerDataField(0x0A13, "<f") """16-bit unit-less value. Used for analog values and for booleans.""" weight = ManufacturerDataField(0x0A14, "<f") """Weight as a float in grams.""" battery_voltage = ManufacturerDataField(0x0A15, "<H") """Battery voltage in millivolts. Saves two bytes over voltage and is more readable in bare packets.""" def __init__(self, *, sequence_number=None): super().__init__() if sequence_number: self.sequence_number = sequence_number def __str__(self): parts = [] for attr in dir(self.__class__): attribute_instance = getattr(self.__class__, attr) if issubclass(attribute_instance.__class__, ManufacturerDataField): value = getattr(self, attr) if value is not None: parts.append("{}={}".format(attr, str(value))) return "<{} {} >".format(self.__class__.__name__, " ".join(parts)) def __bytes__(self): """The raw packet bytes.""" # Must reorder the ManufacturerData contents so the sequence number field is always first. # Necessary to ensure that match_prefixes works right to reconstruct on the receiver. tmp_dict = OrderedDict() tmp_dict[3] = self.data_dict[255].data[3] for key, val in self.data_dict[255].data.items(): if key != 3: tmp_dict[key] = val self.data_dict[255].data = tmp_dict # self.data_dict[255].data.move_to_end(3, last=False) return super().__bytes__() def split(self, max_packet_size=31): """Split the measurement into multiple measurements with the given max_packet_size. Yields each submeasurement.""" current_size = 8 # baseline for mfg data and sequence number if current_size + len(self.manufacturer_data) < max_packet_size: yield self return original_data = self.manufacturer_data.data submeasurement = None for key in original_data: value = original_data[key] entry_size = 2 + len(value) if not submeasurement or current_size + entry_size > max_packet_size: if submeasurement: yield submeasurement submeasurement = self.__class__() current_size = 8 submeasurement.manufacturer_data.data[key] = value current_size += entry_size if submeasurement: yield submeasurement return