Home | History | Annotate | Download | only in bluetooth
      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