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