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