blob: 526243e30b61e825377df38891f0ecc9f1a1c400 [file] [log] [blame]
#
# 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.
#
import abc
import dbus
import dbus.service
import dbus.mainloop.glib
import gc
import logging
import os
import pprint
import subprocess
import shlex
import sys
import threading
import time
import traceback
import uuid
import Queue
import optparse
from optparse import OptionParser, Option, OptionValueError
from ctypes import *
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')
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 bluez.GetManagedObjects().iteritems():
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:
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 = 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.loop_condition = True
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 stop_thread(self, userData):
self.logger.info("stop_thread")
self.timeout_happen = True
self.loop_condition = False
self.Gmainloop.quit()
return False
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()
self.timeout_happen = False
if kwargs and 'timeout' in kwargs:
self.source_id = GObject.timeout_add(kwargs['timeout'] * secondsToMilliseconds, self.stop_thread, None)
try:
self.Gmainloop = GObject.MainLoop()
self.Gmainloop.run()
if kwargs and 'timeout' in kwargs:
if not self.timeout_happen:
GObject.source_remove(self.source_id)
except KeyboardInterrupt:
self.loop_condition = False
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, 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 Usage(self, cmd):
line = "USAGE: "
if cmd == "scan":
line += "ble-scan [-t <timeout>] [<name>|<identifier>] [-q quiet]"
elif cmd == "scan-connect":
line += "ble-scan-connect [-t <timeout>] <name> [-q quiet]"
self.logger.info(line)
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 ParseInputLine(self, line, cmd=None):
if cmd == "scan" or cmd == "scan-connect":
args = shlex.split(line)
optParser = OptionParser(usage=optparse.SUPPRESS_USAGE)
optParser.add_option("-t", "--timeout", action="store", dest="timeout", type="float", default=bleScanDefaultTimeoutSec)
optParser.add_option("-q", "--quiet", action="store_true", dest="quiet")
try:
(options, remainingArgs) = optParser.parse_args(args)
except SystemExit:
self.Usage(cmd)
return None
if len(remainingArgs) > 1:
self.Usage(cmd)
return None
name = None
if len(remainingArgs):
name = str(remainingArgs[0])
elif cmd == "scan-connect":
self.Usage(cmd)
return None
return (options.timeout, options.quiet, name)
else:
args = shlex.split(line)
optParser = OptionParser(usage=optparse.SUPPRESS_USAGE)
try:
(options, remainingArgs) = optParser.parse_args(args)
except SystemExit:
return None
return remainingArgs
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']
while True:
if not self.loop_condition:
break
self.peripheral_list = self.adapter.find_devices([weave_service, weave_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)
self.Gmainloop.quit()
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.loop_condition = True
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):
self.loop_condition = True
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.loop_condition = True
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 = str(_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(ord(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.debug("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