|
32 | 32 | # * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
|
33 | 33 | """
|
34 | 34 |
|
| 35 | +import struct |
| 36 | +from micropython import const |
35 | 37 | import _bleio
|
36 |
| -from adafruit_ble. |
| 38 | +from adafruit_ble.advertising import Advertisement, Struct, AdvertisingDataField |
| 39 | +from adafruit_ble.advertising.standard import ManufacturerData |
| 40 | + |
| 41 | +try: |
| 42 | + from typing import Optional, Union, Any, Type, Tuple, Sequence |
| 43 | +except ImportError: |
| 44 | + pass |
37 | 45 |
|
38 | 46 | __version__ = "0.0.0+auto.0"
|
39 | 47 | __repo__ = "https://github.com/tekktrik/Adafruit_CircuitPython_BLE_Beacon.git"
|
| 48 | + |
| 49 | +_MANUFACTURING_DATA_ADT = const(0xFF) |
| 50 | + |
| 51 | +_APPLE_COMPANY_ID = const(0x004C) |
| 52 | +_IBEACON_TYPE = const(0x02) |
| 53 | +_IBEACON_LENGTH = const(0x15) |
| 54 | + |
| 55 | +# TODO: fix this |
| 56 | +_APPLE_COMPANY_ID_FLIPPED = const(0x4C00) |
| 57 | + |
| 58 | + |
| 59 | +class MultiStruct(AdvertisingDataField): |
| 60 | + """`struct` encoded data in an Advertisement.""" |
| 61 | + |
| 62 | + def __init__(self, struct_format: str, *, advertising_data_type: int) -> None: |
| 63 | + self._format = struct_format |
| 64 | + self._adt = advertising_data_type |
| 65 | + |
| 66 | + def __get__( |
| 67 | + self, obj: Optional["Advertisement"], cls: Type["Advertisement"] |
| 68 | + ) -> Optional[Union[Tuple, "Struct"]]: |
| 69 | + if obj is None: |
| 70 | + return self |
| 71 | + if self._adt not in obj.data_dict: |
| 72 | + return None |
| 73 | + return struct.unpack(self._format, obj.data_dict[self._adt]) |
| 74 | + |
| 75 | + def __set__(self, obj: "Advertisement", value: Sequence) -> None: |
| 76 | + obj.data_dict[self._adt] = struct.pack(self._format, *value) |
| 77 | + |
| 78 | +class _BeaconAdvertisement(Advertisement): |
| 79 | + """Advertisement for location beacons like iBeacon""" |
| 80 | + |
| 81 | + path_loss_const: int = 3 |
| 82 | + """The path loss constant, typically between 2-4""" |
| 83 | + |
| 84 | + @property |
| 85 | + def distance(self) -> float: |
| 86 | + """The approximate distance to the beacon""" |
| 87 | + |
| 88 | + return 10**((self.beacon_tx_power - self.rssi) / (10*self.path_loss_const)) |
| 89 | + |
| 90 | + @property |
| 91 | + def beacon_tx_power(self) -> int: |
| 92 | + raise NotImplementedError("Must be defined in beacon subclass") |
| 93 | + |
| 94 | + |
| 95 | + |
| 96 | +class iBeaconAdvertisement(_BeaconAdvertisement): |
| 97 | + |
| 98 | + match_prefixes = (struct.pack("<BHBB", _MANUFACTURING_DATA_ADT, _APPLE_COMPANY_ID, _IBEACON_TYPE, _IBEACON_LENGTH),) |
| 99 | + |
| 100 | + _data_format = ">HBBQQHHb" |
| 101 | + _beacon_data = MultiStruct(_data_format, advertising_data_type=0xFF) |
| 102 | + |
| 103 | + def __init__(self, *, entry: Optional[_bleio.ScanEntry] = None, ) -> None: |
| 104 | + super().__init__(entry=entry) |
| 105 | + |
| 106 | + if not entry: |
| 107 | + self._init_struct() |
| 108 | + |
| 109 | + @property |
| 110 | + def uuid(self) -> bytes: |
| 111 | + _, _, _, uuid_msb, uuid_lsb, _, _, _ = self._beacon_data |
| 112 | + return struct.pack(">QQ", uuid_msb, uuid_lsb) |
| 113 | + |
| 114 | + @uuid.setter |
| 115 | + def uuid(self, id: bytes) -> None: |
| 116 | + uuid_msb, uuid_lsb = struct.unpack(">QQ", id) |
| 117 | + self._set_struct_index(3, uuid_msb) |
| 118 | + self._set_struct_index(4, uuid_lsb) |
| 119 | + |
| 120 | + |
| 121 | + @property |
| 122 | + def major(self) -> int: |
| 123 | + _, _, _, _, _, major, _, _ = self._beacon_data |
| 124 | + return major |
| 125 | + |
| 126 | + @major.setter |
| 127 | + def major(self, number: int) -> None: |
| 128 | + #flipped = self.flip_endian(number) |
| 129 | + self._set_struct_index(5, number) |
| 130 | + |
| 131 | + @property |
| 132 | + def minor(self) -> int: |
| 133 | + _, _, _, _, _, _, minor, _ = self._beacon_data |
| 134 | + return minor |
| 135 | + |
| 136 | + @minor.setter |
| 137 | + def minor(self, number: int) -> None: |
| 138 | + self._set_struct_index(6, number) |
| 139 | + |
| 140 | + @property |
| 141 | + def beacon_tx_power(self) -> int: |
| 142 | + _, _, _, _, _, _, _, tx_power = self._beacon_data |
| 143 | + return tx_power |
| 144 | + |
| 145 | + @beacon_tx_power.setter |
| 146 | + def beacon_tx_power(self, power: int) -> None: |
| 147 | + self._set_struct_index(7, power) |
| 148 | + |
| 149 | + def _set_struct_index(self, index: int, value: int) -> int: |
| 150 | + current_beacon_data = list(self._beacon_data) |
| 151 | + flipped = self.flip_endian(value, index) |
| 152 | + current_beacon_data[index] = flipped |
| 153 | + self._beacon_data = current_beacon_data |
| 154 | + |
| 155 | + def _init_struct(self) -> None: |
| 156 | + self._beacon_data = (_APPLE_COMPANY_ID_FLIPPED, _IBEACON_TYPE, _IBEACON_LENGTH, 0, 0, 0, 0, 0) |
| 157 | + |
| 158 | + def flip_endian(self, number: int, index: int): |
| 159 | + index_format = self._data_format[index+1] |
| 160 | + temp_bytes = struct.pack("<" + index_format, number) |
| 161 | + return struct.unpack("<" + index_format, temp_bytes)[0] |
0 commit comments