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