183 lines
6.8 KiB
Python
Executable File
183 lines
6.8 KiB
Python
Executable File
# Lint as: python2, python3
|
|
# Copyright 2016 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Construction of an Advertisement object from an advertisement data
|
|
dictionary.
|
|
|
|
Much of this module refers to the code of test/example-advertisement in
|
|
bluez project.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
import dbus
|
|
import dbus.mainloop.glib
|
|
import dbus.service
|
|
import logging
|
|
|
|
|
|
DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
|
|
LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1'
|
|
|
|
|
|
class Advertisement(dbus.service.Object):
|
|
"""An advertisement object."""
|
|
|
|
def __init__(self, bus, advertisement_data):
|
|
"""Construction of an Advertisement object.
|
|
|
|
@param bus: a dbus system bus.
|
|
@param advertisement_data: advertisement data dictionary.
|
|
|
|
"""
|
|
self.bus = bus
|
|
self._get_advertising_data(advertisement_data)
|
|
super(Advertisement, self).__init__(self.bus, self.path)
|
|
|
|
|
|
def _get_advertising_data(self, advertisement_data):
|
|
"""Get advertising data from the advertisement_data dictionary.
|
|
|
|
@param bus: a dbus system bus.
|
|
|
|
"""
|
|
self.path = advertisement_data.get('Path')
|
|
self.type = advertisement_data.get('Type')
|
|
self.service_uuids = advertisement_data.get('ServiceUUIDs', [])
|
|
self.solicit_uuids = advertisement_data.get('SolicitUUIDs', [])
|
|
|
|
# The xmlrpclib library requires that only string keys are allowed in
|
|
# python dictionary. Hence, we need to define the manufacturer data
|
|
# in an advertisement dictionary like
|
|
# 'ManufacturerData': {'0xff00': [0xa1, 0xa2, 0xa3, 0xa4, 0xa5]},
|
|
# in order to let autotest server transmit the advertisement to
|
|
# a client DUT for testing.
|
|
# On the other hand, the dbus method of advertising requires that
|
|
# the signature of the manufacturer data to be 'qv' where 'q' stands
|
|
# for unsigned 16-bit integer. Hence, we need to convert the key
|
|
# from a string, e.g., '0xff00', to its hex value, 0xff00.
|
|
# For signatures of the advertising properties, refer to
|
|
# device_properties in src/third_party/bluez/src/device.c
|
|
# For explanation about signature types, refer to
|
|
# https://dbus.freedesktop.org/doc/dbus-specification.html
|
|
self.manufacturer_data = dbus.Dictionary({}, signature='qv')
|
|
manufacturer_data = advertisement_data.get('ManufacturerData', {})
|
|
for key, value in manufacturer_data.items():
|
|
self.manufacturer_data[int(key, 16)] = dbus.Array(value,
|
|
signature='y')
|
|
|
|
self.service_data = dbus.Dictionary({}, signature='sv')
|
|
service_data = advertisement_data.get('ServiceData', {})
|
|
for uuid, data in service_data.items():
|
|
self.service_data[uuid] = dbus.Array(data, signature='y')
|
|
|
|
self.include_tx_power = advertisement_data.get('IncludeTxPower')
|
|
|
|
self.scan_response = advertisement_data.get('ScanResponseData')
|
|
|
|
def get_path(self):
|
|
"""Get the dbus object path of the advertisement.
|
|
|
|
@returns: the advertisement object path.
|
|
|
|
"""
|
|
return dbus.ObjectPath(self.path)
|
|
|
|
|
|
@dbus.service.method(DBUS_PROP_IFACE, in_signature='s',
|
|
out_signature='a{sv}')
|
|
def GetAll(self, interface):
|
|
"""Get the properties dictionary of the advertisement.
|
|
|
|
@param interface: the bluetooth dbus interface.
|
|
|
|
@returns: the advertisement properties dictionary.
|
|
|
|
"""
|
|
if interface != LE_ADVERTISEMENT_IFACE:
|
|
raise InvalidArgsException()
|
|
|
|
properties = dict()
|
|
properties['Type'] = dbus.String(self.type)
|
|
|
|
if self.service_uuids is not None:
|
|
properties['ServiceUUIDs'] = dbus.Array(self.service_uuids,
|
|
signature='s')
|
|
if self.solicit_uuids is not None:
|
|
properties['SolicitUUIDs'] = dbus.Array(self.solicit_uuids,
|
|
signature='s')
|
|
if self.manufacturer_data is not None:
|
|
properties['ManufacturerData'] = dbus.Dictionary(
|
|
self.manufacturer_data, signature='qv')
|
|
|
|
if self.service_data is not None:
|
|
properties['ServiceData'] = dbus.Dictionary(self.service_data,
|
|
signature='sv')
|
|
if self.include_tx_power is not None:
|
|
properties['IncludeTxPower'] = dbus.Boolean(self.include_tx_power)
|
|
|
|
# Note here: Scan response data is an int (tag) -> array (value) mapping
|
|
# but autotest's xmlrpc server can only accept string keys. For this
|
|
# reason, the scan response key is encoded as a hex string, and then
|
|
# re-mapped here before the advertisement is registered.
|
|
if self.scan_response is not None:
|
|
scan_rsp = dbus.Dictionary({}, signature='yv')
|
|
for key, value in self.scan_response.items():
|
|
scan_rsp[int(key, 16)] = dbus.Array(value, signature='y')
|
|
|
|
properties['ScanResponseData'] = scan_rsp
|
|
|
|
return properties
|
|
|
|
|
|
@dbus.service.method(LE_ADVERTISEMENT_IFACE, in_signature='',
|
|
out_signature='')
|
|
def Release(self):
|
|
"""The method callback at release."""
|
|
logging.info('%s: Advertisement Release() called.', self.path)
|
|
|
|
|
|
def example_advertisement():
|
|
"""A demo example of creating an Advertisement object.
|
|
|
|
@returns: the Advertisement object.
|
|
|
|
"""
|
|
ADVERTISEMENT_DATA = {
|
|
'Path': '/org/bluez/test/advertisement1',
|
|
|
|
# Could be 'central' or 'peripheral'.
|
|
'Type': 'peripheral',
|
|
|
|
# Refer to the specification for a list of service assgined numbers:
|
|
# https://www.bluetooth.com/specifications/gatt/services
|
|
# e.g., 180D represents "Heart Reate" service, and
|
|
# 180F "Battery Service".
|
|
'ServiceUUIDs': ['180D', '180F'],
|
|
|
|
# Service solicitation UUIDs.
|
|
'SolicitUUIDs': [],
|
|
|
|
# Two bytes of manufacturer id followed by manufacturer specific data.
|
|
'ManufacturerData': {'0xff00': [0xa1, 0xa2, 0xa3, 0xa4, 0xa5]},
|
|
|
|
# service UUID followed by additional service data.
|
|
'ServiceData': {'9999': [0x10, 0x20, 0x30, 0x40, 0x50]},
|
|
|
|
# Does it include transmit power level?
|
|
'IncludeTxPower': True}
|
|
|
|
return Advertisement(bus, ADVERTISEMENT_DATA)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# It is required to set the mainloop before creating the system bus object.
|
|
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
|
bus = dbus.SystemBus()
|
|
|
|
adv = example_advertisement()
|
|
print(adv.GetAll(LE_ADVERTISEMENT_IFACE))
|