text: #
  #    Copyright (c) 2015-2017 Nest Labs, Inc.
  #    All rights reserved.
  #
  #    Licensed under the Apache License, Version 2.0 (the "License");
  #    you may not use this file except in compliance with the License.
  #    You may obtain a copy of the License at
  #
  #        http://www.apache.org/licenses/LICENSE-2.0
  #
  #    Unless required by applicable law or agreed to in writing, software
  #    distributed under the License is distributed on an "AS IS" BASIS,
  #    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #    See the License for the specific language governing permissions and
  #    limitations under the License.
  #
  
  #
  #    @file
  #      BLE Central support for Weave Device Manager via BlueZ APIs.
  #
  
  from __future__ import absolute_import
  from __future__ import print_function
  import abc
  import dbus
  import dbus.service
  import dbus.mainloop.glib
  import gc
  import logging
  import os
  import pprint
  import subprocess
  import sys
  import threading
  import time
  import traceback
  import uuid
  import six.moves.queue
  
  
  from ctypes import *
  import six
  from six.moves import range
  
  try:
      from gi.repository import GObject
  except:
      from pgi.repository import GObject
  
  from .WeaveBleUtility import *
  from .WeaveBleUtility import _VoidPtrToUUIDString
  from .WeaveBleUtility import _VoidPtrToByteArray
  
  from .WeaveBleBase import WeaveBleBase
  
  weave_service = uuid.UUID('0000FEAF-0000-1000-8000-00805F9B34FB')
  weave_tx      = uuid.UUID('18EE2EF5-263D-4559-959F-4F9C429F9D11')
  weave_rx      = uuid.UUID('18EE2EF5-263D-4559-959F-4F9C429F9D12')
  weave_service_short = uuid.UUID('0000FEAF-0000-0000-0000-000000000000')
  chromecast_setup_service = uuid.UUID('0000FEA0-0000-1000-8000-00805F9B34FB')
  chromecast_setup_service_short = uuid.UUID('0000FEA0-0000-0000-0000-000000000000')
  
  BLUEZ_NAME = 'org.bluez'
  ADAPTER_INTERFACE           = BLUEZ_NAME + '.Adapter1'
  DEVICE_INTERFACE            = BLUEZ_NAME + '.Device1'
  SERVICE_INTERFACE           = BLUEZ_NAME + '.GattService1'
  CHARACTERISTIC_INTERFACE    = BLUEZ_NAME + '.GattCharacteristic1'
  DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties'
  
  bleScanConnectGuardSec = 2.0
  bleStatusTransitionTimeoutSec = 5.0
  bleScanDefaultTimeoutSec = 10.0
  bleConnectTimeoutSec = 15.0
  bleDisConnectTimeoutSec = 10.0
  bleSeviceDiscoveryTimeoutSec = 5.0
  bleCharDiscoveryTimeoutSec = 5.0
  bleSubscribeTimeoutSec = 5.0
  bleWriteCharacteristicTimeoutSec = 10.0
  bleIdleDelta = 0.1
  secondsToMilliseconds= 1000
  
  
  def get_bluez_objects(bluez, bus, interface, prefix_path):
      results = []
      if bluez is None or bus is None or interface is None or prefix_path is None:
          return results
      for item in six.iteritems(bluez.GetManagedObjects()):
          delegates = item[1].get(interface)
          if not delegates:
              continue
          slice = {}
          if item[0].startswith(prefix_path):
              slice['object'] = bus.get_object(BLUEZ_NAME, item[0])
              slice['path'] = item[0]
              results.append(slice)
      return results
  
  
  class BluezDbusAdapter():
      def __init__(self, bluez_obj, bluez, bus, logger=None):
          self.logger = logger if logger else logging.getLogger('WeaveBLEMgr')
          self.object = bluez_obj
          self.adapter = dbus.Interface(bluez_obj, ADAPTER_INTERFACE)
          self.adapter_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
          self.adapter_event = threading.Event()
          self.bluez = bluez
          self.bus = bus
          self.path = self.adapter.object_path
          self.signalReceiver = None
  
      def __del__(self):
          self.destroy()
  
      def destroy(self):
          self.logger.debug("destroy adapter")
          self.adapter_unregister_signal()
          self.adapter = None
          self.adapter_properties = None
          self.adapter_event.clear()
          self.bluez = None
          self.bus = None
          self.object = None
          self.path = None
          self.signalReceiver = None
  
      def adapter_register_signal(self):
          if self.signalReceiver is None:
              self.logger.debug("add adapter signal")
              self.signalReceiver = self.bus.add_signal_receiver(self.adapter_on_prop_changed_cb,
                                                                 bus_name=BLUEZ_NAME,
                                                                 dbus_interface=DBUS_PROPERTIES,
                                                                 signal_name="PropertiesChanged",
                                                                 path=self.path)
  
      def adapter_unregister_signal(self):
          if self.signalReceiver is not None:
              self.logger.debug(" remove adapter signal")
              self.bus.remove_signal_receiver(self.signalReceiver,
                                              signal_name="PropertiesChanged",
                                              dbus_interface="org.freedesktop.DBus.Properties")
  
      def adapter_on_prop_changed_cb(self, interface, changed_properties, invalidated_properties):
          if len(changed_properties) == 0:
              self.logger.debug( "changed_properties is empty")
              return
  
          if len(invalidated_properties) > 0:
              self.logger.debug( "invalidated_properties is not empty %s" % str(invalidated_properties))
              return
  
          if interface == ADAPTER_INTERFACE:
              if 'Discovering' in changed_properties:
                  self.adapter_event.set()
  
      def adapter_bg_scan(self, enable):
          self.adapter_event.clear()
          action_flag = False
          try:
              if enable:
                  if not self.Discovering:
                      action_flag = True
                      self.logger.info( "scanning started")
                      self.adapter.StartDiscovery()
                  else:
                      self.logger.info("it has started scanning")
              else:
                  if self.Discovering:
                      action_flag = True
                      self.adapter.StopDiscovery()
                      self.logger.info("scanning stopped")
                  else:
                      print("it has stopped scanning")
              if action_flag:
                  if not self.adapter_event.wait(bleStatusTransitionTimeoutSec):
                      if enable:
                          self.logger.debug("scan start error")
                      else:
                          self.logger.debug("scan stop error")
              self.adapter_event.clear()
          except dbus.exceptions.DBusException as ex:
              self.adapter_event.clear()
              self.logger.debug(str(ex))
          except:
              self.logger.debug(traceback.format_exc())
  
      @property
      def Address(self):
          try:
              result = self.adapter_properties.Get(ADAPTER_INTERFACE, 'Address')
              return result
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def UUIDs(self):
          try:
              return self.adapter_properties.Get(ADAPTER_INTERFACE, 'UUIDs')
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      def SetDiscoveryFilter(self, dict):
          try:
              self.adapter.SetDiscoveryFilter(dict)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
          except:
              self.logger.debug(traceback.format_exc())
  
      @property
      def Discovering(self):
          try:
              result = self.adapter_properties.Get(ADAPTER_INTERFACE, 'Discovering')
              return bool(result)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return False
          except:
              self.logger.debug(traceback.format_exc())
              return False
          
      def DiscoverableTimeout(self, timeoutSec):
          try:
              result = self.adapter_properties.Set(ADAPTER_INTERFACE, 'DiscoverableTimeout', timeoutSec)
              return bool(result)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return False
          except:
              self.logger.debug(traceback.format_exc())
              return False
  
      def Powered(self, enable):
          try:
              result = self.adapter_properties.Set(ADAPTER_INTERFACE, 'Powered', enable)
              return bool(result)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return False
          except:
              self.logger.debug(traceback.format_exc())
              return False
  
      def find_devices(self, uuids):
          devices = [BluezDbusDevice(p['object'], self.bluez, self.bus, self.logger) for p in get_bluez_objects(self.bluez, self.bus, DEVICE_INTERFACE, self.path)]
          found = []
          for device in devices:
              for i in device.uuids:
                  if i in uuids:
                      found.append(device)
                      break
          return found
  
      def clear_adapter(self):
          devices = [BluezDbusDevice(p['object'], self.bluez, self.bus, self.logger) for p in get_bluez_objects(self.bluez, self.bus, DEVICE_INTERFACE, self.path)]
          for device in devices:
              try:
                  if device.Connected:
                      device.device_bg_connect(False)
                  self.adapter.RemoveDevice(device.device.object_path)
              except:
                  pass
  
  
  class BluezDbusDevice():
      def __init__(self, bluez_obj, bluez, bus, logger=None):
          self.logger = logger if logger else logging.getLogger('WeaveBLEMgr')
          self.object = bluez_obj
          self.device = dbus.Interface(bluez_obj, DEVICE_INTERFACE)
          self.device_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
          self.path = self.device.object_path
          self.device_event = threading.Event()
          if self.Name:
              try:
                  self.device_id = uuid.uuid3(uuid.NAMESPACE_DNS, self.Name)
              except UnicodeDecodeError:
                  self.device_id = uuid.uuid3(uuid.NAMESPACE_DNS, self.Name.encode('utf-8'))
          else:
              self.device_id = uuid.uuid4()
          self.bluez = bluez
          self.bus = bus
          self.signalReceiver = None
          self.path = self.device.object_path
  
      def __del__(self):
          self.destroy()
  
      def destroy(self):
          self.logger.debug("destroy device")
          self.device_unregister_signal()
          self.device = None
          self.device_properties = None
          self.device_event = None
          self.device_id = None
          self.bluez = None
          self.bus = None
          self.object = None
          self.signalReceiver = None
  
      def device_register_signal(self):
          if self.signalReceiver is None:
              self.logger.debug("add device signal")
              self.signalReceiver = self.bus.add_signal_receiver(self.device_on_prop_changed_cb,
                                                                 bus_name=BLUEZ_NAME,
                                                                 dbus_interface=DBUS_PROPERTIES,
                                                                 signal_name="PropertiesChanged",
                                                                 path=self.path)
  
      def device_unregister_signal(self):
          if self.signalReceiver is not None:
              self.logger.debug("remove device signal")
              self.bus.remove_signal_receiver(self.signalReceiver,
                                              signal_name="PropertiesChanged",
                                              dbus_interface=DBUS_PROPERTIES)
  
      def device_on_prop_changed_cb(self, interface, changed_properties, invalidated_properties):
          if len(changed_properties) == 0:
              self.logger.debug( "changed_properties is empty")
              return
  
          if len(invalidated_properties) > 0:
              self.logger.debug( "invalidated_properties is not empty %s" % str(invalidated_properties))
              return
  
          if interface == DEVICE_INTERFACE:
              if 'Connected' in changed_properties:
                  self.device_event.set()
  
      def device_bg_connect(self, enable):
          time.sleep(bleScanConnectGuardSec)
          action_flag = False
          self.device_event.clear()
          try:
              if enable:
                  if not self.Connected:
                      action_flag = True
                      self.device.Connect()
                      self.logger.info("BLE connecting")
                  else:
                      self.logger.info("BLE has connected")
              else:
                  if self.Connected:
                      action_flag = True
                      self.device.Disconnect()
                      self.logger.info("BLE disconnected")
                  else:
                      self.logger.info("BLE has disconnected")
              if action_flag:
                  if not self.device_event.wait(bleStatusTransitionTimeoutSec):
                      if enable:
                          self.logger.info("BLE connect error")
                      else:
                          self.logger.info("BLE disconnect error")
              self.device_event.clear()
          except dbus.exceptions.DBusException as ex:
              self.device_event.clear()
              self.logger.info(str(ex))
          except:
              self.logger.debug(traceback.format_exc())
  
      def service_discover(self, gatt_dic):
          self.logger.info('Discovering services')
          try:
              expired = time.time() + bleSeviceDiscoveryTimeoutSec
              while time.time() < expired:
                  if self.ServicesResolved:
                      services = [BluezDbusGattService(p['object'], self.bluez, self.bus, self.logger) for p in get_bluez_objects(self.bluez, self.bus, SERVICE_INTERFACE, self.path)]
                      for service in services:
                          if service.uuid in gatt_dic['services']:
                              self.logger.info("Service discovering success")
                              return service
                  time.sleep(bleIdleDelta)
              self.logger.error("Service discovering fail")
              return None
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def uuids(self):
          try:
              uuids = self.device_properties.Get(DEVICE_INTERFACE, 'UUIDs')
              uuid_result = []
              for i in uuids:
                  if len(str(i)) == 4:
                      uuid_normal = '0000%s-0000-0000-0000-000000000000' % i
                  else:
                      uuid_normal = i
                  uuid_result.append(uuid.UUID(str(uuid_normal)))
              return uuid_result
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def Address(self):
          try:
              return self.device_properties.Get(DEVICE_INTERFACE, 'Address')
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def Name(self):
          try:
              name = self.device_properties.Get(DEVICE_INTERFACE, 'Name')
              return name
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def Connected(self):
          try:
              result = self.device_properties.Get(DEVICE_INTERFACE, 'Connected')
              return bool(result)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return False
          except:
              self.logger.debug(traceback.format_exc())
              return False
  
      @property
      def TxPower(self):
          try:
              return self.device_properties.Get(DEVICE_INTERFACE, 'TxPower')
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def RSSI(self):
          try:
              result = self.device_properties.Get(DEVICE_INTERFACE, 'RSSI')
              return result
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def Adapter(self):
          try:
              return self.device_properties.Get(DEVICE_INTERFACE, 'Adapter')
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def ServiceData(self):
          try:
              return self.device_properties.Get(DEVICE_INTERFACE, 'ServiceData')
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def ServicesResolved(self):
          try:
              result = self.device_properties.Get(DEVICE_INTERFACE, 'ServicesResolved')
              return bool(result)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return False
          except:
              self.logger.debug(traceback.format_exc())
              return False
  
  
  class BluezDbusGattService():
      def __init__(self, bluez_obj, bluez, bus, logger=None):
          self.logger = logger if logger else logging.getLogger('WeaveBLEMgr')
          self.object = bluez_obj
          self.service = dbus.Interface(bluez_obj, SERVICE_INTERFACE)
          self.service_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
          self.bluez = bluez
          self.bus = bus
          self.path = self.service.object_path
  
      def __del__(self):
          self.destroy()
  
      def destroy(self):
          self.logger.debug("destroy GattService")
          self.service = None
          self.service_properties = None
          self.bluez = None
          self.bus = None
          self.object = None
          self.path = None
  
      @property
      def uuid(self):
          try:
              result = uuid.UUID(str(self.service_properties.Get(SERVICE_INTERFACE, 'UUID')))
              return result
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      @property
      def Primary(self):
          try:
              result =bool(self.service_properties.Get(SERVICE_INTERFACE, 'Primary'))
              return result
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return False
          except:
              self.logger.debug(traceback.format_exc())
              return False
  
      @property
      def Device(self):
          try:
              result = self.service_properties.Get(SERVICE_INTERFACE, 'Device')
              return result
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      def find_characteristic(self, uuid):
          try:
              expired = time.time() + bleCharDiscoveryTimeoutSec
              while time.time() < expired:
                  characteristics = [BluezDbusGattCharacteristic(p['object'], self.bluez, self.bus, self.logger) for p in get_bluez_objects(self.bluez, self.bus, CHARACTERISTIC_INTERFACE, self.path)]
                  for characteristic in characteristics:
                      if characteristic.uuid == uuid:
                          return characteristic
                  time.sleep(bleIdleDelta)
              self.logger.error("Char discovering fail")
              return None
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
  
  class BluezDbusGattCharacteristic():
      def __init__(self, bluez_obj, bluez, bus, logger=None):
          self.logger = logger if logger else logging.getLogger('WeaveBLEMgr')
          self.object = bluez_obj
          self.characteristic = dbus.Interface(bluez_obj, CHARACTERISTIC_INTERFACE)
          self.characteristic_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
          self.received = None
          self.path = self.characteristic.object_path
          self.bluez = bluez
          self.bus = bus
          self.signalReceiver = None
  
      def __del__(self):
          self.destroy()
  
      def destroy(self):
          self.logger.debug("destroy GattCharacteristic")
          self.gattCharacteristic_unregister_signal()
          self.characteristic = None
          self.object = None
          self.characteristic_properties = None
          self.received = None
          self.bluez = None
          self.bus = None
          self.path = None
          self.signalReceiver = None
  
      def gattCharacteristic_register_signal(self):
          if not self.signalReceiver:
              self.logger.debug("add GattCharacteristic signal")
              self.signalReceiver = self.bus.add_signal_receiver(self.gatt_on_characteristic_changed_cb,
                                                                 bus_name=BLUEZ_NAME,
                                                                 dbus_interface=DBUS_PROPERTIES,
                                                                 signal_name="PropertiesChanged",
                                                                 path=self.path)
  
      def gattCharacteristic_unregister_signal(self):
          if self.signalReceiver:
              self.logger.debug("remove GattCharacteristic signal")
  
              self.bus.remove_signal_receiver(self.signalReceiver,
                                              bus_name=BLUEZ_NAME,
                                              signal_name="PropertiesChanged",
                                              dbus_interface=DBUS_PROPERTIES,
                                              path=self.path)
              self.signalReceiver = None
  
      def gatt_on_characteristic_changed_cb(self, interface, changed_properties, invalidated_properties):
          self.logger.debug("property change in" + str(self.characteristic) + str(changed_properties))
  
          if len(changed_properties) == 0:
              return
  
          if len(invalidated_properties) > 0:
              return
  
          if interface == CHARACTERISTIC_INTERFACE:
              if 'Value' in changed_properties:
                  if self.received:
                      self.received(changed_properties['Value'])
  
      def WriteValue(self, value, options, reply_handler, error_handler, timeout):
          try:
              self.characteristic.WriteValue(value, options, reply_handler=reply_handler, error_handler=error_handler, timeout=timeout)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
          except:
              self.logger.debug(traceback.format_exc())
  
      @property
      def uuid(self):
          try:
              result = uuid.UUID(str(self.characteristic_properties.Get(CHARACTERISTIC_INTERFACE, 'UUID')))
              return result
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return None
          except:
              self.logger.debug(traceback.format_exc())
              return None
  
      def StartNotify(self, cbfunct, reply_handler, error_handler, timeout):
          try:
              if not cbfunct:
                  self.logger.info("please provide the notify callback function")
              self.received = cbfunct
              self.gattCharacteristic_register_signal()
              self.characteristic.StartNotify(reply_handler=reply_handler, error_handler=error_handler, timeout=timeout)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
          except:
              self.logger.debug(traceback.format_exc())
  
      def StopNotify(self, reply_handler, error_handler, timeout):
          try:
              self.logger.debug("stopping notifying")
              self.characteristic.StopNotify(reply_handler=reply_handler, error_handler=error_handler, timeout=timeout)
              self.gattCharacteristic_unregister_signal()
              self.received = None
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
          except:
              self.logger.debug(traceback.format_exc())
  
      @property
      def Notifying(self):
          try:
              result = self.characteristic_properties.Get(CHARACTERISTIC_INTERFACE, 'Notifying')
              return bool(result)
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
              return False
          except:
              self.logger.debug(traceback.format_exc())
              return False
  
  
  class BluezManager(WeaveBleBase):
      def __init__(self, devMgr, logger=None):
          if logger:
              self.logger = logger
          else:
              self.logger = logging.getLogger('WeaveBLEMgr')
              logging.basicConfig(
                  level=logging.INFO,
                  format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
          self.scan_quiet= False
          self.peripheral_list = []
          self.weave_queue = six.moves.queue.Queue()
          self.Gmainloop = None
          self.daemon_thread = None
          self.adapter = None
          dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
          GObject.threads_init()
          dbus.mainloop.glib.threads_init()
          self.bus = dbus.SystemBus()
          self.bluez = dbus.Interface(self.bus.get_object(BLUEZ_NAME, '/'), 'org.freedesktop.DBus.ObjectManager')
          self.target = None
          self.service = None
          self.orig_input_hook = None
          self.hookFuncPtr = None
          self.connect_state = False
          self.tx = None
          self.rx = None
          self.setInputHook(self.readlineCB)
          self.devMgr = devMgr
          self.devMgr.SetBlockingCB(self.devMgrCB)
  
          def HandleBleEventCB():
              return self.GetBleEvent()
  
          def HandleBleWriteCharCB(connObj, svcId, charId, buffer, length):
              return self.WriteBleCharacteristic(connObj, svcId, charId, buffer, length)
  
          def HandleBleSubscribeCB(connObj, svcId, charId, subscribe):
              return self.SubscribeBleCharacteristic(connObj, svcId, charId, subscribe)
  
          def HandleBleCloseCB(connObj):
              return self.CloseBle(connObj)
  
          self.devMgr.SetBleEventCB(HandleBleEventCB)
          self.devMgr.SetBleWriteCharCB(HandleBleWriteCharCB)
          self.devMgr.SetBleSubscribeCharCB(HandleBleSubscribeCB)
          self.devMgr.SetBleCloseCB(HandleBleCloseCB)
  
      def __del__(self):
          self.disconnect()
          self.setInputHook(self.orig_input_hook)
          self.devMgr.SetBlockingCB(None)
          self.devMgr.SetBleEventCB(None)
  
      def ble_adapter_select(self, identifier=None):
          if self.adapter:
              self.adapter.destroy()
              self.adapter = None
          self.adapter = self.get_adapter_by_addr(identifier)
          self.adapter.adapter_register_signal()
          self.adapter.Powered(False)
          self.adapter.Powered(True)
  
      def ble_adapter_print(self):
          try:
              adapters = [BluezDbusAdapter(p['object'], self.bluez, self.bus, self.logger) for p in get_bluez_objects(self.bluez, self.bus, ADAPTER_INTERFACE, '/org/bluez')]
              for i in range(len(adapters)):
                  self.logger.info("adapter %s = %s" % (i, adapters[i].Address))
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
  
      def get_adapter_by_addr(self, identifier):
          try:
              adapters = [BluezDbusAdapter(p['object'], self.bluez, self.bus, self.logger) for p in get_bluez_objects(self.bluez, self.bus, ADAPTER_INTERFACE, '/org/bluez')]
              if identifier is None:
                  return adapters[0]
              if len(adapters) > 0:
                  for adapter in adapters:
                      if str(adapter.Address).upper() == str(identifier).upper():
                          return adapter
              self.logger.info("adapter %s cannot be found, expect the ble mac address" % (identifier))
              return None
  
          except dbus.exceptions.DBusException as ex:
              self.logger.debug(str(ex))
  
      def runLoopUntil(self, target=None, **kwargs):
          if target:
              self.daemon_thread = threading.Thread(target=self.running_thread, args=(target, kwargs))
              self.daemon_thread.daemon = True
              self.daemon_thread.start()
  
          try:
              self.Gmainloop = GObject.MainLoop()
              self.Gmainloop.run()
          except KeyboardInterrupt:
              self.Gmainloop.quit()
              sys.exit(1)
  
      def running_thread(self, target, kwargs):
          try:
              while not self.Gmainloop or not self.Gmainloop.is_running():
                  time.sleep(0.00001)
              target(**kwargs)
          except Exception as err:
              traceback.print_exc()
          finally:
              self.Gmainloop.quit()
  
      def setInputHook(self, hookFunc):
          """Set the PyOS_InputHook to call the specific function."""
          hookFunctionType = CFUNCTYPE(None)
          self.hookFuncPtr = hookFunctionType(hookFunc)
          pyos_inputhook_ptr = c_void_p.in_dll(pythonapi, "PyOS_InputHook")
          # save the original so that on del we can revert it back to the way it was.
          self.orig_input_hook = cast(pyos_inputhook_ptr.value, PYFUNCTYPE(c_int))
          # set the new hook. readLine will call this periodically as it polls for input.
          pyos_inputhook_ptr.value = cast(self.hookFuncPtr, c_void_p).value
  
      def runIdleLoop(self, **kwargs):
          time.sleep(0)
          pass
  
      def devMgrCB(self):
          self.runLoopUntil(self.runIdleLoop)
  
      def readlineCB(self):
          self.runLoopUntil(self.runIdleLoop)
  
          if self.orig_input_hook:
              self.orig_input_hook()
  
      def scan_connect(self, line):
          """ API to perform both scan and connect operations in one call."""
          args = self.ParseInputLine(line, "scan-connect")
          if not args:
              return False
          if not self.adapter:
              self.logger.info("use default adapter")
              self.ble_adapter_select()
          self.scan_quiet= args[1]
          self.scan(line)
          if self.target:
              return self.connect(args[2])
          else:
              self.logger.info("Failed to scan device named: " + args[2] + ". Connection skipped.")
              return False
  
      def dump_scan_result(self, device):
          self.logger.info("{0:<10}{1}".format("Name =", device.Name))
          self.logger.info("{0:<10}{1}".format("ID =", device.device_id))
          self.logger.info("{0:<10}{1}".format("RSSI =", device.RSSI))
          self.logger.info("{0:<10}{1}".format("address =", device.Address))
          self.logger.info("ADV data: " + ("".join([str(i) for i in dict(device.ServiceData).keys()])) if device.ServiceData else '')
          self.logger.info("")
  
      def scan_bg_implementation(self, **kwargs):
          self.adapter.clear_adapter()
          with self.weave_queue.mutex:
              self.weave_queue.queue.clear()
          self.adapter.adapter_bg_scan(True)
          found = False
          identifier = kwargs['identifier']
          timeout = kwargs['timeout'] + time.time()
  
          while time.time() < timeout:
              self.peripheral_list = self.adapter.find_devices([weave_service, weave_service_short, chromecast_setup_service, chromecast_setup_service_short])
              for device in self.peripheral_list:
                  try:
                      if not self.scan_quiet:
                          # display all scanned results
                          self.dump_scan_result(device)
                      if device.Name == identifier or str(device.Address).upper() == str(identifier.upper()):
                          if self.scan_quiet:
                              # only display the scanned target's info when quiet
                              self.dump_scan_result(device)
                          self.target = device
                          found = True
                          break
                  except:
                      pass
              if found:
                  break
  
              time.sleep(bleIdleDelta)
          self.adapter.adapter_bg_scan(False)
  
      def scan(self, line):
          args = self.ParseInputLine(line, "scan")
          if not args:
              return False
          self.target = None
          if not self.adapter:
              self.logger.info("use default adapter")
              self.ble_adapter_select()
          del self.peripheral_list[:]
          self.scan_quiet= args[1]
          self.runLoopUntil(self.scan_bg_implementation, timeout=args[0], identifier=args[2])
          return True
  
      def weaveServieCharConnect(self):
          gatt_dic={'services': [weave_service, weave_service_short], 'chars': [weave_tx, weave_rx]}
          self.service = self.target.service_discover(gatt_dic)
          if self.service is None:
              self.logger.info("weave service cannot be found")
              return False
  
          self.rx = self.service.find_characteristic(weave_rx)
          if self.rx is None:
              self.logger.info("weave rx char cannot be found")
              return False
  
          self.tx = self.service.find_characteristic(weave_tx)
          if self.tx is None:
              self.logger.info("weave tx char cannot be found")
              self.connect_state = False
              return False
  
          return True
  
      def connect_bg_implementation(self, **kwargs):
          self.connect_state = False
          if self.adapter is None:
              self.logger.info("adapter is not configured")
              return
          self.target.device_register_signal()
          self.target.device_bg_connect(True)
          if self.weaveServieCharConnect():
              self.logger.info("connect success")
              self.connect_state = True
          else:
              self.logger.info("connect fail")
              self.connect_state = False
  
      def disconnect_bg_implementation(self, **kwargs):
          if self.target:
              self.target.device_bg_connect(False)
          if self.tx:
              self.tx.destroy()
              self.tx = None
          if self.rx:
              self.rx.destroy()
              self.rx = None
          if self.service:
              self.service.destroy()
              self.service = None
  
      def connect(self, identifier):
          found = False
          self.logger.info("trying to connect to " + identifier)
          for p in self.peripheral_list:
              p_id = str(p.device_id)
              p_name = str(p.Name)
              p_address = str(p.Address)
              self.logger.debug(p_id + " vs " + str(identifier))
              self.logger.debug(p_name + " vs " + str(identifier))
              self.logger.debug(p_address + " vs " + str(identifier))
              if p_id == str(identifier) or p_name == str(identifier) or p_address.upper() == str(identifier).upper():
                  self.target = p
                  found = True
                  break
          if found:
              self.runLoopUntil(self.connect_bg_implementation, identifier = identifier, timeout=bleConnectTimeoutSec)
              if self.connect_state:
                  return True
              else:
                  return False
          else:
              print("device cannot be found")
              return False
  
      def disconnect(self):
          self.runLoopUntil(self.disconnect_bg_implementation)
          for i in range(2):
              n = gc.collect()
              self.logger.debug("Unreached objects: %d", n)
              self.logger.debug("Final Garbage:")
              self.logger.debug(pprint.pformat(gc.garbage))
  
      def WriteCharactertisticSuccessCB(self, *args):
          self.logger.debug("write complete")
          if self.devMgr:
              txEvent = BleTxEvent(charId=self.charId_tx, svcId=self.svcId_tx, status=True)
              self.weave_queue.put(txEvent)
              self.devMgr.DriveBleIO()
  
      def WriteCharactertisticErrorCB(self, *args):
          self.logger.debug("write fail, error:" + repr(args))
          if self.devMgr:
              txEvent = BleTxEvent(charId=self.charId_tx, svcId=self.svcId_tx, status=False)
              self.weave_queue.put(txEvent)
              self.devMgr.DriveBleIO()
  
      def WriteBleCharacteristic(self, connObj, svcId, charId, buffer, length):
          self.logger.debug("write start")
          result = False
          if self.target and self.target.Connected:
              converted_data = _VoidPtrToByteArray(buffer, length)
              self.charId_tx = bytearray(uuid.UUID(str(_VoidPtrToUUIDString(charId, 16))).bytes)
              self.svcId_tx = bytearray(uuid.UUID(str(_VoidPtrToUUIDString(svcId, 16))).bytes)
              self.tx.WriteValue(dbus.Array([dbus.Byte(i) for i in converted_data], 'y'),
                                 options="",
                                 reply_handler=self.WriteCharactertisticSuccessCB,
                                 error_handler=self.WriteCharactertisticErrorCB,
                                 timeout=bleWriteCharacteristicTimeoutSec)
              result = True
          else:
              self.logger.warning("WARNING: peripheral is no longer connected.")
          return result
  
      def receivedNotificationCB(self, data):
          self.logger.debug("received data")
          bytes = bytearray(data)
          if self.devMgr:
              rxEvent = BleRxEvent(charId=self.charId_rx, svcId=self.svcId_rx, buffer=bytes)
              self.weave_queue.put(rxEvent)
              self.devMgr.DriveBleIO()
  
      def subscribeSuccessCb(self, *args):
          self.logger.debug("subscribe complete")
          if self.rx.Notifying:
              success = True
          else:
              success = False
          operation = BleSubscribeOperation_Subscribe
          if self.devMgr:
              subscribeEvent = BleSubscribeEvent(charId=self.charId_rx,
                                                 svcId=self.svcId_rx,
                                                 status=success,
                                                 operation=operation)
              self.weave_queue.put(subscribeEvent)
              self.devMgr.DriveBleIO()
  
      def subscribeErrorCb(self, *args):
          self.logger.error("subscribe fail, error:" + repr(args))
          success = False
          operation = BleSubscribeOperation_Subscribe
          if self.devMgr:
              subscribeEvent = BleSubscribeEvent(charId=self.charId_rx,
                                                 svcId=self.svcId_rx,
                                                 status=success,
                                                 operation=operation)
              self.weave_queue.put(subscribeEvent)
              self.devMgr.DriveBleIO()
  
      def unsubscribeSuccessCb(self, *args):
          self.logger.debug("unsubscribe complete")
          success = True
          operation = BleSubscribeOperation_Unsubscribe
          if self.devMgr:
              subscribeEvent = BleSubscribeEvent(charId=self.charId_rx,
                                                 svcId=self.svcId_rx,
                                                 status=success,
                                                 operation=operation)
              self.weave_queue.put(subscribeEvent)
              self.devMgr.DriveBleIO()
  
      def unsubscribeErrorCb(self, *args):
          self.logger.error("unsubscribe fail, error:" + repr(args))
          success = False
          operation = BleSubscribeOperation_Unsubscribe
          if self.devMgr:
              subscribeEvent = BleSubscribeEvent(charId=self.charId_rx,
                                                 svcId=self.svcId_rx,
                                                 status=success,
                                                 operation=operation)
              self.weave_queue.put(subscribeEvent)
              self.devMgr.DriveBleIO()
  
      def SubscribeBleCharacteristic(self, connObj, svcId, charId, subscribe):
          result = False
          self.charId_rx = bytearray(uuid.UUID(_VoidPtrToUUIDString(charId, 16)).bytes)
          self.svcId_rx = bytearray(uuid.UUID(str(_VoidPtrToUUIDString(svcId, 16))).bytes)
  
          if self.target and self.target.Connected:
              try:
                  if subscribe:
                      self.logger.debug("try to subscribe")
                      self.rx.StartNotify(cbfunct=self.receivedNotificationCB,
                                          reply_handler=self.subscribeSuccessCb,
                                          error_handler=self.subscribeErrorCb,
                                          timeout=bleSubscribeTimeoutSec)
                  else:
                      self.logger.debug("try to unsubscribe")
                      self.rx.StopNotify(reply_handler=self.unsubscribeSuccessCb,
                                         error_handler=self.unsubscribeErrorCb,
                                         timeout=bleSubscribeTimeoutSec)
              except:
                  self.logger.debug(traceback.format_exc())
                  self.logger.debug("(un)subscribe error")
              result = True
          else:
              self.logger.warning("WARNING: peripheral is no longer connected.")
          return result
  
      def GetBleEvent(self):
          """ Called by WeaveDeviceMgr.py on behalf of Weave to retrieve a queued message."""
          if not self.weave_queue.empty():
              ev = self.weave_queue.get()
  
              if isinstance(ev, BleRxEvent):
                  eventStruct = BleRxEventStruct.fromBleRxEvent(ev)
                  return cast( pointer(eventStruct), c_void_p).value
              elif isinstance(ev, BleTxEvent):
                  eventStruct = BleTxEventStruct.fromBleTxEvent(ev)
                  return cast( pointer(eventStruct), c_void_p).value
              elif isinstance(ev, BleSubscribeEvent):
                  eventStruct = BleSubscribeEventStruct.fromBleSubscribeEvent(ev)
                  return cast( pointer(eventStruct), c_void_p).value
              elif isinstance(ev, BleDisconnectEvent):
                  eventStruct = BleDisconnectEventStruct.fromBleDisconnectEvent(ev)
                  return cast( pointer(eventStruct), c_void_p).value
  
          return None
  
      def ble_debug_log(self, line):
          args = self.ParseInputLine(line)
          if int(args[0]) == 1:
              self.logger.setLevel(logging.DEBUG)
              self.logger.debug("current logging level is debug")
          else:
              self.logger.setLevel(logging.INFO)
              self.logger.info("current logging level is info")
          return True
  
      def CloseBle(self, connObj):
          """ Called by Weave to close the BLE connection."""
          # Workaround: comment out disconnect because of hang when close, plz call disconnect explicitly after close
          # Need to fix it
          # self.disconnect()
          if self.devMgr:
              dcEvent = BleDisconnectEvent(BLE_ERROR_REMOTE_DEVICE_DISCONNECTED)
              self.weave_queue.put(dcEvent)
              self.devMgr.DriveBleIO()
          return True
  