diff --git a/tools/FAKEMOBILECLIENT.TXT b/tools/FAKEMOBILECLIENT.TXT new file mode 100644 index 00000000..c1846844 --- /dev/null +++ b/tools/FAKEMOBILECLIENT.TXT @@ -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 diff --git a/tools/fakemobileclient.py b/tools/fakemobileclient.py index d46a674a..198e2b66 100644 --- a/tools/fakemobileclient.py +++ b/tools/fakemobileclient.py @@ -4,9 +4,31 @@ import socketserver import subprocess import sys 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 +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): try: @@ -14,6 +36,48 @@ def get_from_lines(lines, keyword): except StopIteration: 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): def do_GET(self): """Serve a GET request.""" @@ -51,11 +115,13 @@ class FakeMobileClientHandler(http.server.BaseHTTPRequestHandler): break + beacons = async_to_sync(ble_scan()) + self.send_response(200) self.send_header('Content-Type', 'application/json') self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() - self.wfile.write(json.dumps({'wifi':stations}).encode()) + self.wfile.write(json.dumps({'wifi':stations, 'ibeacon':beacons}).encode()) return True