blob: 33926b47b0e006da90c6bb5340a8fed9cd9401ec [file] [log] [blame]
#
# Copyright (c) 2020 Project CHIP Authors
# Copyright (c) 2019-2020 Google LLC.
# Copyright (c) 2015-2018 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 Chip Device Manager via BlueZ APIs.
#
from __future__ import absolute_import, print_function
import logging
import queue
import sys
import threading
import time
import traceback
import uuid
from ctypes import *
import dbus
import dbus.mainloop.glib
import dbus.service
from .ChipBleBase import ChipBleBase
from .ChipBleUtility import BLE_ERROR_REMOTE_DEVICE_DISCONNECTED, BleDisconnectEvent, ParseServiceData
try:
from gi.repository import GObject
except Exception as ex:
logging.exception("Unable to find GObject from gi.repository")
from pgi.repository import GObject
chip_service = uuid.UUID("0000FFF6-0000-1000-8000-00805F9B34FB")
chip_tx = uuid.UUID("18EE2EF5-263D-4559-959F-4F9C429F9D11")
chip_rx = uuid.UUID("18EE2EF5-263D-4559-959F-4F9C429F9D12")
chip_service_short = uuid.UUID("0000FFF6-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"
BLE_SCAN_CONNECT_GUARD_SEC = 2.0
BLE_STATUS_TRANSITION_TIMEOUT_SEC = 5.0
BLE_CONNECT_TIMEOUT_SEC = 15.0
BLE_SERVICE_DISCOVERY_TIMEOUT_SEC = 5.0
BLE_CHAR_DISCOVERY_TIMEOUT_SEC = 5.0
BLE_SUBSCRIBE_TIMEOUT_SEC = 5.0
BLE_WRITE_CHARACTERISTIC_TIMEOUT_SEC = 10.0
BLE_IDLE_DELTA = 0.1
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 bluez.GetManagedObjects().items():
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("ChipBLEMgr")
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(BLE_STATUS_TRANSITION_TIMEOUT_SEC):
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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
# Some devices do not advertise their uuid lists, thus we should also check service data.
if device.ServiceData:
for i in device.ServiceData:
if uuid.UUID(str(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 Exception as ex:
pass
class BluezDbusDevice:
def __init__(self, bluez_obj, bluez, bus, logger=None):
self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
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(BLE_SCAN_CONNECT_GUARD_SEC)
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(BLE_STATUS_TRANSITION_TIMEOUT_SEC):
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 Exception as ex:
self.logger.debug(traceback.format_exc())
def service_discover(self, gatt_dic):
self.logger.info("Discovering services")
try:
expired = time.time() + BLE_SERVICE_DISCOVERY_TIMEOUT_SEC
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(BLE_IDLE_DELTA)
self.logger.error("Service discovering fail")
return None
except dbus.exceptions.DBusException as ex:
self.logger.debug(str(ex))
return None
except Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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("ChipBLEMgr")
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
self.logger.debug(traceback.format_exc())
return None
def find_characteristic(self, uuid):
try:
expired = time.time() + BLE_CHAR_DISCOVERY_TIMEOUT_SEC
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(BLE_IDLE_DELTA)
self.logger.error("Char discovering fail")
return None
except dbus.exceptions.DBusException as ex:
self.logger.debug(str(ex))
return None
except Exception as ex:
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("ChipBLEMgr")
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
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 Exception as ex:
self.logger.debug(traceback.format_exc())
return False
class BluezManager(ChipBleBase):
def __init__(self, devMgr, logger=None):
if logger:
self.logger = logger
else:
self.logger = logging.getLogger("ChipBLEMgr")
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
)
self.scan_quiet = False
self.peripheral_list = []
self.device_uuid_list = []
self.chip_queue = 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 __del__(self):
self.disconnect()
self.setInputHook(self.orig_input_hook)
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 get_adapters(self):
return [
BluezDbusAdapter(p["object"], self.bluez, self.bus, self.logger)
for p in get_bluez_objects(
self.bluez, self.bus, ADAPTER_INTERFACE, "/org/bluez"
)
]
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 adapter in adapters:
self.logger.info("AdapterName: %s AdapterAddress: %s" % (
adapter.path.replace("/org/bluez/", ""), adapter.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() or "/org/bluez/{}".format(identifier) == str(adapter.path):
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)
def devMgrCB(self):
self.runLoopUntil(self.runIdleLoop)
def readlineCB(self):
self.runLoopUntil(self.runIdleLoop)
if self.orig_input_hook:
self.orig_input_hook()
def dump_scan_result(self, device):
self.logger.info("{0:<16}= {1}".format("Name", device.Name))
self.logger.info("{0:<16}= {1}".format("ID", device.device_id))
self.logger.info("{0:<16}= {1}".format("RSSI", device.RSSI))
self.logger.info("{0:<16}= {1}".format("Address", device.Address))
devIdInfo = self.get_peripheral_devIdInfo(device)
if devIdInfo != None:
self.logger.info("{0:<16}= {1}".format(
"Pairing State", devIdInfo.pairingState))
self.logger.info("{0:<16}= {1}".format(
"Discriminator", devIdInfo.discriminator))
self.logger.info("{0:<16}= {1}".format(
"Vendor Id", devIdInfo.vendorId))
self.logger.info("{0:<16}= {1}".format(
"Product Id", devIdInfo.productId))
if device.ServiceData:
for advuuid in device.ServiceData:
self.logger.info("{0:<16}= {1}".format(
"Adv UUID", str(advuuid)))
self.logger.info("{0:<16}= {1}".format(
"Adv Data", bytes(device.ServiceData[advuuid]).hex()))
else:
self.logger.info("")
self.logger.info("")
def scan_bg_implementation(self, **kwargs):
self.adapter.clear_adapter()
with self.chip_queue.mutex:
self.chip_queue.queue.clear()
self.adapter.adapter_bg_scan(True)
found = False
identifier = kwargs["identifier"]
timeout = kwargs["timeout"] + time.time()
self.device_uuid_list = []
self.peripheral_list = []
while time.time() < timeout:
scanned_peripheral_list = self.adapter.find_devices(
[
chip_service,
chip_service_short,
chromecast_setup_service,
chromecast_setup_service_short,
]
)
for device in scanned_peripheral_list:
try:
if not self.scan_quiet and device.Address not in self.device_uuid_list:
# display all scanned results
self.device_uuid_list.append(device.Address)
self.peripheral_list.append(device)
self.dump_scan_result(device)
devIdInfo = self.get_peripheral_devIdInfo(device)
if not devIdInfo:
# Not a chip device
continue
if identifier and (device.Name == identifier or str(device.Address).upper() == str(
identifier.upper()
) or str(devIdInfo.discriminator) == identifier):
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 Exception:
traceback.print_exc()
if found:
break
time.sleep(BLE_IDLE_DELTA)
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 get_peripheral_devIdInfo(self, peripheral):
if not peripheral.ServiceData:
return None
for advuuid in peripheral.ServiceData:
if str(advuuid).lower() == str(chip_service).lower():
return ParseServiceData(bytes(peripheral.ServiceData[advuuid]))
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 Chip 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.chip_queue.put(dcEvent)
self.devMgr.DriveBleIO()
return True