#!/usr/bin/python3
# performs a simple device inquiry, followed by a remote name request of each
# discovered device

import os
import sys
import struct
import bluetooth._bluetooth as bluez
import bluetooth
import datetime

class InqueryHelper:
    def __init__(self,dev_id=0):
        self.dev_id = dev_id
        self.sock = ''
        self._check_bt_device()
        self._check_inquery

    def _check_bt_device(self):
        try:
            self.sock = bluez.hci_open_dev(self.dev_id)
        except:
            print("error accessing bluetooth device...")
            sys.exit(1)

    def _check_inquery(self):
        try:
            mode = self._read_inquiry_mode(self.sock)
        except Exception as e:
            print("error reading inquiry mode.  ")
            print("Are you sure this a bluetooth 1.2 device?")
            print(e)
            sys.exit(1)
        print("current inquiry mode is %d" % mode)
        if mode != 1:
            print("writing inquiry mode...")
            try:
                result = self._write_inquiry_mode(self.sock, 1)
            except Exception as e:
                print("error writing inquiry mode.  Are you sure you're root?")
                print(e)
                sys.exit(1)
            if result != 0:
                print("error while setting inquiry mode")
            print("result: %d" % result)

    def _print_packet(self,pkt):
        for c in pkt:
            sys.stdout.write("%02x " % struct.unpack("B",c)[0])
        print()


    def _read_inquiry_mode(self,sock):
        """returns the current mode, or -1 on failure"""
        # save current filter
        old_filter = sock.getsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, 14)

        # Setup socket filter to receive only events related to the
        # read_inquiry_mode command
        flt = bluez.hci_filter_new()
        opcode = bluez.cmd_opcode_pack(bluez.OGF_HOST_CTL,
                bluez.OCF_read_inquiry_mode)
        bluez.hci_filter_set_ptype(flt, bluez.HCI_EVENT_PKT)
        bluez.hci_filter_set_event(flt, bluez.EVT_CMD_COMPLETE)
        bluez.hci_filter_set_opcode(flt, opcode)
        sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, flt )

        # first read the current inquiry mode.
        bluez.hci_send_cmd(sock, bluez.OGF_HOST_CTL,
                bluez.OCF_read_inquiry_mode )

        pkt = sock.recv(255)

        status,mode = struct.unpack("xxxxxxBB", pkt)
        if status != 0: mode = -1

        # restore old filter
        sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, old_filter )
        return mode

    def _write_inquiry_mode(self,sock, mode):
        """returns 0 on success, -1 on failure"""
        # save current filter
        old_filter = sock.getsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, 14)

        # Setup socket filter to receive only events related to the
        # write_inquiry_mode command
        flt = bluez.hci_filter_new()
        opcode = bluez.cmd_opcode_pack(bluez.OGF_HOST_CTL,
                bluez.OCF_write_inquiry_mode)
        bluez.hci_filter_set_ptype(flt, bluez.HCI_EVENT_PKT)
        bluez.hci_filter_set_event(flt, bluez.EVT_CMD_COMPLETE)
        bluez.hci_filter_set_opcode(flt, opcode)
        sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, flt)

        # send the command!
        bluez.hci_send_cmd(sock, bluez.OGF_HOST_CTL,
                bluez.OCF_write_inquiry_mode, struct.pack("B", mode))

        pkt = sock.recv(255)

        status = struct.unpack("xxxxxxB", pkt)[0]

        # restore old filter
        sock.setsockopt(bluez.SOL_HCI, bluez.HCI_FILTER, old_filter)
        if status != 0: return -1
        return 0

    def _device_inquiry_with_with_rssi(self,sock):
        """inquiry blutooth devices with rssi"""
        # save current filter
        old_filter = sock.getsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, 14)

        # perform a device inquiry on bluetooth device #0
        # The inquiry should last 8 * 1.28 = 10.24 seconds
        # before the inquiry is performed, bluez should flush its cache of
        # previously discovered devices
        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)

        duration = 4
        max_responses = 255
        cmd_pkt = struct.pack("BBBBB", 0x33, 0x8b, 0x9e, duration, max_responses)
        bluez.hci_send_cmd(sock, bluez.OGF_LINK_CTL, bluez.OCF_INQUIRY, cmd_pkt)

        results = []

        done = False
        while not done:
            pkt = sock.recv(255)
            ptype, event, plen = struct.unpack("BBB", pkt[:3])
            if event == bluez.EVT_INQUIRY_RESULT_WITH_RSSI:
                pkt = pkt[3:]
                nrsp = bluetooth.get_byte(pkt[0])
                for i in range(nrsp):
                    addr = bluez.ba2str( pkt[1+6*i:1+6*i+6] )
                    rssi = bluetooth.byte_to_signed_int(
                            bluetooth.get_byte(pkt[1+13*nrsp+i]))
                    results.append((addr, rssi, str(datetime.datetime.utcnow())))

            elif event == bluez.EVT_INQUIRY_COMPLETE:
                done = True
            elif event == bluez.EVT_CMD_STATUS:
                status, ncmd, opcode = struct.unpack("BBH", pkt[3:7])
                if status != 0:
                    print("uh oh...")
                    self._print_packet(pkt[3:7])
                    done = True
            elif event == bluez.EVT_INQUIRY_RESULT:
                pkt = pkt[3:]
                nrsp = bluetooth.get_byte(pkt[0])
                for i in range(nrsp):
                    addr = bluez.ba2str(pkt[1+6*i:1+6*i+6])
                    results.append((addr, -1, str(datetime.datetime.utcnow())))
            else:
                print("unrecognized packet type 0x%02x" % ptype)


        # restore old filter
        sock.setsockopt( bluez.SOL_HCI, bluez.HCI_FILTER, old_filter )

        return results

    def scan(self):
        """scan and return inquiry bt devices"""
        inquryResult = self._device_inquiry_with_with_rssi(self.sock)
        results = []
        for device in inquryResult:
            # scan for bluetooth device names
            name = bluetooth.lookup_name(device[0], 10)
            results.append({"mac": device[0], "rssi": device[1], "name": name, "ts": device[2]})
        return results


# testing the helper class and write results to btinquery.txt
if __name__ == '__main__':
    inqurer = InqueryHelper()
    f = open("btinquery.txt","w")
    f.write('Bluetooth MAC;rssi;Name;time stamp UTC\r\n')
    f.close()

    while True:
        res = inqurer.scan()
        f = open("btinquery.txt", "a+")
        for dev in res:
            f.write(str(dev['mac']) +";"+ str(dev["rssi"])+';'+str(dev["name"])+';'+dev["ts"]+"\r\n")
        f.close()
