Merge pull request #209 from jennypaxian/main

Initial support for iBeacon BLE scanning for FakeMobileClient
This commit is contained in:
Jenny Paxian 2024-12-29 17:44:21 +01:00 committed by GitHub
commit bbb0e9279b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 1 deletions

View file

@ -0,0 +1,4 @@
For Wifi Scanning you need wireless_tools
For Bluetooth Scanning you need BlueZ bluetooth + Dev Lib libbluetooth and pip install bleak
Edit /lib/systemd/system/bluetooth.service and add --experimental to ExecStart Line for bluetoothd

View file

@ -4,9 +4,31 @@ import socketserver
import subprocess import subprocess
import sys import sys
import time import time
import struct
import math
import asyncio
from uuid import UUID
from construct import Array, Byte, Const, Int8sl, Int16ub, Struct
from construct.core import ConstError
from collections import OrderedDict
from bleak import BleakScanner
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
PORT = int(sys.argv[1]) if sys.argv[1:] else 8042 PORT = int(sys.argv[1]) if sys.argv[1:] else 8042
ibeacon_format = Struct(
"type_length" / Const(b"\x02\x15"),
"uuid" / Array(16, Byte),
"major" / Int16ub,
"minor" / Int16ub,
"power" / Int8sl,
)
def get_from_lines(lines, keyword): def get_from_lines(lines, keyword):
try: try:
@ -14,6 +36,48 @@ def get_from_lines(lines, keyword):
except StopIteration: except StopIteration:
return return
def calc_distance(txPower, rssi):
if (rssi == 0):
return -1.0
ratio = rssi*1.0/txPower;
if (ratio < 1.0):
distance = math.pow(ratio,10);
else:
distance = (0.89976) * math.pow(ratio,7.7095) + 0.111;
return distance;
async def ble_scan():
beacons = []
devices = await BleakScanner.discover(return_adv=True)
devices = OrderedDict(
sorted(devices.items(), key=lambda x: x[1][1].rssi, reverse=True)
)
for i, (addr, (device, advertisement_data)) in enumerate(devices.items()):
try:
apple_data = advertisement_data.manufacturer_data[0x004C]
ibeacon = ibeacon_format.parse(apple_data)
uuid = UUID(bytes=bytes(ibeacon.uuid))
beacons.append({'uuid': str(uuid), 'major': ibeacon.major, 'minor': ibeacon.minor, 'distance': calc_distance(ibeacon.power, advertisement_data.rssi), 'last_seen_ago': 0 })
except KeyError:
# Apple company ID (0x004c) not found
pass
except ConstError:
# No iBeacon (type 0x02 and length 0x15)
pass
return beacons
def async_to_sync(awaitable):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(awaitable)
class FakeMobileClientHandler(http.server.BaseHTTPRequestHandler): class FakeMobileClientHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
"""Serve a GET request.""" """Serve a GET request."""
@ -51,11 +115,13 @@ class FakeMobileClientHandler(http.server.BaseHTTPRequestHandler):
break break
beacons = async_to_sync(ble_scan())
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'application/json') self.send_header('Content-Type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers() self.end_headers()
self.wfile.write(json.dumps({'wifi':stations}).encode()) self.wfile.write(json.dumps({'wifi':stations, 'ibeacon':beacons}).encode())
return True return True