| # |
| # Copyright (c) 2015-2018 Nest Labs, Inc. |
| # Copyright (c) 2019-2020 Google LLC. |
| # 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 .WeaveUtility import WeaveUtility |
| from .WeaveBleUtility import VoidPtrToUUIDString |
| |
| 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 = WeaveUtility.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 |