如何在 BLE(BlueTooth Low Energy) 中有 2 个广告?

How to have 2 advertisements in BLE(BlueTooth Low Energy)?

我正在做 BLE 广告。我想知道是否有可能在 BLE 中有 2 个广告。我需要同时拥有服务数据和制造商数据。我正在使用 Python。该代码基于 https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/test/example-advertisement。 我需要支持 EddyStone Beacon 和一些制造商数据。但我不知道如何实施。 谢谢

想要做多个广告的关键是每个广告都必须使用其发布的唯一 D-Bus 对象路径来创建。

在 BlueZ 示例中,他们通过 PATH_BASE 并向其附加 index 值以使其唯一:

class Advertisement(dbus.service.Object):
    PATH_BASE = '/org/bluez/example/advertisement'

    def __init__(self, bus, index, advertising_type):
        self.path = self.PATH_BASE + str(index)
        self.bus = bus
        self.ad_type = advertising_type
        self.service_uuids = None
        self.manufacturer_data = None
        self.solicit_uuids = None
        self.service_data = None
        self.local_name = None
        self.include_tx_power = False
        self.data = None
        dbus.service.Object.__init__(self, bus, self.path)

然后他们在调用 RegisterAdvertisement 时使用此唯一路径:

    ad_manager.RegisterAdvertisement(test_advertisement.get_path(), {},
                                     reply_handler=register_ad_cb,
                                     error_handler=register_ad_error_cb)

为了做出 运行 的东西,我修改了 BlueZ 示例。 这些修改的重点是通过最小的更改使某些东西成为 运行,而不是我在生产中的做法。

首先,我更改了 TestAdvertisement 以执行不同的广告,具体取决于它是使用索引 0 还是索引 1:

调用的
class TestAdvertisement(Advertisement):

    def __init__(self, bus, index):
        Advertisement.__init__(self, bus, index, 'broadcast')
        self.add_service_uuid('FEAA')
        frame_type = [0x10] # Frame Type = 0x10
        power = [0x00]      # Power = 0x00
        if index == 0:
            prefix = [0x02]     # URL scheme = 0x02 (http://)
            url = [0x73, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x77, 0x65, 0x62, 0x73, 0x69, 0x74, 0x65, 0x07]
        elif index == 1:
            prefix = [0x01]  # URL scheme = https://www.
            url = [0x62, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68, 0x00]
        eddystone_data = frame_type + power + prefix + url
        self.add_service_data('FEAA', eddystone_data)

然后我修改了调用 TestAdvertisement 的地方所以它被调用了两次,一次是 index=0 一次是 index=1:

    ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                LE_ADVERTISING_MANAGER_IFACE)

    mainloop = GLib.MainLoop()
    test_advertisement= []
    for ad_id in range(2):
        test_advertisement.append(TestAdvertisement(bus=bus, index=ad_id))

        print(f'{ad_id}: Registering advert path: {test_advertisement[ad_id].get_path()}')
        ad_manager.RegisterAdvertisement(test_advertisement[ad_id].get_path(), {},
                                         reply_handler=register_ad_cb,
                                         error_handler=register_ad_error_cb)

    if timeout > 0:
        threading.Thread(target=shutdown, args=(timeout,)).start()
    else:
        print('Advertising forever...')

    try:
        mainloop.run()  # blocks until mainloop.quit() is called
    except KeyboardInterrupt:
        print('Cleaning up advertisements')

    for this_ad in test_advertisement:
        ad_manager.UnregisterAdvertisement(this_ad)
        print('Advertisement unregistered')
        dbus.service.Object.remove_from_connection(this_ad)

我还修改了代码取消注册两个广告以在最后清理。

该示例应显示两个具有不同 URL 的 Eddystone URL 信标。

谢谢@ukBaz!你的代码有效!我也尝试过2个不同内容的广告:一个是我们自己的特殊数据,另一个是EddyStone-URL。我们想要广告 1 的原因是因为我们的移动应用程序想要有一种方法来识别服务是否来自我们的设备。所以我们需要一个特殊的制造商数据,例如“1234”。同时,我们还需要支持EddyStone Beacon。所以我们需要 2 个广告。我不确定这是否是最佳解决方案。请评论。 我尝试过这个。它似乎工作。有 2 个不同内容的广告有什么副作用吗? 我将 'broadcast' 更改为 'peripheral',因为我们的设备需要连接。这是我的代码(我主要更改了“if index == 0:”部分):

class TestAdvertisement(Advertisement):

    def __init__(self, bus, index):
        Advertisement.__init__(self, bus, index, 'peripheral')
        if index == 0:
            # My code to have special manufacturer data and service UUID
            self.add_manufacturer_data(0x1234, b"1234567890")
            self.add_service_uuid("1234")
        elif index == 1:
            self.add_service_uuid('FEAA')
            frame_type = [0x10] # Frame Type = 0x10
            power = [0x00]      # Power = 0x00
            prefix = [0x01]  # URL scheme = https://www.
            url = [0x62, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68, 0x00]
            eddystone_data = frame_type + power + prefix + url
            self.add_service_data('FEAA', eddystone_data)    

由于评论对代码的支持不是很好,所以我把它放在一个问题的答案中。