Skip to content

Commit 5573647

Browse files
committed
Update ibeacon_scanner.py
1 parent 4de2337 commit 5573647

1 file changed

Lines changed: 60 additions & 1 deletion

File tree

modules/scanners/ibeacon_scanner.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,66 @@ def classify_addr(addr: str, addr_type: int) -> Tuple[str, Optional[str]]:
245245

246246

247247
# =====================================================================
248-
# Phase 1 — Discovery
248+
# Phase 1 — Discovery (BleakScanner fallback — no root required)
249+
# =====================================================================
250+
def discover_ibeacons_bleak(duration: float,
251+
min_rssi: Optional[int]) -> List[Dict[str, Any]]:
252+
"""
253+
BleakScanner-based iBeacon discovery. No raw socket / root required.
254+
Parses Apple manufacturer data (company ID 0x004C, type 0x02, len 0x15).
255+
addr_type is returned as 0 (public) or 1 (random) from BlueZ props.
256+
"""
257+
seen: Dict[str, Dict[str, Any]] = {}
258+
259+
async def _scan() -> None:
260+
scanner = BleakScanner()
261+
await scanner.start()
262+
await asyncio.sleep(duration)
263+
await scanner.stop()
264+
for dev, adv in scanner.discovered_devices_and_advertisement_data.values():
265+
rssi = getattr(adv, "rssi", -999) or -999
266+
if min_rssi is not None and rssi < min_rssi:
267+
continue
268+
payload = adv.manufacturer_data.get(APPLE_COMPANY_ID, b"")
269+
if len(payload) < 23:
270+
continue
271+
if payload[0] != IBEACON_TYPE or payload[1] != IBEACON_LENGTH:
272+
continue
273+
uuid = _uuid_str(payload[2:18])
274+
major = struct.unpack(">H", payload[18:20])[0]
275+
minor = struct.unpack(">H", payload[20:22])[0]
276+
tx_power = struct.unpack("<b", payload[22:23])[0]
277+
278+
try:
279+
props = dev.details if isinstance(dev.details, dict) else {}
280+
props = props.get("props", props)
281+
addr_type_str = props.get("AddressType", "random")
282+
addr_type = 0 if addr_type_str == "public" else 1
283+
except Exception:
284+
addr_type = 1
285+
286+
key = f"{dev.address}|{uuid}|{major}|{minor}"
287+
rec = seen.get(key)
288+
if rec is None:
289+
rec = {
290+
"addr": dev.address.upper(), "addr_type": addr_type,
291+
"evt_types": set(), "rssi_min": rssi, "rssi_max": rssi,
292+
"count": 0, "uuid": uuid, "major": major,
293+
"minor": minor, "tx_power": tx_power,
294+
}
295+
seen[key] = rec
296+
rec["rssi_min"] = min(rec["rssi_min"], rssi)
297+
rec["rssi_max"] = max(rec["rssi_max"], rssi)
298+
rec["count"] += 1
299+
300+
asyncio.run(_scan())
301+
out = list(seen.values())
302+
out.sort(key=lambda r: -r["rssi_max"])
303+
return out
304+
305+
306+
# =====================================================================
307+
# Phase 1 — Discovery (raw HCI — needs CAP_NET_RAW or root)
249308
# =====================================================================
250309
def discover_ibeacons(dev_id: int, duration: float,
251310
min_rssi: Optional[int]) -> List[Dict[str, Any]]:

0 commit comments

Comments
 (0)