Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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."""
Ejemplo n.º 4
0
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))
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 10
0
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))
Ejemplo n.º 11
0
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