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