Skip to content

Commit a5d6678

Browse files
committed
Implement basic functionality
1 parent cafcc67 commit a5d6678

File tree

3 files changed

+157
-1
lines changed

3 files changed

+157
-1
lines changed

adafruit_ble_beacon.py

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,130 @@
3232
# * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
3333
"""
3434

35+
import struct
36+
from micropython import const
3537
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
3745

3846
__version__ = "0.0.0+auto.0"
3947
__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]

examples/ble_beacon_sendtest.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import time
2+
from adafruit_ble import BLERadio
3+
from adafruit_ble_beacon import iBeaconAdvertisement
4+
5+
ble = BLERadio()
6+
7+
advertisement = iBeaconAdvertisement()
8+
advertisement.uuid = b"circuitPython123"
9+
advertisement.major = 1
10+
advertisement.minor = 32
11+
advertisement.beacon_tx_power = -80
12+
13+
while True:
14+
ble.start_advertising(advertisement)
15+
time.sleep(10)
16+
ble.stop_advertising()
17+
time.sleep(3)

examples/ble_beacon_simpletest.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,20 @@
22
# SPDX-FileCopyrightText: Copyright (c) 2022 Alec Delaney for Adafruit Industries
33
#
44
# SPDX-License-Identifier: Unlicense
5+
6+
import time
7+
from adafruit_ble import BLERadio
8+
from adafruit_ble_beacon import iBeaconAdvertisement
9+
10+
ble = BLERadio()
11+
12+
while True:
13+
for entry in ble.start_scan(iBeaconAdvertisement, minimum_rssi=-120, timeout=3):
14+
entry: iBeaconAdvertisement
15+
print("Beacon Power", entry.beacon_tx_power)
16+
print("UUID:", entry.uuid)
17+
print("Major", entry.major)
18+
print("Minor:", entry.minor)
19+
print("Distance:", entry.distance)
20+
time.sleep(1)
21+
time.sleep(3)

0 commit comments

Comments
 (0)