Mercurial > code > home > repos > homeauto
diff service/beacon/rssiscan.py @ 291:299ddd7e2070
start bt beacon tools
Ignore-this: a19bb907ede601562ef44c27ae706dca
author | drewp@bigasterisk.com |
---|---|
date | Wed, 20 Jul 2016 23:52:03 -0700 |
parents | |
children | 70755e76bc03 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/beacon/rssiscan.py Wed Jul 20 23:52:03 2016 -0700 @@ -0,0 +1,194 @@ +# BLE iBeaconScanner based on https://github.com/adamf/BLE/blob/master/ble-scanner.py +# JCS 06/07/14 +# Adapted for Python3 by Michael duPont 2015-04-05 + +#DEBUG = False +# BLE scanner based on https://github.com/adamf/BLE/blob/master/ble-scanner.py +# BLE scanner, based on https://code.google.com/p/pybluez/source/browse/trunk/examples/advanced/inquiry-with-rssi.py + +# https://github.com/pauloborges/bluez/blob/master/tools/hcitool.c for lescan +# https://kernel.googlesource.com/pub/scm/bluetooth/bluez/+/5.6/lib/hci.h for opcodes +# https://github.com/pauloborges/bluez/blob/master/lib/hci.c#L2782 for functions used by lescan + +import struct +import sys +import bluetooth._bluetooth as bluez +from pymongo import MongoClient +import datetime +from dateutil.tz import tzlocal +from bson.binary import Binary + +LE_META_EVENT = 0x3e +OGF_LE_CTL=0x08 +OCF_LE_SET_SCAN_ENABLE=0x000C + +# these are actually subevents of LE_META_EVENT +EVT_LE_CONN_COMPLETE=0x01 +EVT_LE_ADVERTISING_REPORT=0x02 + +def hci_enable_le_scan(sock): + enable = 0x01 + cmd_pkt = struct.pack("<BB", enable, 0x00) + bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE, cmd_pkt) + + +# ported from android/source/external/bluetooth/hcidump/parser/hci.c + +def evttype2str(etype): + return { + 0x00: "ADV_IND", # Connectable undirected advertising + 0x01: "ADV_DIRECT_IND", # Connectable directed advertising + 0x02: "ADV_SCAN_IND", # Scannable undirected advertising + 0x03: "ADV_NONCONN_IND", # Non connectable undirected advertising + 0x04: "SCAN_RSP", # Scan Response + }.get(etype, "Reserved") + +def bdaddrtype2str(btype): + return { + 0x00: "Public", + 0x01: "Random", + }.get(btype, "Reserved") + + +# from https://github.com/google/eddystone/blob/master/eddystone-url/implementations/linux/scan-for-urls + +schemes = [ + "http://www.", + "https://www.", + "http://", + "https://", + ] + +extensions = [ + ".com/", ".org/", ".edu/", ".net/", ".info/", ".biz/", ".gov/", + ".com", ".org", ".edu", ".net", ".info", ".biz", ".gov", + ] + +def decodeUrl(encodedUrl): + """ + Decode a url encoded with the Eddystone (or UriBeacon) URL encoding scheme + """ + + decodedUrl = schemes[encodedUrl[0]] + for c in encodedUrl[1:]: + if c <= 0x20: + decodedUrl += extensions[c] + else: + decodedUrl += chr(c) + + return decodedUrl + +def decodeBeacon(data, row): + # this padding makes the offsets line up to the scan-for-urls code + padData = map(ord, '*' * 14 + data) + + # Eddystone + if len(padData) >= 20 and padData[19] == 0xaa and padData[20] == 0xfe: + serviceDataLength = padData[21] + frameType = padData[25] + + # Eddystone-URL + if frameType == 0x10: + row["Eddystone-URL"] = decodeUrl(padData[27:22 + serviceDataLength]) + elif frameType == 0x00: + row["Eddystone-UID"] = Binary(data) + elif frameType == 0x20: + row["Eddystone-TLM"] = Binary(data) + else: + row["Eddystone"] = "Unknown Eddystone frame type: %r data: %r" % (frameType, data) + + # UriBeacon + elif len(padData) >= 20 and padData[19] == 0xd8 and padData[20] == 0xfe: + serviceDataLength = padData[21] + row["UriBeacon"] = decodeUrl(padData[27:22 + serviceDataLength]) + + else: + pass # "Unknown beacon type" + +def decodeInquiryData(data, row): + # offset 19 is totally observed from data, not any spec. IDK if the preceding part is variable-length. + if len(data) > 20 and data[19] in ['\x08', '\x09']: + localName = data[20:] + if data[19] == '\x08': + row['local_name_shortened'] = Binary(localName) + else: + row['local_name_complete'] = Binary(localName) + # more at android/source/external/bluetooth/hcidump/parser/hci.c ext_inquiry_data_dump + +# from android/source/external/bluetooth/hcidump/parser/hci.c +def evt_le_advertising_report_dump(frm, now): + num_reports = ord(frm[0]) + frm = frm[1:] + + for i in range(num_reports): + fmt = 'B B 6B B' + row = {'t': now} + + evt_type, bdaddr_type, b5, b4, b3, b2, b1, b0, length = struct.unpack(fmt, frm[:struct.calcsize(fmt)]) + frm = frm[struct.calcsize(fmt):] + + row['addr'] = '%02x:%02x:%02x:%02x:%02x:%02x' % (b0, b1, b2, b3, b4, b5) + row['addr_type'] = bdaddrtype2str(bdaddr_type) + row['evt_type'] = evttype2str(evt_type) + + data = frm[:length] + frm = frm[length:] + row['data'] = Binary(data) + #row['data_hex'] = ' '.join('%02x' % ord(c) for c in data) + + decodeBeacon(data, row) + decodeInquiryData(data, row) + + row['rssi'], = struct.unpack('b', frm[-1]) + frm = frm[1:] + yield row + + + +def parse_events(sock, loop_count, source, coll): + old_filter = sock.getsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, 14) + flt = bluez.hci_filter_new() + bluez.hci_filter_all_events(flt) + bluez.hci_filter_set_ptype(flt, bluez.HCI_EVENT_PKT) + sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, flt ) + + for i in range(0, loop_count): + pkt = sock.recv(255) + ptype, event, plen = struct.unpack("BBB", pkt[:3]) + now = datetime.datetime.now(tzlocal()) + if event == bluez.EVT_INQUIRY_RESULT_WITH_RSSI: + print "EVT_INQUIRY_RESULT_WITH_RSSI" + elif event == bluez.EVT_NUM_COMP_PKTS: + print "EVT_NUM_COMP_PKTS" + elif event == bluez.EVT_DISCONN_COMPLETE: + print "EVT_DISCONN_COMPLETE" + elif event == LE_META_EVENT: + subevent, = struct.unpack("B", pkt[3:4]) + pkt = pkt[4:] + if subevent == EVT_LE_CONN_COMPLETE: + pass + elif subevent == EVT_LE_ADVERTISING_REPORT: + rows = list(evt_le_advertising_report_dump(pkt, now)) + for row in sorted(rows): + #print row['addr'], row['t'], row['rssi'], row + row['from'] = source + coll.insert(row) + + + sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, old_filter ) + + +if __name__ == '__main__': + mongoHost, myLocation = sys.argv[1:] + + client = MongoClient(mongoHost) + coll = client['beacon']['scan'] + + dev_id = 0 + sock = bluez.hci_open_dev(dev_id) + sock.getsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, 14) + + hci_enable_le_scan(sock) + + while True: + parse_events(sock, 10, source=myLocation, coll=coll)