• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import base64
6import json
7
8from autotest_lib.client.cros import constants
9from autotest_lib.server import autotest
10
11
12class BluetoothDevice(object):
13    """BluetoothDevice is a thin layer of logic over a remote DUT.
14
15    The Autotest host object representing the remote DUT, passed to this
16    class on initialization, can be accessed from its host property.
17
18    """
19
20    XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
21    XMLRPC_LOG_PATH = '/var/log/bluetooth_xmlrpc_device.log'
22
23    def __init__(self, device_host):
24        """Construct a BluetoothDevice.
25
26        @param device_host: host object representing a remote host.
27
28        """
29        self.host = device_host
30        # Make sure the client library is on the device so that the proxy code
31        # is there when we try to call it.
32        client_at = autotest.Autotest(self.host)
33        client_at.install()
34        # Start up the XML-RPC proxy on the client.
35        self._proxy = self.host.rpc_server_tracker.xmlrpc_connect(
36                constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_COMMAND,
37                constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT,
38                command_name=
39                  constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_CLEANUP_PATTERN,
40                ready_test_name=
41                  constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_READY_METHOD,
42                timeout_seconds=self.XMLRPC_BRINGUP_TIMEOUT_SECONDS,
43                logfile=self.XMLRPC_LOG_PATH)
44
45        # Get some static information about the bluetooth adapter.
46        properties = self.get_adapter_properties()
47        self.bluez_version = properties.get('Name')
48        self.address = properties.get('Address')
49        self.bluetooth_class = properties.get('Class')
50        self.UUIDs = properties.get('UUIDs')
51
52
53    def start_bluetoothd(self):
54        """start bluetoothd.
55
56        @returns: True if bluetoothd is started correctly.
57                  False otherwise.
58
59        """
60        return self._proxy.start_bluetoothd()
61
62
63    def stop_bluetoothd(self):
64        """stop bluetoothd.
65
66        @returns: True if bluetoothd is stopped correctly.
67                  False otherwise.
68
69        """
70        return self._proxy.stop_bluetoothd()
71
72
73    def is_bluetoothd_running(self):
74        """Is bluetoothd running?
75
76        @returns: True if bluetoothd is running
77
78        """
79        return self._proxy.is_bluetoothd_running()
80
81
82    def reset_on(self):
83        """Reset the adapter and settings and power up the adapter.
84
85        @return True on success, False otherwise.
86
87        """
88        return self._proxy.reset_on()
89
90
91    def reset_off(self):
92        """Reset the adapter and settings, leave the adapter powered off.
93
94        @return True on success, False otherwise.
95
96        """
97        return self._proxy.reset_off()
98
99
100    def has_adapter(self):
101        """@return True if an adapter is present, False if not."""
102        return self._proxy.has_adapter()
103
104
105    def set_powered(self, powered):
106        """Set the adapter power state.
107
108        @param powered: adapter power state to set (True or False).
109
110        @return True on success, False otherwise.
111
112        """
113        return self._proxy.set_powered(powered)
114
115
116    def is_powered_on(self):
117        """Is the adapter powered on?
118
119        @returns: True if the adapter is powered on
120
121        """
122        properties = self.get_adapter_properties()
123        return bool(properties.get(u'Powered'))
124
125
126    def get_hci(self):
127        """Get hci of the adapter; normally, it is 'hci0'.
128
129        @returns: the hci name of the adapter.
130
131        """
132        dev_info = self.get_dev_info()
133        hci = (dev_info[1] if isinstance(dev_info, list) and
134               len(dev_info) > 1 else None)
135        return hci
136
137
138    def get_address(self):
139        """Get the bluetooth address of the adapter.
140
141        An example of the bluetooth address of the adapter: '6C:29:95:1A:D4:6F'
142
143        @returns: the bluetooth address of the adapter.
144
145        """
146        return self.address
147
148
149    def get_bluez_version(self):
150        """Get bluez version.
151
152        An exmaple of bluez version: 'BlueZ 5.39'
153
154        @returns: the bluez version
155
156        """
157        return self.bluez_version
158
159
160    def get_bluetooth_class(self):
161        """Get the bluetooth class of the adapter.
162
163        An example of the bluetooth class of a chromebook: 4718852
164
165        @returns: the bluetooth class.
166
167        """
168        return self.bluetooth_class
169
170
171    def get_UUIDs(self):
172        """Get the UUIDs.
173
174        An example of UUIDs:
175            [u'00001112-0000-1000-8000-00805f9b34fb',
176             u'00001801-0000-1000-8000-00805f9b34fb',
177             u'0000110a-0000-1000-8000-00805f9b34fb',
178             u'0000111f-0000-1000-8000-00805f9b34fb',
179             u'00001200-0000-1000-8000-00805f9b34fb',
180             u'00001800-0000-1000-8000-00805f9b34fb']
181
182        @returns: the list of the UUIDs.
183
184        """
185        return self.UUIDs
186
187
188    def set_discoverable(self, discoverable):
189        """Set the adapter discoverable state.
190
191        @param discoverable: adapter discoverable state to set (True or False).
192
193        @return True on success, False otherwise.
194
195        """
196        return self._proxy.set_discoverable(discoverable)
197
198
199    def is_discoverable(self):
200        """Is the adapter in the discoverable state?
201
202        @return True if discoverable. False otherwise.
203
204        """
205        properties = self.get_adapter_properties()
206        return properties.get('Discoverable') == 1
207
208
209    def set_pairable(self, pairable):
210        """Set the adapter pairable state.
211
212        @param pairable: adapter pairable state to set (True or False).
213
214        @return True on success, False otherwise.
215
216        """
217        return self._proxy.set_pairable(pairable)
218
219
220    def is_pairable(self):
221        """Is the adapter in the pairable state?
222
223        @return True if pairable. False otherwise.
224
225        """
226        properties = self.get_adapter_properties()
227        return properties.get('Pairable') == 1
228
229
230    def get_adapter_properties(self):
231        """Read the adapter properties from the Bluetooth Daemon.
232
233        An example of the adapter properties looks like
234        {u'Name': u'BlueZ 5.35',
235         u'Alias': u'Chromebook',
236         u'Modalias': u'bluetooth:v00E0p2436d0400',
237         u'Powered': 1,
238         u'DiscoverableTimeout': 180,
239         u'PairableTimeout': 0,
240         u'Discoverable': 0,
241         u'Address': u'6C:29:95:1A:D4:6F',
242         u'Discovering': 0,
243         u'Pairable': 1,
244         u'Class': 4718852,
245         u'UUIDs': [u'00001112-0000-1000-8000-00805f9b34fb',
246                    u'00001801-0000-1000-8000-00805f9b34fb',
247                    u'0000110a-0000-1000-8000-00805f9b34fb',
248                    u'0000111f-0000-1000-8000-00805f9b34fb',
249                    u'00001200-0000-1000-8000-00805f9b34fb',
250                    u'00001800-0000-1000-8000-00805f9b34fb']}
251
252        @return the properties as a dictionary on success,
253            the value False otherwise.
254
255        """
256        return json.loads(self._proxy.get_adapter_properties())
257
258
259    def read_version(self):
260        """Read the version of the management interface from the Kernel.
261
262        @return the version as a tuple of:
263          ( version, revision )
264
265        """
266        return json.loads(self._proxy.read_version())
267
268
269    def read_supported_commands(self):
270        """Read the set of supported commands from the Kernel.
271
272        @return set of supported commands as arrays in a tuple of:
273          ( commands, events )
274
275        """
276        return json.loads(self._proxy.read_supported_commands())
277
278
279    def read_index_list(self):
280        """Read the list of currently known controllers from the Kernel.
281
282        @return array of controller indexes.
283
284        """
285        return json.loads(self._proxy.read_index_list())
286
287
288    def read_info(self):
289        """Read the adapter information from the Kernel.
290
291        An example of the adapter information looks like
292        [u'6C:29:95:1A:D4:6F', 6, 2, 65535, 2769, 4718852, u'Chromebook', u'']
293
294        @return the information as a tuple of:
295          ( address, bluetooth_version, manufacturer_id,
296            supported_settings, current_settings, class_of_device,
297            name, short_name )
298
299        """
300        return json.loads(self._proxy.read_info())
301
302
303    def add_device(self, address, address_type, action):
304        """Add a device to the Kernel action list.
305
306        @param address: Address of the device to add.
307        @param address_type: Type of device in @address.
308        @param action: Action to take.
309
310        @return tuple of ( address, address_type ) on success,
311          None on failure.
312
313        """
314        return json.loads(self._proxy.add_device(address, address_type, action))
315
316
317    def remove_device(self, address, address_type):
318        """Remove a device from the Kernel action list.
319
320        @param address: Address of the device to remove.
321        @param address_type: Type of device in @address.
322
323        @return tuple of ( address, address_type ) on success,
324          None on failure.
325
326        """
327        return json.loads(self._proxy.remove_device(address, address_type))
328
329
330    def get_devices(self):
331        """Read information about remote devices known to the adapter.
332
333        An example of the device information of RN-42 looks like
334        [{u'Name': u'RNBT-A96F',
335          u'Alias': u'RNBT-A96F',
336          u'Adapter': u'/org/bluez/hci0',
337          u'LegacyPairing': 0,
338          u'Paired': 1,
339          u'Connected': 0,
340          u'UUIDs': [u'00001124-0000-1000-8000-00805f9b34fb'],
341          u'Address': u'00:06:66:75:A9:6F',
342          u'Icon': u'input-mouse',
343          u'Class': 1408,
344          u'Trusted': 1,
345          u'Blocked': 0}]
346
347        @return the properties of each device as an array of
348            dictionaries on success, the value False otherwise.
349
350        """
351        return json.loads(self._proxy.get_devices())
352
353
354    def get_device_properties(self, address):
355        """Read information about remote devices known to the adapter.
356
357        An example of the device information of RN-42 looks like
358
359        @param address: Address of the device to pair.
360        @param pin: The pin code of the device to pair.
361        @param timeout: The timeout in seconds for pairing.
362
363        @returns: a dictionary of device properties of the device on success;
364                  an empty dictionary otherwise.
365
366        """
367        return json.loads(self._proxy.get_device_by_address(address))
368
369        for device in self.get_devices():
370            if device.get['Address'] == address:
371                return device
372        return dict()
373
374
375    def start_discovery(self):
376        """Start discovery of remote devices.
377
378        Obtain the discovered device information using get_devices(), called
379        stop_discovery() when done.
380
381        @return True on success, False otherwise.
382
383        """
384        return self._proxy.start_discovery()
385
386
387    def stop_discovery(self):
388        """Stop discovery of remote devices.
389
390        @return True on success, False otherwise.
391
392        """
393        return self._proxy.stop_discovery()
394
395
396    def is_discovering(self):
397        """Is it discovering?
398
399        @return True if it is discovering. False otherwise.
400
401        """
402        return self.get_adapter_properties().get('Discovering') == 1
403
404
405    def get_dev_info(self):
406        """Read raw HCI device information.
407
408        An example of the device information looks like:
409        [0, u'hci0', u'6C:29:95:1A:D4:6F', 13, 0, 1, 581900950526, 52472, 7,
410         32768, 1021, 5, 96, 6, 0, 0, 151, 151, 0, 0, 0, 0, 1968, 12507]
411
412        @return tuple of (index, name, address, flags, device_type, bus_type,
413                       features, pkt_type, link_policy, link_mode,
414                       acl_mtu, acl_pkts, sco_mtu, sco_pkts,
415                       err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx,
416                       sco_tx, sco_rx, byte_rx, byte_tx) on success,
417                None on failure.
418
419        """
420        return json.loads(self._proxy.get_dev_info())
421
422
423    def register_profile(self, path, uuid, options):
424        """Register new profile (service).
425
426        @param path: Path to the profile object.
427        @param uuid: Service Class ID of the service as string.
428        @param options: Dictionary of options for the new service, compliant
429                        with BlueZ D-Bus Profile API standard.
430
431        @return True on success, False otherwise.
432
433        """
434        return self._proxy.register_profile(path, uuid, options)
435
436
437    def has_device(self, address):
438        """Checks if the device with a given address exists.
439
440        @param address: Address of the device.
441
442        @returns: True if there is a device with that address.
443                  False otherwise.
444
445        """
446        return self._proxy.has_device(address)
447
448
449    def device_is_paired(self, address):
450        """Checks if a device is paired.
451
452        @param address: address of the device.
453
454        @returns: True if device is paired. False otherwise.
455
456        """
457        return self._proxy.device_is_paired(address)
458
459
460    def device_services_resolved(self, address):
461        """Checks if services are resolved for a device.
462
463        @param address: address of the device.
464
465        @returns: True if services are resolved. False otherwise.
466
467        """
468        return self._proxy.device_services_resolved(address)
469
470
471    def set_trusted(self, address, trusted=True):
472        """Set the device trusted.
473
474        @param address: The bluetooth address of the device.
475        @param trusted: True or False indicating whether to set trusted or not.
476
477        @returns: True if successful. False otherwise.
478
479        """
480        return self._proxy.set_trusted(address, trusted)
481
482
483    def pair_legacy_device(self, address, pin, trusted, timeout):
484        """Pairs a device with a given pin code.
485
486        Registers an agent who handles pin code request and
487        pairs a device with known pin code.
488
489        @param address: Address of the device to pair.
490        @param pin: The pin code of the device to pair.
491        @param trusted: indicating whether to set the device trusted.
492        @param timeout: The timeout in seconds for pairing.
493
494        @returns: True on success. False otherwise.
495
496        """
497        return self._proxy.pair_legacy_device(address, pin, trusted, timeout)
498
499
500    def remove_device_object(self, address):
501        """Removes a device object and the pairing information.
502
503        Calls RemoveDevice method to remove remote device
504        object and the pairing information.
505
506        @param address: address of the device to unpair.
507
508        @returns: True on success. False otherwise.
509
510        """
511        return self._proxy.remove_device_object(address)
512
513
514    def connect_device(self, address):
515        """Connects a device.
516
517        Connects a device if it is not connected.
518
519        @param address: Address of the device to connect.
520
521        @returns: True on success. False otherwise.
522
523        """
524        return self._proxy.connect_device(address)
525
526
527    def device_is_connected(self, address):
528        """Checks if a device is connected.
529
530        @param address: Address of the device to check if it is connected.
531
532        @returns: True if device is connected. False otherwise.
533
534        """
535        return self._proxy.device_is_connected(address)
536
537
538    def disconnect_device(self, address):
539        """Disconnects a device.
540
541        Disconnects a device if it is connected.
542
543        @param address: Address of the device to disconnect.
544
545        @returns: True on success. False otherwise.
546
547        """
548        return self._proxy.disconnect_device(address)
549
550
551    def btmon_start(self):
552        """Start btmon monitoring."""
553        self._proxy.btmon_start()
554
555
556    def btmon_stop(self):
557        """Stop btmon monitoring."""
558        self._proxy.btmon_stop()
559
560
561    def btmon_get(self, search_str='', start_str=''):
562        """Get btmon output contents.
563
564        @param search_str: only lines with search_str would be kept.
565        @param start_str: all lines before the occurrence of start_str would be
566                filtered.
567
568        @returns: the recorded btmon output.
569
570        """
571        return self._proxy.btmon_get(search_str, start_str)
572
573
574    def btmon_find(self, pattern_str):
575        """Find if a pattern string exists in btmon output.
576
577        @param pattern_str: the pattern string to find.
578
579        @returns: True on success. False otherwise.
580
581        """
582        return self._proxy.btmon_find(pattern_str)
583
584
585    def register_advertisement(self, advertisement_data):
586        """Register an advertisement.
587
588        Note that rpc supports only conformable types. Hence, a
589        dict about the advertisement is passed as a parameter such
590        that the advertisement object could be contructed on the host.
591
592        @param advertisement_data: a dict of the advertisement for
593                                   the adapter to register.
594
595        @returns: True on success. False otherwise.
596
597        """
598        return self._proxy.register_advertisement(advertisement_data)
599
600
601    def unregister_advertisement(self, advertisement_data):
602        """Unregister an advertisement.
603
604        @param advertisement_data: a dict of the advertisement to unregister.
605
606        @returns: True on success. False otherwise.
607
608        """
609        return self._proxy.unregister_advertisement(advertisement_data)
610
611
612    def set_advertising_intervals(self, min_adv_interval_ms,
613                                  max_adv_interval_ms):
614        """Set advertising intervals.
615
616        @param min_adv_interval_ms: the min advertising interval in ms.
617        @param max_adv_interval_ms: the max advertising interval in ms.
618
619        @returns: True on success. False otherwise.
620
621        """
622        return self._proxy.set_advertising_intervals(min_adv_interval_ms,
623                                                     max_adv_interval_ms)
624
625
626    def reset_advertising(self):
627        """Reset advertising.
628
629        This includes unregister all advertisements, reset advertising
630        intervals, and disable advertising.
631
632        @returns: True on success. False otherwise.
633
634        """
635        return self._proxy.reset_advertising()
636
637
638    def read_characteristic(self, uuid, address):
639        """Reads the value of a gatt characteristic.
640
641        Reads the current value of a gatt characteristic.
642
643        @param uuid: The uuid of the characteristic to read, as a string.
644        @param address: The MAC address of the remote device.
645
646        @returns: A byte array containing the value of the if the uuid/address
647                      was found in the object tree.
648                  None if the uuid/address was not found in the object tree, or
649                      if a DBus exception was raised by the read operation.
650
651        """
652        value = self._proxy.read_characteristic(uuid, address)
653        if value is None:
654            return None
655        return bytearray(base64.standard_b64decode(value))
656
657
658    def write_characteristic(self, uuid, address, bytes_to_write):
659        """Performs a write operation on a gatt characteristic.
660
661        Writes to a GATT characteristic on a remote device.
662
663        @param uuid: The uuid of the characteristic to write to, as a string.
664        @param address: The MAC address of the remote device, as a string.
665        @param bytes_to_write: A byte array containing the data to write.
666
667        @returns: True if the write operation does not raise an exception.
668                  None if the uuid/address was not found in the object tree, or
669                      if a DBus exception was raised by the write operation.
670
671        """
672        return self._proxy.write_characteristic(
673            uuid, address, base64.standard_b64encode(bytes_to_write))
674
675
676    def is_characteristic_path_resolved(self, uuid, address):
677        """Checks whether a characteristic is in the object tree.
678
679        Checks whether a characteristic is curently found in the object tree.
680
681        @param uuid: The uuid of the characteristic to search for.
682        @param address: The MAC address of the device on which to search for
683            the characteristic.
684
685        @returns: True if the characteristic is found, False otherwise.
686
687        """
688        return self._proxy.is_characteristic_path_resolved(uuid, address)
689
690
691    def copy_logs(self, destination):
692        """Copy the logs generated by this device to a given location.
693
694        @param destination: destination directory for the logs.
695
696        """
697        self.host.collect_logs(self.XMLRPC_LOG_PATH, destination)
698
699
700    def close(self, close_host=True):
701        """Tear down state associated with the client.
702
703        @param close_host: If True, shut down the xml rpc server by closing the
704            underlying host object (which also shuts down all other xml rpc
705            servers running on the DUT). Otherwise, only shut down the
706            bluetooth device xml rpc server, which can be desirable if the host
707            object and/or other xml rpc servers need to be used afterwards.
708        """
709        # Turn off the discoverable flag since it may affect future tests.
710        self._proxy.set_discoverable(False)
711        # Leave the adapter powered off, but don't do a full reset.
712        self._proxy.set_powered(False)
713        # This kills the RPC server.
714        if close_host:
715          self.host.close()
716        else:
717          self.host.rpc_server_tracker.disconnect(
718              constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT)
719