adafruit / Adafruit_CircuitPython_BLE

Bluetooth Low Energy (BLE) library for CircuitPython
MIT License
127 stars 58 forks source link

A custom Advertisement with a constructor can fail to parse incoming BLE data correctly #86

Open kevinjwalters opened 4 years ago

kevinjwalters commented 4 years ago

This innocent looking constructor in a sub-class of Advertisement:

    def __init__(self, enc_data=None, round=0):
        super().__init__()
        if enc_data is not None:
            self.enc_data = enc_data
        if round is not None:
            self.round = round

was causing ads to be receieved on another Adafruit device use this library as

<RpsEncDataAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=03 31 ff 00 > >

Minor possibility I've misdiagnosed this and they are sent like this due to a bug but this seems unlikely.

Originally mentioned in the comments of https://github.com/adafruit/Adafruit_CircuitPython_BLE/issues/79#issuecomment-634990957

kevinjwalters commented 4 years ago

I'm in the middle of writing an application using BLE. Give me a week or so and I'll provide a concise example which shows this.

kevinjwalters commented 4 years ago

I don't think extended is factor here but my example happens to feature it.

import struct

from adafruit_ble.advertising import Advertisement, LazyObjectField
from adafruit_ble.advertising.standard import ManufacturerData, ManufacturerDataField

MANUFACTURING_DATA_ADT = const(0xFF)
ADAFRUIT_COMPANY_ID = const(0x0822)

### DIRTY HACK - numbers chosen to get particular order based on current dict ordering
BIG_AD_ID = const(0xf1ac)
BIG_AD_ID_CANDIDATE = const(0xf1ad)

_DATA_FMT_SOME_NUMBER = "H"

class BigAdvertisement(Advertisement):
    """An Advertisement with a size that can only be transmitted as an extended Advertisement."""
    flags = None

    _PREFIX_FMT = "<B" "BHBH"
    _DATA_FMT_CANDIDATE = "200s"

    prefix = struct.pack(
        _PREFIX_FMT,
        struct.calcsize(_PREFIX_FMT) - 1,
        MANUFACTURING_DATA_ADT,
        ADAFRUIT_COMPANY_ID,
        struct.calcsize("<H" + _DATA_FMT_SOME_NUMBER),
        BIG_AD_ID
    )
    manufacturer_data = LazyObjectField(
        ManufacturerData,
        "manufacturer_data",
        advertising_data_type=MANUFACTURING_DATA_ADT,
        company_id=ADAFRUIT_COMPANY_ID,
        key_encoding="<H"
    )

    some_number = ManufacturerDataField(BIG_AD_ID, "<" + _DATA_FMT_SOME_NUMBER)
    candidate = ManufacturerDataField(BIG_AD_ID_CANDIDATE, "<" + _DATA_FMT_CANDIDATE)

    def __init__(self, *, some_number=0, candidate=None):
        super().__init__()
        if some_number is not None:
            self.some_number = some_number
        if candidate is not None:
            self.candidate = candidate

CLUE 1

Press any key to enter the REPL. Use CTRL-D to reload.
Adafruit CircuitPython 5.3.0 on 2020-04-29; Adafruit CLUE nRF52840 Express with nRF52840
>>>
>>> from bigads import BigAdvertisement
>>> kw = BigAdvertisement()
>>> bytes(kw)
b'\x08\xff"\x08\x04\xac\xf1\x00\x00'
>>> kw.candidate = "Kanye"
>>> bytes(kw)
b'\xd3\xff"\x08\x04\xac\xf1\x00\x00\xca\xad\xf1Kanye\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> from adafruit_ble import BLERadio
>>> ble = BLERadio()
>>> ble.start_advertising(kw)

CLUE 2

Adafruit CircuitPython 5.3.0 on 2020-04-29; Adafruit CLUE nRF52840 Express with nRF52840
>>>
>>> from bigads import BigAdvertisement
>>> from adafruit_ble import BLERadio
>>> ble = BLERadio()
>>> for adv in ble.start_scan(BigAdvertisement, extended=True, timeout=2.0):
...     print(adv, adv.some_number, adv.candidate)
...
...
...
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
<BigAdvertisement manufacturer_data=<ManufacturerData company_id=0822 data=04 ac f1 00 00 > > 0 None
kevinjwalters commented 2 years ago

There's an unrelated issue with constructor needing to accept and forward entry kwarg.