#! /usr/bin/python # Bluetooth Device Scanner # # API to allow notifications when nearby Bluetooth devices appear and disappear. # # @author Gareth Jones (http://blog.garethj.com) # # # Todo # ---- # # - # # Change history # -------------- # # 0.3 # - Provided list of all currently known devices on update notification # - Ensure we twitter on the first update even if the list is empty # - Fixed the device list comparison failure for inconsistent results # # 0.2 # - Scan at least twice for devices and ensure the lists are consistent in # case we miss a device while scanning # # 0.1 # - Alpha release import time, lightblue from sets import Set from lightblue import BluetoothError # Constants VERSION = "0.3" DEBUG = True DEFAULT_SECONDS_BETWEEN_BLUETOOTH_DEVICE_DETECTION = 30 # Utility method for outputting debug information. # @param message The debug information to output # @param level A string representing the log level def debug(message, level="INFO"): if DEBUG: print time.ctime() + " " + level + " <-- " + message + " -->" # Utility method for debugging raised exceptions. # @param exception The exception to raise def raiseException(exception): debug(str(exception), "ERROR") raise exception # Output version number when loaded debug("Loading Bluetooth Device Scanner server version " + VERSION) # Main server application. Polls for nearby Bluetooth devices and # notifies any registered listeners. class BluetoothDeviceScanner: # Creates a new instance of this server def __init__(self): self.__deviceList = None self.__listeners = Set() # Starts the service def start(self): self.__running = True debug("Starting Bluetooth Device Scanner") try: try: while self.__running: try: self.updateKnownDeviceList() except Exception, exception: debug("An error occurred while updating the known device list: " + str(exception), "ERROR") time.sleep(DEFAULT_SECONDS_BETWEEN_BLUETOOTH_DEVICE_DETECTION) except KeyboardInterrupt: pass finally: self.stop() debug("Exiting Bluetooth Device Scanner") # Stops the service def stop(self): self.__running = False emptyDeviceList = BluetoothDeviceList() self.notifyListeners(emptyDeviceList) # Scans for nearby Bluetooth devices and notifies any registered listeners def updateKnownDeviceList(self): newDeviceList = self.getNewDeviceList() self.notifyListeners(newDeviceList) # Scans at least twice for nearby Bluetooth devices until the results are # consistent (we may miss a device in a single scan). # @return The new list of devices def getNewDeviceList(self): newDeviceList = self.scanForDevices() newDeviceList2 = self.scanForDevices() while(newDeviceList != newDeviceList2): debug("Found inconsistent results when scanning, trying again") newDeviceList = newDeviceList2 newDeviceList2 = self.scanForDevices() return newDeviceList # Scans for nearby Bluetooth devices # @return A BluetoothDeviceList containing the list of visible Bluetooth # devices def scanForDevices(self): newDeviceList = BluetoothDeviceList() try: scanResults = lightblue.finddevices() debug ("Visible bluetooth devices: " + str(scanResults)) for (deviceAddress, deviceName, deviceId) in scanResults: newDevice = BluetoothDevice(deviceName.encode(), deviceAddress.encode()) newDeviceList.add(newDevice) except BluetoothError, exception: debug("Failed to scan for local bluetooth devices: " + str(exception), "ERROR") return newDeviceList # Notifies any registered listeners if the devices in the new list have # changed from the previous one # @param newDeviceList A BluetoothDeviceList def notifyListeners(self, newDeviceList): notify = False if (self.__deviceList): newDevices = self.__deviceList.getNewDevices(newDeviceList) missingDevices = self.__deviceList.getMissingDevices(newDeviceList) notify = newDevices.getSize() > 0 or missingDevices.getSize() > 0 else: self.__deviceList = newDeviceList newDevices = newDeviceList missingDevices = BluetoothDeviceList() notify = True if (notify): for listener in self.__listeners: listener.deviceListUpdated(newDeviceList, newDevices, missingDevices) self.__deviceList = newDeviceList # Adds a listener to the list of classes to be notified when the local # bluetooth device list changes. # @param listener The new BluetoothDeviceListChangeListener to add def addListener(self, listener): self.__listeners.add(listener) # Class to represent a collection of Bluetooth devices class BluetoothDeviceList: # Creates a new instance of this collection class. def __init__(self): self.__devices = Set() # @return True if there are the same devices in the given list def __eq__(self, otherDeviceList): missingDevices = self.getMissingDevices(otherDeviceList) newDevices = self.getNewDevices(otherDeviceList) return missingDevices.getSize() == 0 and newDevices.getSize() == 0 # @return True if the same devices are not in the given list def __ne__(self, otherDeviceList): return self.__eq__(otherDeviceList) == False # Compares against the given device list and reports which devices are # not in the given list. # @return A BluetoothDeviceList containing the missing devices def getMissingDevices(self, newDeviceList): missingDevices = BluetoothDeviceList() for oldDevice in self.__devices: found = False for newDevice in newDeviceList.getDevices(): if (newDevice == oldDevice): found = True if (found == False): missingDevices.add(oldDevice) return missingDevices # Compares against the given device list and reports which devices are # not in this list. # @return A BluetoothDeviceList containing the new devices def getNewDevices(self, newDeviceList): newDevices = BluetoothDeviceList() for newDevice in newDeviceList.getDevices(): found = False for oldDevice in self.__devices: if (newDevice == oldDevice): found = True if (found == False): newDevices.add(newDevice) return newDevices # Adds a new device to the list def add(self, device): self.__devices.add(device) # @return The list of devices def getDevices(self): return self.__devices # @return The number of devices in the list def getSize(self): return len(self.__devices) # Class to represent a Bluetooth device. class BluetoothDevice: def __init__(self, name, address): self.__name = name self.__address = address def getName(self): return self.__name def getAddress(self): return self.__address def __eq__(self, other): return (self.__name == other.getName()) and (self.__address == other.getAddress()) def __ne__(self, other): return self.__eq__(other) == False def __hash__(self): return hash(self.__name + self.__address) def __str__(self): return self.__name + " [" + self.__address + "]" # An listener interface for classes that wish to be notified when the list of # local bluetooth device list changes class BluetoothDeviceListChangeListener: # Called when the local bluetooth device list changes # @param currentDevices A BluetoothDeviceList containing all currently known # devices # @param newDevices A BluetoothDeviceList containing all previously unknown # devices now visible # @param missingDevices A BluetoothDeviceList containing all previously # known devices now not visible def deviceListUpdated(self, currentDevices, newDevices, missingDevices): pass