1# Lint as: python2, python3 2# Copyright 2020 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Advertisement Monitor Test Application.""" 7 8import dbus 9import dbus.mainloop.glib 10import dbus.service 11# AU tests use ToT client code, but ToT -3 client version. 12try: 13 from gi.repository import GObject 14except ImportError: 15 import gobject as GObject 16import logging 17 18from multiprocessing import Process, Pipe 19from threading import Thread 20 21DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' 22DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' 23 24BLUEZ_SERVICE_NAME = 'org.bluez' 25 26ADV_MONITOR_MANAGER_IFACE = 'org.bluez.AdvertisementMonitorManager1' 27ADV_MONITOR_IFACE = 'org.bluez.AdvertisementMonitor1' 28ADV_MONITOR_APP_BASE_PATH = '/org/bluez/adv_monitor_app' 29 30 31class AdvMonitor(dbus.service.Object): 32 """A monitor object. 33 34 This class exposes a dbus monitor object along with its properties 35 and methods. 36 37 More information can be found at BlueZ documentation: 38 doc/advertisement-monitor-api.txt 39 40 """ 41 42 # Refer doc/advertisement-monitor-api.txt for more info about unset values. 43 UNSET_RSSI = 127 44 UNSET_TIMEOUT = 0 45 UNSET_SAMPLING_PERIOD = 256 46 47 # Indexes of the Monitor object parameters in a monitor data list. 48 MONITOR_TYPE = 0 49 RSSI_FILTER = 1 50 PATTERNS = 2 51 52 # Indexes of the RSSI filter parameters in a monitor data list. 53 RSSI_H_THRESH = 0 54 RSSI_H_TIMEOUT = 1 55 RSSI_L_THRESH = 2 56 RSSI_L_TIMEOUT = 3 57 SAMPLING_PERIOD = 4 58 59 # Indexes of the Patterns filter parameters in a monitor data list. 60 PATTERN_START_POS = 0 61 PATTERN_AD_TYPE = 1 62 PATTERN_DATA = 2 63 64 def __init__(self, bus, app_path, monitor_id, monitor_data): 65 """Construction of a Monitor object. 66 67 @param bus: a dbus system bus. 68 @param app_path: application path. 69 @param monitor_id: unique monitor id. 70 71 """ 72 self.path = app_path + '/monitor' + str(monitor_id) 73 self.bus = bus 74 75 self.events = dict() 76 self.events['Activate'] = 0 77 self.events['Release'] = 0 78 self.events['DeviceFound'] = 0 79 self.events['DeviceLost'] = 0 80 81 self.target_devices = [] 82 83 self._set_type(monitor_data[self.MONITOR_TYPE]) 84 self._set_rssi(monitor_data[self.RSSI_FILTER]) 85 self._set_patterns(monitor_data[self.PATTERNS]) 86 87 super(AdvMonitor, self).__init__(self.bus, self.path) 88 89 90 def get_path(self): 91 """Get the dbus object path of the monitor. 92 93 @returns: the monitor object path. 94 95 """ 96 return dbus.ObjectPath(self.path) 97 98 99 def get_properties(self): 100 """Get the properties dictionary of the monitor. 101 102 @returns: the monitor properties dictionary. 103 104 """ 105 properties = dict() 106 properties['Type'] = dbus.String(self.monitor_type) 107 if self.rssi_h_thresh != self.UNSET_RSSI: 108 properties['RSSIHighThreshold'] = dbus.Int16(self.rssi_h_thresh) 109 if self.rssi_h_timeout != self.UNSET_TIMEOUT: 110 properties['RSSIHighTimeout'] = dbus.UInt16(self.rssi_h_timeout) 111 if self.rssi_l_thresh != self.UNSET_RSSI: 112 properties['RSSILowThreshold'] = dbus.Int16(self.rssi_l_thresh) 113 if self.rssi_l_timeout != self.UNSET_TIMEOUT: 114 properties['RSSILowTimeout'] = dbus.UInt16(self.rssi_l_timeout) 115 if self.sampling_period != self.UNSET_SAMPLING_PERIOD: 116 properties['RSSISamplingPeriod'] = dbus.UInt16(self.sampling_period) 117 properties['Patterns'] = dbus.Array(self.patterns, signature='(yyay)') 118 return {ADV_MONITOR_IFACE: properties} 119 120 121 def _set_type(self, monitor_type): 122 """Set the monitor type. 123 124 @param monitor_type: the type of a monitor. 125 126 """ 127 self.monitor_type = monitor_type 128 129 130 def _set_rssi(self, rssi): 131 """Set the RSSI filter values. 132 133 @param rssi: the list of rssi threshold and timeout values. 134 135 """ 136 self.rssi_h_thresh = rssi[self.RSSI_H_THRESH] 137 self.rssi_h_timeout = rssi[self.RSSI_H_TIMEOUT] 138 self.rssi_l_thresh = rssi[self.RSSI_L_THRESH] 139 self.rssi_l_timeout = rssi[self.RSSI_L_TIMEOUT] 140 self.sampling_period = rssi[self.SAMPLING_PERIOD] 141 142 143 def _set_patterns(self, patterns): 144 """Set the content filter patterns. 145 146 @param patterns: the list of start position, ad type and patterns. 147 148 """ 149 self.patterns = [] 150 for pattern in patterns: 151 start_pos = dbus.Byte(pattern[self.PATTERN_START_POS]) 152 ad_type = dbus.Byte(pattern[self.PATTERN_AD_TYPE]) 153 ad_data = [] 154 for byte in pattern[self.PATTERN_DATA]: 155 ad_data.append(dbus.Byte(byte)) 156 adv_pattern = dbus.Struct((start_pos, ad_type, ad_data), 157 signature='yyay') 158 self.patterns.append(adv_pattern) 159 160 161 def remove_monitor(self): 162 """Remove the monitor object. 163 164 Invoke the dbus method to remove current monitor object from the 165 connection. 166 167 """ 168 self.remove_from_connection() 169 170 171 def _update_event_count(self, event): 172 """Update the event count. 173 174 @param event: name of the event. 175 176 """ 177 self.events[event] += 1 178 179 180 def get_event_count(self, event): 181 """Read the event count. 182 183 @param event: name of the specific event or 'All' for all events. 184 185 @returns: count of the specific event or dict of counts of all events. 186 187 """ 188 if event == 'All': 189 return self.events 190 191 return self.events.get(event) 192 193 194 def reset_event_count(self, event): 195 """Reset the event count. 196 197 @param event: name of the specific event or 'All' for all events. 198 199 @returns: True on success, False otherwise. 200 201 """ 202 if event == 'All': 203 for event_key in self.events: 204 self.events[event_key] = 0 205 return True 206 207 if event in self.events: 208 self.events[event] = 0 209 return True 210 211 return False 212 213 214 def set_target_devices(self, devices): 215 """Set the target devices to the given monitor. 216 217 DeviceFound and DeviceLost will only be counted if it is triggered by a 218 target device. 219 220 @param devices: a list of devices in dbus object path 221 222 """ 223 self.target_devices = devices 224 225 @dbus.service.method(DBUS_PROP_IFACE, 226 in_signature='s', 227 out_signature='a{sv}') 228 def GetAll(self, interface): 229 """Get the properties dictionary of the monitor. 230 231 @param interface: the bluetooth dbus interface. 232 233 @returns: the monitor properties dictionary. 234 235 """ 236 logging.info('%s: %s GetAll', self.path, interface) 237 238 if interface != ADV_MONITOR_IFACE: 239 logging.error('%s: GetAll: Invalid arg %s', self.path, interface) 240 return {} 241 242 return self.get_properties()[ADV_MONITOR_IFACE] 243 244 245 @dbus.service.method(ADV_MONITOR_IFACE, 246 in_signature='', 247 out_signature='') 248 def Activate(self): 249 """The method callback at Activate.""" 250 logging.info('%s: Monitor Activated!', self.path) 251 self._update_event_count('Activate') 252 253 254 @dbus.service.method(ADV_MONITOR_IFACE, 255 in_signature='', 256 out_signature='') 257 def Release(self): 258 """The method callback at Release.""" 259 logging.info('%s: Monitor Released!', self.path) 260 self._update_event_count('Release') 261 262 263 @dbus.service.method(ADV_MONITOR_IFACE, 264 in_signature='o', 265 out_signature='') 266 def DeviceFound(self, device): 267 """The method callback at DeviceFound. 268 269 @param device: the dbus object path of the found device. 270 271 """ 272 logging.info('%s: %s Device Found!', self.path, device) 273 if device in self.target_devices: 274 self._update_event_count('DeviceFound') 275 else: 276 logging.debug('Found an uninteresting device: %s', device) 277 278 279 @dbus.service.method(ADV_MONITOR_IFACE, 280 in_signature='o', 281 out_signature='') 282 def DeviceLost(self, device): 283 """The method callback at DeviceLost. 284 285 @param device: the dbus object path of the lost device. 286 287 """ 288 logging.info('%s: %s Device Lost!', self.path, device) 289 if device in self.target_devices: 290 self._update_event_count('DeviceLost') 291 else: 292 logging.debug('Lost an uninteresting device: %s', device) 293 294 295class AdvMonitorApp(dbus.service.Object): 296 """The test application. 297 298 This class implements a test application to manage monitor objects. 299 300 """ 301 302 def __init__(self, bus, dbus_mainloop, advmon_manager, app_id): 303 """Construction of a test application object. 304 305 @param bus: a dbus system bus. 306 @param dbus_mainloop: an instance of mainloop. 307 @param advmon_manager: AdvertisementMonitorManager1 interface on 308 the adapter. 309 @param app_id: application id (to create application path). 310 311 """ 312 self.bus = bus 313 self.mainloop = dbus_mainloop 314 self.advmon_mgr = advmon_manager 315 self.app_path = ADV_MONITOR_APP_BASE_PATH + str(app_id) 316 317 self.monitors = dict() 318 319 super(AdvMonitorApp, self).__init__(self.bus, self.app_path) 320 321 322 def get_app_path(self): 323 """Get the dbus object path of the application. 324 325 @returns: the application path. 326 327 """ 328 return dbus.ObjectPath(self.app_path) 329 330 331 def add_monitor(self, monitor_data): 332 """Create a monitor object. 333 334 @param monitor_data: the list containing monitor type, RSSI filter 335 values and patterns. 336 337 @returns: monitor id, once the monitor is created. 338 339 """ 340 monitor_id = 0 341 while monitor_id in self.monitors: 342 monitor_id += 1 343 344 monitor = AdvMonitor(self.bus, self.app_path, monitor_id, monitor_data) 345 346 # Emit the InterfacesAdded signal once the Monitor object is created. 347 self.InterfacesAdded(monitor.get_path(), monitor.get_properties()) 348 349 self.monitors[monitor_id] = monitor 350 351 return monitor_id 352 353 354 def remove_monitor(self, monitor_id): 355 """Remove a monitor object based on the given monitor id. 356 357 @param monitor_id: the monitor id. 358 359 @returns: True on success, False otherwise. 360 361 """ 362 if monitor_id not in self.monitors: 363 return False 364 365 monitor = self.monitors[monitor_id] 366 367 # Emit the InterfacesRemoved signal before removing the Monitor object. 368 self.InterfacesRemoved(monitor.get_path(), 369 list(monitor.get_properties().keys())) 370 371 monitor.remove_monitor() 372 373 self.monitors.pop(monitor_id) 374 375 return True 376 377 378 def get_event_count(self, monitor_id, event): 379 """Read the count of a particular event on the given monitor. 380 381 @param monitor_id: the monitor id. 382 @param event: name of the specific event or 'All' for all events. 383 384 @returns: count of the specific event or dict of counts of all events. 385 386 """ 387 if monitor_id not in self.monitors: 388 return None 389 390 return self.monitors[monitor_id].get_event_count(event) 391 392 393 def reset_event_count(self, monitor_id, event): 394 """Reset the count of a particular event on the given monitor. 395 396 @param monitor_id: the monitor id. 397 @param event: name of the specific event or 'All' for all events. 398 399 @returns: True on success, False otherwise. 400 401 """ 402 if monitor_id not in self.monitors: 403 return False 404 405 return self.monitors[monitor_id].reset_event_count(event) 406 407 408 def set_target_devices(self, monitor_id, devices): 409 """Set the target devices to the given monitor. 410 411 DeviceFound and DeviceLost will only be counted if it is triggered by a 412 target device. 413 414 @param monitor_id: the monitor id. 415 @param devices: a list of devices in dbus object path 416 417 @returns: True on success, False otherwise. 418 """ 419 if monitor_id not in self.monitors: 420 return False 421 422 self.monitors[monitor_id].set_target_devices(devices) 423 return True 424 425 def _mainloop_thread(self): 426 """Run the dbus mainloop thread. 427 428 Callback methods on the monitor objects get invoked only when the 429 dbus mainloop is running. This thread starts when app is registered 430 and stops when app is unregistered. 431 432 """ 433 self.mainloop.run() # blocks until mainloop.quit() is called 434 435 436 def register_app(self): 437 """Register an advertisement monitor app. 438 439 @returns: True on success, False otherwise. 440 441 """ 442 if self.mainloop.is_running(): 443 self.mainloop.quit() 444 445 self.register_successful = False 446 447 def register_cb(): 448 """Handler when RegisterMonitor succeeded.""" 449 logging.info('%s: RegisterMonitor successful!', self.app_path) 450 self.register_successful = True 451 self.mainloop.quit() 452 453 def register_error_cb(error): 454 """Handler when RegisterMonitor failed.""" 455 logging.error('%s: RegisterMonitor failed: %s', self.app_path, 456 str(error)) 457 self.register_successful = False 458 self.mainloop.quit() 459 460 self.advmon_mgr.RegisterMonitor(self.get_app_path(), 461 reply_handler=register_cb, 462 error_handler=register_error_cb) 463 self.mainloop.run() # blocks until mainloop.quit() is called 464 465 # Start a background thread to run mainloop.run(). This is required for 466 # the bluetoothd to be able to invoke methods on the monitor object. 467 # Mark this thread as a daemon to make sure that the thread is killed 468 # in case the parent process dies unexpectedly. 469 t = Thread(target=self._mainloop_thread) 470 t.daemon = True 471 t.start() 472 473 return self.register_successful 474 475 476 def unregister_app(self): 477 """Unregister an advertisement monitor app. 478 479 @returns: True on success, False otherwise. 480 481 """ 482 if self.mainloop.is_running(): 483 self.mainloop.quit() 484 485 self.unregister_successful = False 486 487 def unregister_cb(): 488 """Handler when UnregisterMonitor succeeded.""" 489 logging.info('%s: UnregisterMonitor successful!', self.app_path) 490 self.unregister_successful = True 491 self.mainloop.quit() 492 493 def unregister_error_cb(error): 494 """Handler when UnregisterMonitor failed.""" 495 logging.error('%s: UnregisterMonitor failed: %s', self.app_path, 496 str(error)) 497 self.unregister_successful = False 498 self.mainloop.quit() 499 500 self.advmon_mgr.UnregisterMonitor(self.get_app_path(), 501 reply_handler=unregister_cb, 502 error_handler=unregister_error_cb) 503 self.mainloop.run() # blocks until mainloop.quit() is called 504 505 return self.unregister_successful 506 507 508 @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}') 509 def GetManagedObjects(self): 510 """Get the list of managed monitor objects. 511 512 @returns: the list of managed objects and their properties. 513 514 """ 515 logging.info('%s: GetManagedObjects', self.app_path) 516 517 objects = dict() 518 for monitor_id in self.monitors: 519 monitor = self.monitors[monitor_id] 520 objects[monitor.get_path()] = monitor.get_properties() 521 522 return objects 523 524 525 @dbus.service.signal(DBUS_OM_IFACE, signature='oa{sa{sv}}') 526 def InterfacesAdded(self, object_path, interfaces_and_properties): 527 """Emit the InterfacesAdded signal for a given monitor object. 528 529 Invoking this method emits the InterfacesAdded signal, 530 nothing needs to be done here. 531 532 @param object_path: the dbus object path of a monitor. 533 @param interfaces_and_properties: the monitor properties dictionary. 534 535 """ 536 return 537 538 539 @dbus.service.signal(DBUS_OM_IFACE, signature='oas') 540 def InterfacesRemoved(self, object_path, interfaces): 541 """Emit the InterfacesRemoved signal for a given monitor object. 542 543 Invoking this method emits the InterfacesRemoved signal, 544 nothing needs to be done here. 545 546 @param object_path: the dbus object path of a monitor. 547 @param interfaces: the list of monitor interfaces. 548 549 """ 550 return 551 552 553class AdvMonitorAppMgr(): 554 """The app manager for Advertisement Monitor Test Apps. 555 556 This class manages instances of multiple advertisement monitor test 557 applications. 558 559 """ 560 561 # List of commands used by AdvMonitor AppMgr, AppMgr-helper process and 562 # AdvMonitor Test Application for communication between each other. 563 CMD_EXIT_HELPER = 0 564 CMD_CREATE_APP = 1 565 CMD_EXIT_APP = 2 566 CMD_KILL_APP = 3 567 CMD_REGISTER_APP = 4 568 CMD_UNREGISTER_APP = 5 569 CMD_ADD_MONITOR = 6 570 CMD_REMOVE_MONITOR = 7 571 CMD_GET_EVENT_COUNT = 8 572 CMD_RESET_EVENT_COUNT = 9 573 CMD_SET_TARGET_DEVICES = 10 574 575 def __init__(self): 576 """Construction of applications manager object.""" 577 578 # Due to a limitation of python, it is not possible to fork a new 579 # process once any dbus connections are established. So, create a 580 # helper process before making any dbus connections. This helper 581 # process can be used to create more processes on demand. 582 parent_conn, child_conn = Pipe() 583 p = Process(target=self._appmgr_helper, args=(child_conn,)) 584 p.start() 585 586 self._helper_proc = p 587 self._helper_conn = parent_conn 588 self.apps = [] 589 590 591 def _appmgr_helper(self, appmgr_conn): 592 """AppMgr helper process. 593 594 This process is used to create new instances of the AdvMonitor Test 595 Application on demand and acts as a communication bridge between the 596 AppMgr and test applications. 597 598 @param appmgr_conn: an object of AppMgr connection pipe. 599 600 """ 601 app_conns = dict() 602 603 done = False 604 while not done: 605 cmd, app_id, data = appmgr_conn.recv() 606 ret = None 607 608 if cmd == self.CMD_EXIT_HELPER: 609 # Terminate any outstanding test application instances before 610 # exiting the helper process. 611 for app_id in app_conns: 612 p, app_conn = app_conns[app_id] 613 if p.is_alive(): 614 # Try to exit the app gracefully first, terminate if it 615 # doesn't work. 616 app_conn.send((self.CMD_EXIT_APP, None)) 617 if not app_conn.recv() or p.is_alive(): 618 p.terminate() 619 p.join() # wait for test app to terminate 620 done = True 621 ret = True 622 623 elif cmd == self.CMD_CREATE_APP: 624 if app_id not in app_conns: 625 parent_conn, child_conn = Pipe() 626 p = Process(target=self._testapp_main, 627 args=(child_conn, app_id,)) 628 p.start() 629 630 app_conns[app_id] = (p, parent_conn) 631 ret = app_id 632 633 elif cmd == self.CMD_KILL_APP: 634 if app_id in app_conns: 635 p, _ = app_conns[app_id] 636 if p.is_alive(): 637 p.terminate() 638 p.join() # wait for test app to terminate 639 640 app_conns.pop(app_id) 641 ret = not p.is_alive() 642 643 else: 644 if app_id in app_conns: 645 p, app_conn = app_conns[app_id] 646 647 app_conn.send((cmd, data)) 648 ret = app_conn.recv() 649 650 if cmd == self.CMD_EXIT_APP: 651 p.join() # wait for test app to terminate 652 653 app_conns.pop(app_id) 654 ret = not p.is_alive() 655 656 appmgr_conn.send(ret) 657 658 659 def _testapp_main(self, helper_conn, app_id): 660 """AdvMonitor Test Application Process. 661 662 This process acts as a client application for AdvMonitor tests and used 663 to host AdvMonitor dbus objects. 664 665 @param helper_conn: an object of AppMgr-helper process connection pipe. 666 @param app_id: the app id of this test app process. 667 668 """ 669 # Initialize threads in GObject/dbus-glib before creating local threads. 670 GObject.threads_init() 671 dbus.mainloop.glib.threads_init() 672 673 # Arrange for the GObject main loop to be the default. 674 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 675 676 def get_advmon_mgr(bus): 677 """Finds the AdvMonitor Manager object exported by bluetoothd.""" 678 remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'), 679 DBUS_OM_IFACE) 680 objects = remote_om.GetManagedObjects() 681 682 for o, props in objects.items(): 683 if ADV_MONITOR_MANAGER_IFACE in props: 684 return dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, o), 685 ADV_MONITOR_MANAGER_IFACE) 686 return None 687 688 bus = dbus.SystemBus() 689 mainloop = GObject.MainLoop() 690 advmon_mgr = get_advmon_mgr(bus) 691 692 app = AdvMonitorApp(bus, mainloop, advmon_mgr, app_id) 693 694 done = False 695 while not done: 696 cmd, data = helper_conn.recv() 697 ret = None 698 699 if cmd == self.CMD_EXIT_APP: 700 done = True 701 ret = True 702 703 elif cmd == self.CMD_REGISTER_APP: 704 ret = app.register_app() 705 706 elif cmd == self.CMD_UNREGISTER_APP: 707 ret = app.unregister_app() 708 709 elif cmd == self.CMD_ADD_MONITOR: 710 ret = app.add_monitor(data) 711 712 elif cmd == self.CMD_REMOVE_MONITOR: 713 ret = app.remove_monitor(data) 714 715 elif cmd == self.CMD_GET_EVENT_COUNT: 716 ret = app.get_event_count(*data) 717 718 elif cmd == self.CMD_RESET_EVENT_COUNT: 719 ret = app.reset_event_count(*data) 720 721 elif cmd == self.CMD_SET_TARGET_DEVICES: 722 ret = app.set_target_devices(*data) 723 724 helper_conn.send(ret) 725 726 727 def _send_to_helper(self, cmd, app_id=None, data=None): 728 """Sends commands to the helper process. 729 730 @param cmd: command number from the above set of CMD_* commands. 731 @param app_id: the app id. 732 @param data: the command data. 733 734 @returns: outcome of the command returned by the helper process. 735 736 """ 737 if not self._helper_proc.is_alive(): 738 return None 739 740 self._helper_conn.send((cmd, app_id, data)) 741 return self._helper_conn.recv() 742 743 744 def create_app(self): 745 """Create an advertisement monitor app. 746 747 @returns: app id, once the app is created. 748 749 """ 750 app_id = 0 751 while app_id in self.apps: 752 app_id += 1 753 754 self.apps.append(app_id) 755 756 return self._send_to_helper(self.CMD_CREATE_APP, app_id) 757 758 759 def exit_app(self, app_id): 760 """Exit an advertisement monitor app. 761 762 @param app_id: the app id. 763 764 @returns: True on success, False otherwise. 765 766 """ 767 if app_id not in self.apps: 768 return False 769 770 self.apps.remove(app_id) 771 772 return self._send_to_helper(self.CMD_EXIT_APP, app_id) 773 774 775 def kill_app(self, app_id): 776 """Kill an advertisement monitor app by sending SIGKILL. 777 778 @param app_id: the app id. 779 780 @returns: True on success, False otherwise. 781 782 """ 783 if app_id not in self.apps: 784 return False 785 786 self.apps.remove(app_id) 787 788 return self._send_to_helper(self.CMD_KILL_APP, app_id) 789 790 791 def register_app(self, app_id): 792 """Register an advertisement monitor app. 793 794 @param app_id: the app id. 795 796 @returns: True on success, False otherwise. 797 798 """ 799 if app_id not in self.apps: 800 return False 801 802 return self._send_to_helper(self.CMD_REGISTER_APP, app_id) 803 804 805 def unregister_app(self, app_id): 806 """Unregister an advertisement monitor app. 807 808 @param app_id: the app id. 809 810 @returns: True on success, False otherwise. 811 812 """ 813 if app_id not in self.apps: 814 return False 815 816 return self._send_to_helper(self.CMD_UNREGISTER_APP, app_id) 817 818 819 def add_monitor(self, app_id, monitor_data): 820 """Create a monitor object. 821 822 @param app_id: the app id. 823 @param monitor_data: the list containing monitor type, RSSI filter 824 values and patterns. 825 826 @returns: monitor id, once the monitor is created, None otherwise. 827 828 """ 829 if app_id not in self.apps: 830 return None 831 832 return self._send_to_helper(self.CMD_ADD_MONITOR, app_id, monitor_data) 833 834 835 def remove_monitor(self, app_id, monitor_id): 836 """Remove a monitor object based on the given monitor id. 837 838 @param app_id: the app id. 839 @param monitor_id: the monitor id. 840 841 @returns: True on success, False otherwise. 842 843 """ 844 if app_id not in self.apps: 845 return False 846 847 return self._send_to_helper(self.CMD_REMOVE_MONITOR, app_id, monitor_id) 848 849 850 def get_event_count(self, app_id, monitor_id, event): 851 """Read the count of a particular event on the given monitor. 852 853 @param app_id: the app id. 854 @param monitor_id: the monitor id. 855 @param event: name of the specific event or 'All' for all events. 856 857 @returns: count of the specific event or dict of counts of all events. 858 859 """ 860 if app_id not in self.apps: 861 return None 862 863 return self._send_to_helper(self.CMD_GET_EVENT_COUNT, app_id, 864 (monitor_id, event)) 865 866 867 def reset_event_count(self, app_id, monitor_id, event): 868 """Reset the count of a particular event on the given monitor. 869 870 @param app_id: the app id. 871 @param monitor_id: the monitor id. 872 @param event: name of the specific event or 'All' for all events. 873 874 @returns: True on success, False otherwise. 875 876 """ 877 if app_id not in self.apps: 878 return False 879 880 return self._send_to_helper(self.CMD_RESET_EVENT_COUNT, app_id, 881 (monitor_id, event)) 882 883 884 def set_target_devices(self, app_id, monitor_id, devices): 885 """Set the target devices to the given monitor. 886 887 DeviceFound and DeviceLost will only be counted if it is triggered by a 888 target device. 889 890 @param app_id: the app id. 891 @param monitor_id: the monitor id. 892 @param devices: a list of devices in dbus object path 893 894 @returns: True on success, False otherwise. 895 """ 896 if app_id not in self.apps: 897 return False 898 899 self._send_to_helper(self.CMD_SET_TARGET_DEVICES, app_id, 900 (monitor_id, devices)) 901 return True 902 903 def destroy(self): 904 """Clean up the helper process and test app processes.""" 905 906 self._send_to_helper(self.CMD_EXIT_HELPER) 907 908 if self._helper_proc.is_alive(): 909 self._helper_proc.terminate() 910 self._helper_proc.join() # wait for helper process to terminate 911 912 return not self._helper_proc.is_alive() 913