# Bluetool code is placed under the GPL license. # Written by Aleksandr Aleksandrov (aleksandr.aleksandrov@emlid.com) # Copyright (c) 2017, Emlid Limited # All rights reserved. # If you are interested in using Bluetool code as a part of a # closed source project, please contact Emlid Limited (info@emlid.com). # This file is part of Bluetool. # Bluetool is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # Bluetool is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with Bluetool. If not, see <http://www.gnu.org/licenses/>. import logging import dbus import dbus.service import dbus.mainloop.glib try: from gi.repository import GObject except ImportError: import gobject as GObject from bluetool import Bluetooth logger = logging.getLogger(__name__) class _Rejected(dbus.DBusException): _dbus_error_name = "org.bluez.Error.Rejected" class Client(object): def authorize_service(self, dev_info, *args): """Should return None or raise _Rejected""" pass def request_pin_code(self, dev_info): """Should return str or int""" pass def request_passkey(self, dev_info): """Should return int""" pass def display_passkey(self, dev_info, *args): """Should return None or raise _Rejected""" pass def display_pin_code(self, dev_info, *args): """Should return None or raise _Rejected""" pass def request_confirmation(self, dev_info, *args): """Should return bool""" pass def request_authorization(self, dev_info): """Should return bool""" pass _bluetooth = Bluetooth() class Agent(dbus.service.Object): def __init__( self, client_class=None, timeout=180, path="/org/bluez/my_bluetooth_agent"): dbus.service.Object.__init__(self, dbus.SystemBus(), path) if client_class is not None: self._client = client_class() else: self._client = Client() def _trust(self, dbus_device): addr = self._dbus_device2addr(dbus_device) return _bluetooth.trust(addr) def _dbus_device2addr(self, dbus_device): address = str(dbus_device) address = address[len(address) - 17:len(address)] address = address.replace("_", ":") return address def _get_device_info(self, dbus_device): addr = self._dbus_device2addr(dbus_device) name = _bluetooth.get_device_property(addr, "Name") name = name.encode("utf-8") if name else "<unknown>" return {"mac_address": addr, "name": name} @dbus.service.method( "org.bluez.Agent1", in_signature="os", out_signature="") def AuthorizeService(self, device, uuid): logger.info("AuthorizeService: {}, {}\n".format(device, uuid)) dev_info = self._get_device_info(device) self._client.authorize_service(dev_info, str(uuid)) @dbus.service.method( "org.bluez.Agent1", in_signature="o", out_signature="s") def RequestPinCode(self, device): logger.info("RequestPinCode: {}\n".format(device)) if not self._trust(device): logger.error("RequestPinCode: failed to trust\n") raise _Rejected dev_info = self._get_device_info(device) try: pin_code = self._client.request_pin_code(dev_info) assert isinstance(pin_code, (str, int)) return str(pin_code) except BaseException as error: logger.error("RequestPinCode: {}\n".format(error)) raise _Rejected @dbus.service.method( "org.bluez.Agent1", in_signature="o", out_signature="u") def RequestPasskey(self, device): logger.info("RequestPasskey: {}\n".format(device)) if not self._trust(device): logger.error("RequestPasskey: failed to trust\n") raise _Rejected dev_info = self._get_device_info(device) try: passkey = int(self._client.request_passkey(dev_info)) except BaseException as error: logger.error("RequestPasskey: {}\n".format(error)) raise _Rejected return dbus.UInt32(passkey) @dbus.service.method( "org.bluez.Agent1", in_signature="os", out_signature="") def DisplayPinCode(self, device, pincode): logger.info("DisplayPinCode: {}: {}\n".format(device, pincode)) dev_info = self._get_device_info(device) self._client.display_pin_code(dev_info, str(pincode)) @dbus.service.method( "org.bluez.Agent1", in_signature="ouq", out_signature="") def DisplayPasskey(self, device, passkey, entered): logger.info("DisplayPasskey: {}: {} entered {}\n".format( device, passkey, entered)) dev_info = self._get_device_info(device) self._client.display_passkey(dev_info, str(passkey), str(entered)) @dbus.service.method( "org.bluez.Agent1", in_signature="ou", out_signature="") def RequestConfirmation(self, device, passkey): logger.info("RequestConfirmation: {}, {}\n".format(device, passkey)) if not self._trust(device): logger.error("RequestConfirmation: failed to trust\n") raise _Rejected dev_info = self._get_device_info(device) try: result = self._client.request_confirmation(dev_info, str(passkey)) except BaseException as error: logger.error("RequestConfirmation: {}\n".format(error)) raise _Rejected logger.info("RequestConfirmation: {}: {}\n".format(device, result)) try: assert result == True except AssertionError: raise _Rejected @dbus.service.method( "org.bluez.Agent1", in_signature="o", out_signature="") def RequestAuthorization(self, device): logger.info("RequestAuthorization: {}\n".format(device)) if not self._trust(device): logger.error("RequestAuthorization: failed to trust\n") raise _Rejected dev_info = self._get_device_info(device) try: result = self._client.request_authorization(dev_info) except BaseException as error: logger.error("RequestAuthorization: {}\n".format(error)) raise _Rejected logger.info("RequestAuthorization: {}: {}\n".format(device, result)) try: assert result == True except AssertionError: raise _Rejected class AgentSvr(object): def __init__( self, client_class, timeout=180, capability="KeyboardDisplay", path="/org/bluez/my_bluetooth_agent"): dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) self.client_class = client_class self.timeout = timeout self.capability = capability self.path = path self._bus = dbus.SystemBus() self._mainloop = GObject.MainLoop() _bluetooth.make_discoverable(False) def run(self): _bluetooth.make_discoverable(True, self.timeout) _bluetooth.set_adapter_property("Pairable", dbus.Boolean(1)) _bluetooth.set_adapter_property("PairableTimeout", dbus.UInt32(0)) self.agent = Agent(self.client_class, self.timeout, self.path) if not self._register(): self.shutdown() return self._mainloop.run() def _register(self): try: manager = dbus.Interface( self._bus.get_object("org.bluez", "/org/bluez"), "org.bluez.AgentManager1") manager.RegisterAgent(self.path, self.capability) manager.RequestDefaultAgent(self.path) except dbus.exceptions.DBusException as error: logger.error(str(error) + "\n") return False return True def shutdown(self): _bluetooth.make_discoverable(False) self._mainloop.quit() self._unregister() try: self.agent.remove_from_connection() del self.agent except AttributeError: pass def _unregister(self): try: manager = dbus.Interface( self._bus.get_object("org.bluez", "/org/bluez"), "org.bluez.AgentManager1") manager.UnregisterAgent(self.path) except dbus.exceptions.DBusException: pass if __name__ == "__main__": pass