• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright 2016 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"""Construction of an Advertisement object from an advertisement data
7dictionary.
8
9Much of this module refers to the code of test/example-advertisement in
10bluez project.
11"""
12
13from __future__ import absolute_import
14from __future__ import division
15from __future__ import print_function
16import dbus
17import dbus.mainloop.glib
18import dbus.service
19import logging
20
21
22DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
23LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1'
24
25
26class Advertisement(dbus.service.Object):
27    """An advertisement object."""
28
29    def __init__(self, bus, advertisement_data):
30        """Construction of an Advertisement object.
31
32        @param bus: a dbus system bus.
33        @param advertisement_data: advertisement data dictionary.
34
35        """
36        self.bus = bus
37        self._get_advertising_data(advertisement_data)
38        super(Advertisement, self).__init__(self.bus, self.path)
39
40
41    def _get_advertising_data(self, advertisement_data):
42        """Get advertising data from the advertisement_data dictionary.
43
44        @param bus: a dbus system bus.
45
46        """
47        self.path = advertisement_data.get('Path')
48        self.type = advertisement_data.get('Type')
49        self.service_uuids = advertisement_data.get('ServiceUUIDs', [])
50        self.solicit_uuids = advertisement_data.get('SolicitUUIDs', [])
51
52        # The xmlrpclib library requires that only string keys are allowed in
53        # python dictionary. Hence, we need to define the manufacturer data
54        # in an advertisement dictionary like
55        #    'ManufacturerData': {'0xff00': [0xa1, 0xa2, 0xa3, 0xa4, 0xa5]},
56        # in order to let autotest server transmit the advertisement to
57        # a client DUT for testing.
58        # On the other hand, the dbus method of advertising requires that
59        # the signature of the manufacturer data to be 'qv' where 'q' stands
60        # for unsigned 16-bit integer. Hence, we need to convert the key
61        # from a string, e.g., '0xff00', to its hex value, 0xff00.
62        # For signatures of the advertising properties, refer to
63        #     device_properties in src/third_party/bluez/src/device.c
64        # For explanation about signature types, refer to
65        #     https://dbus.freedesktop.org/doc/dbus-specification.html
66        self.manufacturer_data = dbus.Dictionary({}, signature='qv')
67        manufacturer_data = advertisement_data.get('ManufacturerData', {})
68        for key, value in manufacturer_data.items():
69            self.manufacturer_data[int(key, 16)] = dbus.Array(value,
70                                                              signature='y')
71
72        self.service_data = dbus.Dictionary({}, signature='sv')
73        service_data = advertisement_data.get('ServiceData', {})
74        for uuid, data in service_data.items():
75            self.service_data[uuid] = dbus.Array(data, signature='y')
76
77        self.include_tx_power = advertisement_data.get('IncludeTxPower')
78
79        self.scan_response = advertisement_data.get('ScanResponseData')
80
81    def get_path(self):
82        """Get the dbus object path of the advertisement.
83
84        @returns: the advertisement object path.
85
86        """
87        return dbus.ObjectPath(self.path)
88
89
90    @dbus.service.method(DBUS_PROP_IFACE, in_signature='s',
91                         out_signature='a{sv}')
92    def GetAll(self, interface):
93        """Get the properties dictionary of the advertisement.
94
95        @param interface: the bluetooth dbus interface.
96
97        @returns: the advertisement properties dictionary.
98
99        """
100        if interface != LE_ADVERTISEMENT_IFACE:
101            raise InvalidArgsException()
102
103        properties = dict()
104        properties['Type'] = dbus.String(self.type)
105
106        if self.service_uuids is not None:
107            properties['ServiceUUIDs'] = dbus.Array(self.service_uuids,
108                                                    signature='s')
109        if self.solicit_uuids is not None:
110            properties['SolicitUUIDs'] = dbus.Array(self.solicit_uuids,
111                                                    signature='s')
112        if self.manufacturer_data is not None:
113            properties['ManufacturerData'] = dbus.Dictionary(
114                self.manufacturer_data, signature='qv')
115
116        if self.service_data is not None:
117            properties['ServiceData'] = dbus.Dictionary(self.service_data,
118                                                        signature='sv')
119        if self.include_tx_power is not None:
120            properties['IncludeTxPower'] = dbus.Boolean(self.include_tx_power)
121
122        # Note here: Scan response data is an int (tag) -> array (value) mapping
123        # but autotest's xmlrpc server can only accept string keys. For this
124        # reason, the scan response key is encoded as a hex string, and then
125        # re-mapped here before the advertisement is registered.
126        if self.scan_response is not None:
127            scan_rsp = dbus.Dictionary({}, signature='yv')
128            for key, value in self.scan_response.items():
129                scan_rsp[int(key, 16)] = dbus.Array(value, signature='y')
130
131            properties['ScanResponseData'] = scan_rsp
132
133        return properties
134
135
136    @dbus.service.method(LE_ADVERTISEMENT_IFACE, in_signature='',
137                         out_signature='')
138    def Release(self):
139        """The method callback at release."""
140        logging.info('%s: Advertisement Release() called.', self.path)
141
142
143def example_advertisement():
144    """A demo example of creating an Advertisement object.
145
146    @returns: the Advertisement object.
147
148    """
149    ADVERTISEMENT_DATA = {
150        'Path': '/org/bluez/test/advertisement1',
151
152        # Could be 'central' or 'peripheral'.
153        'Type': 'peripheral',
154
155        # Refer to the specification for a list of service assgined numbers:
156        # https://www.bluetooth.com/specifications/gatt/services
157        # e.g., 180D represents "Heart Reate" service, and
158        #       180F "Battery Service".
159        'ServiceUUIDs': ['180D', '180F'],
160
161        # Service solicitation UUIDs.
162        'SolicitUUIDs': [],
163
164        # Two bytes of manufacturer id followed by manufacturer specific data.
165        'ManufacturerData': {'0xff00': [0xa1, 0xa2, 0xa3, 0xa4, 0xa5]},
166
167        # service UUID followed by additional service data.
168        'ServiceData': {'9999': [0x10, 0x20, 0x30, 0x40, 0x50]},
169
170        # Does it include transmit power level?
171        'IncludeTxPower': True}
172
173    return Advertisement(bus, ADVERTISEMENT_DATA)
174
175
176if __name__ == '__main__':
177    # It is required to set the mainloop before creating the system bus object.
178    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
179    bus = dbus.SystemBus()
180
181    adv = example_advertisement()
182    print(adv.GetAll(LE_ADVERTISEMENT_IFACE))
183