Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / src / controller / python / chip / ChipBluezMgr.py
1 #
2 #    Copyright (c) 2020 Project CHIP Authors
3 #    Copyright (c) 2019-2020 Google LLC.
4 #    Copyright (c) 2015-2018 Nest Labs, Inc.
5 #    All rights reserved.
6 #
7 #    Licensed under the Apache License, Version 2.0 (the "License");
8 #    you may not use this file except in compliance with the License.
9 #    You may obtain a copy of the License at
10 #
11 #        http://www.apache.org/licenses/LICENSE-2.0
12 #
13 #    Unless required by applicable law or agreed to in writing, software
14 #    distributed under the License is distributed on an "AS IS" BASIS,
15 #    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 #    See the License for the specific language governing permissions and
17 #    limitations under the License.
18 #
19
20 #
21 #    @file
22 #      BLE Central support for Chip Device Manager via BlueZ APIs.
23 #
24
25 from __future__ import absolute_import
26 from __future__ import print_function
27 import dbus
28 import dbus.service
29 import dbus.mainloop.glib
30 import gc
31 import logging
32 import pprint
33 import sys
34 import threading
35 import time
36 import traceback
37 import uuid
38 import queue
39
40 from ctypes import *
41
42 try:
43     from gi.repository import GObject
44 except Exception as ex:
45     logging.exception("Unable to find GObject from gi.repository")
46     from pgi.repository import GObject
47
48 from .ChipUtility import ChipUtility
49
50 from .ChipBleUtility import (
51     BLE_SUBSCRIBE_OPERATION_SUBSCRIBE,
52     BLE_SUBSCRIBE_OPERATION_UNSUBSCRIBE,
53     BLE_ERROR_REMOTE_DEVICE_DISCONNECTED,
54     VoidPtrToUUIDString,
55     BleTxEvent,
56     BleDisconnectEvent,
57     BleRxEvent,
58     BleSubscribeEvent,
59     BleTxEventStruct,
60     BleDisconnectEventStruct,
61     BleRxEventStruct,
62     BleSubscribeEventStruct,
63     BleDeviceIdentificationInfo,
64     ParseServiceData,
65 )
66
67 from .ChipBleBase import ChipBleBase
68
69 chip_service = uuid.UUID("0000FEAF-0000-1000-8000-00805F9B34FB")
70 chip_tx = uuid.UUID("18EE2EF5-263D-4559-959F-4F9C429F9D11")
71 chip_rx = uuid.UUID("18EE2EF5-263D-4559-959F-4F9C429F9D12")
72 chip_service_short = uuid.UUID("0000FEAF-0000-0000-0000-000000000000")
73 chromecast_setup_service = uuid.UUID("0000FEA0-0000-1000-8000-00805F9B34FB")
74 chromecast_setup_service_short = uuid.UUID("0000FEA0-0000-0000-0000-000000000000")
75
76 BLUEZ_NAME = "org.bluez"
77 ADAPTER_INTERFACE = BLUEZ_NAME + ".Adapter1"
78 DEVICE_INTERFACE = BLUEZ_NAME + ".Device1"
79 SERVICE_INTERFACE = BLUEZ_NAME + ".GattService1"
80 CHARACTERISTIC_INTERFACE = BLUEZ_NAME + ".GattCharacteristic1"
81 DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"
82
83 BLE_SCAN_CONNECT_GUARD_SEC = 2.0
84 BLE_STATUS_TRANSITION_TIMEOUT_SEC = 5.0
85 BLE_CONNECT_TIMEOUT_SEC = 15.0
86 BLE_SERVICE_DISCOVERY_TIMEOUT_SEC = 5.0
87 BLE_CHAR_DISCOVERY_TIMEOUT_SEC = 5.0
88 BLE_SUBSCRIBE_TIMEOUT_SEC = 5.0
89 BLE_WRITE_CHARACTERISTIC_TIMEOUT_SEC = 10.0
90 BLE_IDLE_DELTA = 0.1
91
92
93 def get_bluez_objects(bluez, bus, interface, prefix_path):
94     results = []
95     if bluez is None or bus is None or interface is None or prefix_path is None:
96         return results
97     for item in bluez.GetManagedObjects().items():
98         delegates = item[1].get(interface)
99         if not delegates:
100             continue
101         slice = {}
102         if item[0].startswith(prefix_path):
103             slice["object"] = bus.get_object(BLUEZ_NAME, item[0])
104             slice["path"] = item[0]
105             results.append(slice)
106     return results
107
108
109 class BluezDbusAdapter:
110     def __init__(self, bluez_obj, bluez, bus, logger=None):
111         self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
112         self.object = bluez_obj
113         self.adapter = dbus.Interface(bluez_obj, ADAPTER_INTERFACE)
114         self.adapter_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
115         self.adapter_event = threading.Event()
116         self.bluez = bluez
117         self.bus = bus
118         self.path = self.adapter.object_path
119         self.signalReceiver = None
120
121     def __del__(self):
122         self.destroy()
123
124     def destroy(self):
125         self.logger.debug("destroy adapter")
126         self.adapter_unregister_signal()
127         self.adapter = None
128         self.adapter_properties = None
129         self.adapter_event.clear()
130         self.bluez = None
131         self.bus = None
132         self.object = None
133         self.path = None
134         self.signalReceiver = None
135
136     def adapter_register_signal(self):
137         if self.signalReceiver is None:
138             self.logger.debug("add adapter signal")
139             self.signalReceiver = self.bus.add_signal_receiver(
140                 self.adapter_on_prop_changed_cb,
141                 bus_name=BLUEZ_NAME,
142                 dbus_interface=DBUS_PROPERTIES,
143                 signal_name="PropertiesChanged",
144                 path=self.path,
145             )
146
147     def adapter_unregister_signal(self):
148         if self.signalReceiver is not None:
149             self.logger.debug(" remove adapter signal")
150             self.bus.remove_signal_receiver(
151                 self.signalReceiver,
152                 signal_name="PropertiesChanged",
153                 dbus_interface="org.freedesktop.DBus.Properties",
154             )
155
156     def adapter_on_prop_changed_cb(
157         self, interface, changed_properties, invalidated_properties
158     ):
159         if len(changed_properties) == 0:
160             self.logger.debug("changed_properties is empty")
161             return
162
163         if len(invalidated_properties) > 0:
164             self.logger.debug(
165                 "invalidated_properties is not empty %s" % str(invalidated_properties)
166             )
167             return
168
169         if interface == ADAPTER_INTERFACE:
170             if "Discovering" in changed_properties:
171                 self.adapter_event.set()
172
173     def adapter_bg_scan(self, enable):
174         self.adapter_event.clear()
175         action_flag = False
176         try:
177             if enable:
178                 if not self.Discovering:
179                     action_flag = True
180                     self.logger.info("scanning started")
181                     self.adapter.StartDiscovery()
182                 else:
183                     self.logger.info("it has started scanning")
184             else:
185                 if self.Discovering:
186                     action_flag = True
187                     self.adapter.StopDiscovery()
188                     self.logger.info("scanning stopped")
189                 else:
190                     print("it has stopped scanning")
191             if action_flag:
192                 if not self.adapter_event.wait(BLE_STATUS_TRANSITION_TIMEOUT_SEC):
193                     if enable:
194                         self.logger.debug("scan start error")
195                     else:
196                         self.logger.debug("scan stop error")
197             self.adapter_event.clear()
198         except dbus.exceptions.DBusException as ex:
199             self.adapter_event.clear()
200             self.logger.debug(str(ex))
201         except Exception as ex:
202             self.logger.debug(traceback.format_exc())
203
204     @property
205     def Address(self):
206         try:
207             result = self.adapter_properties.Get(ADAPTER_INTERFACE, "Address")
208             return result
209         except dbus.exceptions.DBusException as ex:
210             self.logger.debug(str(ex))
211             return None
212         except Exception as ex:
213             self.logger.debug(traceback.format_exc())
214             return None
215
216     @property
217     def UUIDs(self):
218         try:
219             return self.adapter_properties.Get(ADAPTER_INTERFACE, "UUIDs")
220         except dbus.exceptions.DBusException as ex:
221             self.logger.debug(str(ex))
222             return None
223         except Exception as ex:
224             self.logger.debug(traceback.format_exc())
225             return None
226
227     def SetDiscoveryFilter(self, dict):
228         try:
229             self.adapter.SetDiscoveryFilter(dict)
230         except dbus.exceptions.DBusException as ex:
231             self.logger.debug(str(ex))
232         except Exception as ex:
233             self.logger.debug(traceback.format_exc())
234
235     @property
236     def Discovering(self):
237         try:
238             result = self.adapter_properties.Get(ADAPTER_INTERFACE, "Discovering")
239             return bool(result)
240         except dbus.exceptions.DBusException as ex:
241             self.logger.debug(str(ex))
242             return False
243         except Exception as ex:
244             self.logger.debug(traceback.format_exc())
245             return False
246
247     def DiscoverableTimeout(self, timeoutSec):
248         try:
249             result = self.adapter_properties.Set(
250                 ADAPTER_INTERFACE, "DiscoverableTimeout", timeoutSec
251             )
252             return bool(result)
253         except dbus.exceptions.DBusException as ex:
254             self.logger.debug(str(ex))
255             return False
256         except Exception as ex:
257             self.logger.debug(traceback.format_exc())
258             return False
259
260     def Powered(self, enable):
261         try:
262             result = self.adapter_properties.Set(ADAPTER_INTERFACE, "Powered", enable)
263             return bool(result)
264         except dbus.exceptions.DBusException as ex:
265             self.logger.debug(str(ex))
266             return False
267         except Exception as ex:
268             self.logger.debug(traceback.format_exc())
269             return False
270
271     def find_devices(self, uuids):
272         devices = [
273             BluezDbusDevice(p["object"], self.bluez, self.bus, self.logger)
274             for p in get_bluez_objects(
275                 self.bluez, self.bus, DEVICE_INTERFACE, self.path
276             )
277         ]
278         found = []
279         for device in devices:
280             for i in device.uuids:
281                 if i in uuids:
282                     found.append(device)
283                     break
284             # Some devices do not advertise their uuid lists, thus we should also check service data.
285             if device.ServiceData:
286                 for i in device.ServiceData:
287                     if uuid.UUID(str(i)) in uuids:
288                         found.append(device)
289                         break
290         return found
291
292     def clear_adapter(self):
293         devices = [
294             BluezDbusDevice(p["object"], self.bluez, self.bus, self.logger)
295             for p in get_bluez_objects(
296                 self.bluez, self.bus, DEVICE_INTERFACE, self.path
297             )
298         ]
299         for device in devices:
300             try:
301                 if device.Connected:
302                     device.device_bg_connect(False)
303                 self.adapter.RemoveDevice(device.device.object_path)
304             except Exception as ex:
305                 pass
306
307
308 class BluezDbusDevice:
309     def __init__(self, bluez_obj, bluez, bus, logger=None):
310         self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
311         self.object = bluez_obj
312         self.device = dbus.Interface(bluez_obj, DEVICE_INTERFACE)
313         self.device_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
314         self.path = self.device.object_path
315         self.device_event = threading.Event()
316         if self.Name:
317             try:
318                 self.device_id = uuid.uuid3(uuid.NAMESPACE_DNS, self.Name)
319             except UnicodeDecodeError:
320                 self.device_id = uuid.uuid3(
321                     uuid.NAMESPACE_DNS, self.Name.encode("utf-8")
322                 )
323         else:
324             self.device_id = uuid.uuid4()
325         self.bluez = bluez
326         self.bus = bus
327         self.signalReceiver = None
328         self.path = self.device.object_path
329
330     def __del__(self):
331         self.destroy()
332
333     def destroy(self):
334         self.logger.debug("destroy device")
335         self.device_unregister_signal()
336         self.device = None
337         self.device_properties = None
338         self.device_event = None
339         self.device_id = None
340         self.bluez = None
341         self.bus = None
342         self.object = None
343         self.signalReceiver = None
344
345     def device_register_signal(self):
346         if self.signalReceiver is None:
347             self.logger.debug("add device signal")
348             self.signalReceiver = self.bus.add_signal_receiver(
349                 self.device_on_prop_changed_cb,
350                 bus_name=BLUEZ_NAME,
351                 dbus_interface=DBUS_PROPERTIES,
352                 signal_name="PropertiesChanged",
353                 path=self.path,
354             )
355
356     def device_unregister_signal(self):
357         if self.signalReceiver is not None:
358             self.logger.debug("remove device signal")
359             self.bus.remove_signal_receiver(
360                 self.signalReceiver,
361                 signal_name="PropertiesChanged",
362                 dbus_interface=DBUS_PROPERTIES,
363             )
364
365     def device_on_prop_changed_cb(
366         self, interface, changed_properties, invalidated_properties
367     ):
368         if len(changed_properties) == 0:
369             self.logger.debug("changed_properties is empty")
370             return
371
372         if len(invalidated_properties) > 0:
373             self.logger.debug(
374                 "invalidated_properties is not empty %s" % str(invalidated_properties)
375             )
376             return
377
378         if interface == DEVICE_INTERFACE:
379             if "Connected" in changed_properties:
380                 self.device_event.set()
381
382     def device_bg_connect(self, enable):
383         time.sleep(BLE_SCAN_CONNECT_GUARD_SEC)
384         action_flag = False
385         self.device_event.clear()
386         try:
387             if enable:
388                 if not self.Connected:
389                     action_flag = True
390                     self.device.Connect()
391                     self.logger.info("BLE connecting")
392                 else:
393                     self.logger.info("BLE has connected")
394             else:
395                 if self.Connected:
396                     action_flag = True
397                     self.device.Disconnect()
398                     self.logger.info("BLE disconnected")
399                 else:
400                     self.logger.info("BLE has disconnected")
401             if action_flag:
402                 if not self.device_event.wait(BLE_STATUS_TRANSITION_TIMEOUT_SEC):
403                     if enable:
404                         self.logger.info("BLE connect error")
405                     else:
406                         self.logger.info("BLE disconnect error")
407             self.device_event.clear()
408         except dbus.exceptions.DBusException as ex:
409             self.device_event.clear()
410             self.logger.info(str(ex))
411         except Exception as ex:
412             self.logger.debug(traceback.format_exc())
413
414     def service_discover(self, gatt_dic):
415         self.logger.info("Discovering services")
416         try:
417             expired = time.time() + BLE_SERVICE_DISCOVERY_TIMEOUT_SEC
418             while time.time() < expired:
419                 if self.ServicesResolved:
420                     services = [
421                         BluezDbusGattService(
422                             p["object"], self.bluez, self.bus, self.logger
423                         )
424                         for p in get_bluez_objects(
425                             self.bluez, self.bus, SERVICE_INTERFACE, self.path
426                         )
427                     ]
428                     for service in services:
429                         if service.uuid in gatt_dic["services"]:
430                             self.logger.info("Service discovering success")
431                             return service
432                 time.sleep(BLE_IDLE_DELTA)
433             self.logger.error("Service discovering fail")
434             return None
435         except dbus.exceptions.DBusException as ex:
436             self.logger.debug(str(ex))
437             return None
438         except Exception as ex:
439             self.logger.debug(traceback.format_exc())
440             return None
441
442     @property
443     def uuids(self):
444         try:
445             uuids = self.device_properties.Get(DEVICE_INTERFACE, "UUIDs")
446             uuid_result = []
447             for i in uuids:
448                 if len(str(i)) == 4:
449                     uuid_normal = "0000%s-0000-0000-0000-000000000000" % i
450                 else:
451                     uuid_normal = i
452                 uuid_result.append(uuid.UUID(str(uuid_normal)))
453             return uuid_result
454         except dbus.exceptions.DBusException as ex:
455             self.logger.debug(str(ex))
456             return None
457         except Exception as ex:
458             self.logger.debug(traceback.format_exc())
459             return None
460
461     @property
462     def Address(self):
463         try:
464             return self.device_properties.Get(DEVICE_INTERFACE, "Address")
465         except dbus.exceptions.DBusException as ex:
466             self.logger.debug(str(ex))
467             return None
468         except Exception as ex:
469             self.logger.debug(traceback.format_exc())
470             return None
471
472     @property
473     def Name(self):
474         try:
475             name = self.device_properties.Get(DEVICE_INTERFACE, "Name")
476             return name
477         except dbus.exceptions.DBusException as ex:
478             self.logger.debug(str(ex))
479             return None
480         except Exception as ex:
481             self.logger.debug(traceback.format_exc())
482             return None
483
484     @property
485     def Connected(self):
486         try:
487             result = self.device_properties.Get(DEVICE_INTERFACE, "Connected")
488             return bool(result)
489         except dbus.exceptions.DBusException as ex:
490             self.logger.debug(str(ex))
491             return False
492         except Exception as ex:
493             self.logger.debug(traceback.format_exc())
494             return False
495
496     @property
497     def TxPower(self):
498         try:
499             return self.device_properties.Get(DEVICE_INTERFACE, "TxPower")
500         except dbus.exceptions.DBusException as ex:
501             self.logger.debug(str(ex))
502             return None
503         except Exception as ex:
504             self.logger.debug(traceback.format_exc())
505             return None
506
507     @property
508     def RSSI(self):
509         try:
510             result = self.device_properties.Get(DEVICE_INTERFACE, "RSSI")
511             return result
512         except dbus.exceptions.DBusException as ex:
513             self.logger.debug(str(ex))
514             return None
515         except Exception as ex:
516             self.logger.debug(traceback.format_exc())
517             return None
518
519     @property
520     def Adapter(self):
521         try:
522             return self.device_properties.Get(DEVICE_INTERFACE, "Adapter")
523         except dbus.exceptions.DBusException as ex:
524             self.logger.debug(str(ex))
525             return None
526         except Exception as ex:
527             self.logger.debug(traceback.format_exc())
528             return None
529
530     @property
531     def ServiceData(self):
532         try:
533             return self.device_properties.Get(DEVICE_INTERFACE, "ServiceData")
534         except dbus.exceptions.DBusException as ex:
535             self.logger.debug(str(ex))
536             return None
537         except Exception as ex:
538             self.logger.debug(traceback.format_exc())
539             return None
540
541     @property
542     def ServicesResolved(self):
543         try:
544             result = self.device_properties.Get(DEVICE_INTERFACE, "ServicesResolved")
545             return bool(result)
546         except dbus.exceptions.DBusException as ex:
547             self.logger.debug(str(ex))
548             return False
549         except Exception as ex:
550             self.logger.debug(traceback.format_exc())
551             return False
552
553
554 class BluezDbusGattService:
555     def __init__(self, bluez_obj, bluez, bus, logger=None):
556         self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
557         self.object = bluez_obj
558         self.service = dbus.Interface(bluez_obj, SERVICE_INTERFACE)
559         self.service_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
560         self.bluez = bluez
561         self.bus = bus
562         self.path = self.service.object_path
563
564     def __del__(self):
565         self.destroy()
566
567     def destroy(self):
568         self.logger.debug("destroy GattService")
569         self.service = None
570         self.service_properties = None
571         self.bluez = None
572         self.bus = None
573         self.object = None
574         self.path = None
575
576     @property
577     def uuid(self):
578         try:
579             result = uuid.UUID(
580                 str(self.service_properties.Get(SERVICE_INTERFACE, "UUID"))
581             )
582             return result
583         except dbus.exceptions.DBusException as ex:
584             self.logger.debug(str(ex))
585             return None
586         except Exception as ex:
587             self.logger.debug(traceback.format_exc())
588             return None
589
590     @property
591     def Primary(self):
592         try:
593             result = bool(self.service_properties.Get(SERVICE_INTERFACE, "Primary"))
594             return result
595         except dbus.exceptions.DBusException as ex:
596             self.logger.debug(str(ex))
597             return False
598         except Exception as ex:
599             self.logger.debug(traceback.format_exc())
600             return False
601
602     @property
603     def Device(self):
604         try:
605             result = self.service_properties.Get(SERVICE_INTERFACE, "Device")
606             return result
607         except dbus.exceptions.DBusException as ex:
608             self.logger.debug(str(ex))
609             return None
610         except Exception as ex:
611             self.logger.debug(traceback.format_exc())
612             return None
613
614     def find_characteristic(self, uuid):
615         try:
616             expired = time.time() + BLE_CHAR_DISCOVERY_TIMEOUT_SEC
617             while time.time() < expired:
618                 characteristics = [
619                     BluezDbusGattCharacteristic(
620                         p["object"], self.bluez, self.bus, self.logger
621                     )
622                     for p in get_bluez_objects(
623                         self.bluez, self.bus, CHARACTERISTIC_INTERFACE, self.path
624                     )
625                 ]
626                 for characteristic in characteristics:
627                     if characteristic.uuid == uuid:
628                         return characteristic
629                 time.sleep(BLE_IDLE_DELTA)
630             self.logger.error("Char discovering fail")
631             return None
632         except dbus.exceptions.DBusException as ex:
633             self.logger.debug(str(ex))
634             return None
635         except Exception as ex:
636             self.logger.debug(traceback.format_exc())
637             return None
638
639
640 class BluezDbusGattCharacteristic:
641     def __init__(self, bluez_obj, bluez, bus, logger=None):
642         self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
643         self.object = bluez_obj
644         self.characteristic = dbus.Interface(bluez_obj, CHARACTERISTIC_INTERFACE)
645         self.characteristic_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
646         self.received = None
647         self.path = self.characteristic.object_path
648         self.bluez = bluez
649         self.bus = bus
650         self.signalReceiver = None
651
652     def __del__(self):
653         self.destroy()
654
655     def destroy(self):
656         self.logger.debug("destroy GattCharacteristic")
657         self.gattCharacteristic_unregister_signal()
658         self.characteristic = None
659         self.object = None
660         self.characteristic_properties = None
661         self.received = None
662         self.bluez = None
663         self.bus = None
664         self.path = None
665         self.signalReceiver = None
666
667     def gattCharacteristic_register_signal(self):
668         if not self.signalReceiver:
669             self.logger.debug("add GattCharacteristic signal")
670             self.signalReceiver = self.bus.add_signal_receiver(
671                 self.gatt_on_characteristic_changed_cb,
672                 bus_name=BLUEZ_NAME,
673                 dbus_interface=DBUS_PROPERTIES,
674                 signal_name="PropertiesChanged",
675                 path=self.path,
676             )
677
678     def gattCharacteristic_unregister_signal(self):
679         if self.signalReceiver:
680             self.logger.debug("remove GattCharacteristic signal")
681
682             self.bus.remove_signal_receiver(
683                 self.signalReceiver,
684                 bus_name=BLUEZ_NAME,
685                 signal_name="PropertiesChanged",
686                 dbus_interface=DBUS_PROPERTIES,
687                 path=self.path,
688             )
689             self.signalReceiver = None
690
691     def gatt_on_characteristic_changed_cb(
692         self, interface, changed_properties, invalidated_properties
693     ):
694         self.logger.debug(
695             "property change in" + str(self.characteristic) + str(changed_properties)
696         )
697
698         if len(changed_properties) == 0:
699             return
700
701         if len(invalidated_properties) > 0:
702             return
703
704         if interface == CHARACTERISTIC_INTERFACE:
705             if "Value" in changed_properties:
706                 if self.received:
707                     self.received(changed_properties["Value"])
708
709     def WriteValue(self, value, options, reply_handler, error_handler, timeout):
710         try:
711             self.characteristic.WriteValue(
712                 value,
713                 options,
714                 reply_handler=reply_handler,
715                 error_handler=error_handler,
716                 timeout=timeout,
717             )
718         except dbus.exceptions.DBusException as ex:
719             self.logger.debug(str(ex))
720         except Exception as ex:
721             self.logger.debug(traceback.format_exc())
722
723     @property
724     def uuid(self):
725         try:
726             result = uuid.UUID(
727                 str(
728                     self.characteristic_properties.Get(CHARACTERISTIC_INTERFACE, "UUID")
729                 )
730             )
731             return result
732         except dbus.exceptions.DBusException as ex:
733             self.logger.debug(str(ex))
734             return None
735         except Exception as ex:
736             self.logger.debug(traceback.format_exc())
737             return None
738
739     def StartNotify(self, cbfunct, reply_handler, error_handler, timeout):
740         try:
741             if not cbfunct:
742                 self.logger.info("please provide the notify callback function")
743             self.received = cbfunct
744             self.gattCharacteristic_register_signal()
745             self.characteristic.StartNotify(
746                 reply_handler=reply_handler,
747                 error_handler=error_handler,
748                 timeout=timeout,
749             )
750         except dbus.exceptions.DBusException as ex:
751             self.logger.debug(str(ex))
752         except Exception as ex:
753             self.logger.debug(traceback.format_exc())
754
755     def StopNotify(self, reply_handler, error_handler, timeout):
756         try:
757             self.logger.debug("stopping notifying")
758             self.characteristic.StopNotify(
759                 reply_handler=reply_handler,
760                 error_handler=error_handler,
761                 timeout=timeout,
762             )
763             self.gattCharacteristic_unregister_signal()
764             self.received = None
765         except dbus.exceptions.DBusException as ex:
766             self.logger.debug(str(ex))
767         except Exception as ex:
768             self.logger.debug(traceback.format_exc())
769
770     @property
771     def Notifying(self):
772         try:
773             result = self.characteristic_properties.Get(
774                 CHARACTERISTIC_INTERFACE, "Notifying"
775             )
776             return bool(result)
777         except dbus.exceptions.DBusException as ex:
778             self.logger.debug(str(ex))
779             return False
780         except Exception as ex:
781             self.logger.debug(traceback.format_exc())
782             return False
783
784
785 class BluezManager(ChipBleBase):
786     def __init__(self, devMgr, logger=None):
787         if logger:
788             self.logger = logger
789         else:
790             self.logger = logging.getLogger("ChipBLEMgr")
791             logging.basicConfig(
792                 level=logging.INFO,
793                 format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
794             )
795         self.scan_quiet = False
796         self.peripheral_list = []
797         self.device_uuid_list = []
798         self.chip_queue = queue.Queue()
799         self.Gmainloop = None
800         self.daemon_thread = None
801         self.adapter = None
802         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
803         GObject.threads_init()
804         dbus.mainloop.glib.threads_init()
805         self.bus = dbus.SystemBus()
806         self.bluez = dbus.Interface(
807             self.bus.get_object(BLUEZ_NAME, "/"), "org.freedesktop.DBus.ObjectManager"
808         )
809         self.target = None
810         self.service = None
811         self.orig_input_hook = None
812         self.hookFuncPtr = None
813         self.connect_state = False
814         self.tx = None
815         self.rx = None
816         self.setInputHook(self.readlineCB)
817         self.devMgr = devMgr
818         self.devMgr.SetBlockingCB(self.devMgrCB)
819
820     def __del__(self):
821         self.disconnect()
822         self.setInputHook(self.orig_input_hook)
823
824     def ble_adapter_select(self, identifier=None):
825         if self.adapter:
826             self.adapter.destroy()
827             self.adapter = None
828         self.adapter = self.get_adapter_by_addr(identifier)
829         self.adapter.adapter_register_signal()
830         self.adapter.Powered(False)
831         self.adapter.Powered(True)
832
833     def get_adapters(self):
834         return [
835             BluezDbusAdapter(p["object"], self.bluez, self.bus, self.logger)
836             for p in get_bluez_objects(
837                 self.bluez, self.bus, ADAPTER_INTERFACE, "/org/bluez"
838             )
839         ]
840
841
842     def ble_adapter_print(self):
843         try:
844             adapters = [
845                 BluezDbusAdapter(p["object"], self.bluez, self.bus, self.logger)
846                 for p in get_bluez_objects(
847                     self.bluez, self.bus, ADAPTER_INTERFACE, "/org/bluez"
848                 )
849             ]
850             for i in range(len(adapters)):
851                 self.logger.info("AdapterName: %s   AdapterAddress: %s" % (adapters[i].path.replace("/org/bluez/", ""), adapters[i].Address))
852         except dbus.exceptions.DBusException as ex:
853             self.logger.debug(str(ex))
854
855     def get_adapter_by_addr(self, identifier):
856         try:
857             adapters = [
858                 BluezDbusAdapter(p["object"], self.bluez, self.bus, self.logger)
859                 for p in get_bluez_objects(
860                     self.bluez, self.bus, ADAPTER_INTERFACE, "/org/bluez"
861                 )
862             ]
863             if identifier is None:
864                 return adapters[0]
865             if len(adapters) > 0:
866                 for adapter in adapters:
867                     if str(adapter.Address).upper() == str(identifier).upper() or "/org/bluez/{}".format(identifier) == str(adapter.path):
868                         return adapter
869             self.logger.info(
870                 "adapter %s cannot be found, expect the ble mac address" % (identifier)
871             )
872             return None
873
874         except dbus.exceptions.DBusException as ex:
875             self.logger.debug(str(ex))
876
877     def runLoopUntil(self, target=None, **kwargs):
878         if target:
879             self.daemon_thread = threading.Thread(
880                 target=self.running_thread, args=(target, kwargs)
881             )
882             self.daemon_thread.daemon = True
883             self.daemon_thread.start()
884
885         try:
886             self.Gmainloop = GObject.MainLoop()
887             self.Gmainloop.run()
888         except KeyboardInterrupt:
889             self.Gmainloop.quit()
890             sys.exit(1)
891
892     def running_thread(self, target, kwargs):
893         try:
894             while not self.Gmainloop or not self.Gmainloop.is_running():
895                 time.sleep(0.00001)
896             target(**kwargs)
897         except Exception as err:
898             traceback.print_exc()
899         finally:
900             self.Gmainloop.quit()
901
902     def setInputHook(self, hookFunc):
903         """Set the PyOS_InputHook to call the specific function."""
904         hookFunctionType = CFUNCTYPE(None)
905         self.hookFuncPtr = hookFunctionType(hookFunc)
906         pyos_inputhook_ptr = c_void_p.in_dll(pythonapi, "PyOS_InputHook")
907         # save the original so that on del we can revert it back to the way it was.
908         self.orig_input_hook = cast(pyos_inputhook_ptr.value, PYFUNCTYPE(c_int))
909         # set the new hook. readLine will call this periodically as it polls for input.
910         pyos_inputhook_ptr.value = cast(self.hookFuncPtr, c_void_p).value
911
912     def runIdleLoop(self, **kwargs):
913         time.sleep(0)
914
915     def devMgrCB(self):
916         self.runLoopUntil(self.runIdleLoop)
917
918     def readlineCB(self):
919         self.runLoopUntil(self.runIdleLoop)
920
921         if self.orig_input_hook:
922             self.orig_input_hook()
923
924     def dump_scan_result(self, device):
925         self.logger.info("{0:<16}= {1}".format("Name", device.Name))
926         self.logger.info("{0:<16}= {1}".format("ID", device.device_id))
927         self.logger.info("{0:<16}= {1}".format("RSSI", device.RSSI))
928         self.logger.info("{0:<16}= {1}".format("Address", device.Address))
929
930         devIdInfo = self.get_peripheral_devIdInfo(device)
931         if devIdInfo != None:
932             self.logger.info("{0:<16}= {1}".format("Pairing State", devIdInfo.pairingState))
933             self.logger.info("{0:<16}= {1}".format("Discriminator", devIdInfo.discriminator))
934             self.logger.info("{0:<16}= {1}".format("Vendor Id", devIdInfo.vendorId))
935             self.logger.info("{0:<16}= {1}".format("Product Id", devIdInfo.productId))
936
937         if device.ServiceData:
938             for advuuid in device.ServiceData:
939                 self.logger.info("{0:<16}= {1}".format("Adv UUID", str(advuuid)))
940                 self.logger.info("{0:<16}= {1}".format("Adv Data", bytes(device.ServiceData[advuuid]).hex()))
941         else:
942             self.logger.info("")
943         self.logger.info("")
944
945     def scan_bg_implementation(self, **kwargs):
946         self.adapter.clear_adapter()
947         with self.chip_queue.mutex:
948             self.chip_queue.queue.clear()
949         self.adapter.adapter_bg_scan(True)
950         found = False
951         identifier = kwargs["identifier"]
952         timeout = kwargs["timeout"] + time.time()
953         self.device_uuid_list = []
954         self.peripheral_list = []
955
956         while time.time() < timeout:
957             scanned_peripheral_list = self.adapter.find_devices(
958                 [
959                     chip_service,
960                     chip_service_short,
961                     chromecast_setup_service,
962                     chromecast_setup_service_short,
963                 ]
964             )
965             for device in scanned_peripheral_list:
966                 try:
967                     if not self.scan_quiet and device.Address not in self.device_uuid_list:
968                         # display all scanned results
969                         self.device_uuid_list.append(device.Address)
970                         self.peripheral_list.append(device)
971                         self.dump_scan_result(device)
972                     devIdInfo = self.get_peripheral_devIdInfo(device)
973                     if not devIdInfo:
974                         # Not a chip device
975                         continue
976                     if identifier and (device.Name == identifier or str(device.Address).upper() == str(
977                         identifier.upper()
978                     ) or str(devIdInfo.discriminator) == identifier):
979                         if self.scan_quiet:
980                             # only display the scanned target's info when quiet
981                             self.dump_scan_result(device)
982                         self.target = device
983                         found = True
984                         break
985                 except Exception:
986                     traceback.print_exc()
987             if found:
988                 break
989
990             time.sleep(BLE_IDLE_DELTA)
991         self.adapter.adapter_bg_scan(False)
992
993     def scan(self, line):
994         args = self.ParseInputLine(line, "scan")
995         if not args:
996             return False
997         self.target = None
998         if not self.adapter:
999             self.logger.info("use default adapter")
1000             self.ble_adapter_select()
1001         del self.peripheral_list[:]
1002         self.scan_quiet = args[1]
1003         self.runLoopUntil(
1004             self.scan_bg_implementation, timeout=args[0], identifier=args[2]
1005         )
1006         return True
1007
1008     def get_peripheral_devIdInfo(self, peripheral):
1009         if not peripheral.ServiceData:
1010             return None
1011         for advuuid in peripheral.ServiceData:
1012             if str(advuuid).lower() == str(chip_service).lower():
1013                 return ParseServiceData(bytes(peripheral.ServiceData[advuuid]))
1014         return None
1015
1016     def ble_debug_log(self, line):
1017         args = self.ParseInputLine(line)
1018         if int(args[0]) == 1:
1019             self.logger.setLevel(logging.DEBUG)
1020             self.logger.debug("current logging level is debug")
1021         else:
1022             self.logger.setLevel(logging.INFO)
1023             self.logger.info("current logging level is info")
1024         return True
1025
1026     def CloseBle(self, connObj):
1027         """ Called by Chip to close the BLE connection."""
1028         # Workaround: comment out disconnect because of hang when close, plz call disconnect explicitly after close
1029         # Need to fix it
1030         # self.disconnect()
1031         if self.devMgr:
1032             dcEvent = BleDisconnectEvent(BLE_ERROR_REMOTE_DEVICE_DISCONNECTED)
1033             self.chip_queue.put(dcEvent)
1034             self.devMgr.DriveBleIO()
1035         return True