Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hci: add advertiser support for ManufacturerData and ServiceData #307

Merged
merged 6 commits into from
Jan 5, 2025

Conversation

deadprogram
Copy link
Member

@deadprogram deadprogram commented Nov 27, 2024

This PR adds advertiser support for ManufacturerData and ServiceData to the HCI implementation.

Addresses #306

gap_linux.go Outdated
@@ -125,6 +125,26 @@ func (a *Advertisement) Stop() error {
return nil
}

// SetServiceData sets the service data for the advertisement.
func (a *Advertisement) SetServiceData(sd []ServiceDataElement) error {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aykevl do you happen to know how I can do this? 😸

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your goal?

If you want to change advertisement data then I think you can stop and restart the advertisement with the new service data. Looking at the API I don't see a straightforward way to update any part of the advertisement. But this is straightforward to do from userspace: simply stop and restart the advertisement with the new parameters.

See:
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.LEAdvertisement.rst
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.LEAdvertisingManager.rst

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to change advertisement data then I think you can stop and restart the advertisement

OK I will probably do that in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aykevl I took your advice and got rid of SetServiceData() entirely. PTAL.

gap_nrf51.go Outdated Show resolved Hide resolved
gap_linux.go Outdated
@@ -125,6 +125,26 @@ func (a *Advertisement) Stop() error {
return nil
}

// SetServiceData sets the service data for the advertisement.
func (a *Advertisement) SetServiceData(sd []ServiceDataElement) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your goal?

If you want to change advertisement data then I think you can stop and restart the advertisement with the new service data. Looking at the API I don't see a straightforward way to update any part of the advertisement. But this is straightforward to do from userspace: simply stop and restart the advertisement with the new parameters.

See:
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.LEAdvertisement.rst
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.LEAdvertisingManager.rst

Comment on lines 90 to 91
// TODO: implement
return nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this can be implemented here, but it needs a different API.
https://docs.nordicsemi.com/bundle/s132_v6.0.0_api/page/group_b_l_e_g_a_p_f_u_n_c_t_i_o_n_s.html#ga9969047f4e7485c3f856c841978cc31a
Instead of a SetServiceData, you'd need to construct an entirely new advertisement payload (bluetooth.AdvertisementOptions) and set it as a whole.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about preserve the initial options and then set a new payload when SetServiceData is changed?

Please see the new example beacon for how I am trying to use this function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment above.

@@ -420,3 +441,51 @@ func (a *Advertisement) Start() error {
func (a *Advertisement) Stop() error {
return a.adapter.hci.leSetAdvertiseEnable(false)
}

// SetServiceData sets the service data for the advertisement.
Copy link

@colececil colececil Dec 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some points I think it might be helpful to cover in the method documentation here, to make it more clear how SetServiceData needs to be used and what its effects are:

  1. It causes a new advertisement to be broadcast, with the updated service data that has been passed in.
  2. Just updating the data in the []ServiceDataElement that has already been passed in to Configure via AdvertisementOptions.ServiceData will not change the data being broadcast in the advertisement. SetServiceData must be called for the new data to go into effect.
  3. What happens if Configure or Start have not yet been called on the Advertisement?
  4. What effect does this have if the Advertisement has been configured with the Interval option?

@deadprogram
Copy link
Member Author

After some consideration, I think that it is better to just call adv.Stop(), then adv.Configure() and then adv.Start() again instead of adding a new API function.

This makes it more straightforward to understand what is happening, and to consequently then make the various platform implementations work as expected.

@colececil I think this implementation answers the questions you raised:

It causes a new advertisement to be broadcast, with the updated service data that has been passed in.

That is accommodated by the pattern in this updated PR.

Just updating the data in the []ServiceDataElement that has already been passed in to Configure via AdvertisementOptions.ServiceData will not change the data being broadcast in the advertisement. SetServiceData must be called for the new data to go into effect.

That is no longer a problem since there is only 1 way to set this data.

What happens if Configure or Start have not yet been called on the Advertisement?

This is also not a problem now.

What effect does this have if the Advertisement has been configured with the Interval option?

This is just a normal advertising option, nothing specific to the ServiceData.

I have updated this PR accordingly, including the new example. It works correctly with the HCI, I will still need to make some changes for other targets.

@deadprogram
Copy link
Member Author

I just added another commit to this PR that allows the example/beacon to run correctly on Linux.

@deadprogram
Copy link
Member Author

OK this PR is rebased and squashed. Works correctly on HCI and Linux. nrf is currently broken (See #310) and Windows does not support ServiceData. macOS does not yet have the Advertisement implemented. As such, I think this PR is now ready for final review.

@deadprogram deadprogram force-pushed the hci-service-data branch 2 times, most recently from 579edec to 6871cd0 Compare January 3, 2025 16:44
@deadprogram
Copy link
Member Author

@colececil @aykevl or anyone else, any thoughts here? I think this is ready to merge.

@colececil
Copy link

@deadprogram I haven't had the chance to check it out in detail, but the new approach you described does indeed sound more straightforward and less confusing.

A couple questions:

  1. To send updated data at a set interval, I'm assuming I would leave the Interval option unset as before, and just call call Stop, Configure, and Start at whatever interval I want?
  2. If the above assumption is correct, is there any advantage to calling Stop right after the advertisement is sent, rather than right before sending the next one? Like, would that save power or anything? And if so, is there a way for the program to know when the advertisement has been sent, so it knows when Stop can safely be called?

Thanks!

@deadprogram
Copy link
Member Author

To send updated data at a set interval, I'm assuming I would leave the Interval option unset as before, and just call call Stop, Configure, and Start at whatever interval I want?

Not setting Interval is probably just leaving it at the default. So as long as it is something less than the frequency that you are calling "Stop, Configure, and Start" it would do what you would expect.

If the above assumption is correct, is there any advantage to calling Stop right after the advertisement is sent, rather than right before sending the next one?

That would almost certainly result in some receivers not getting the advertisement data, since you do not know how often they are scanning for updates..

…advertising correctly.

Needed to update ServiceData after Advertisment is already started.

Signed-off-by: deadprogram <[email protected]>
@deadprogram
Copy link
Member Author

Rebased against the latest dev and ready.

@deadprogram
Copy link
Member Author

OK I realized from some testing I needed to add another commit. The problem was that multiple calls to adv.Configure() when using the HCI adaptor were creating additional generic Services each time it was called. The additional commit handles that case.

gap_linux.go Outdated Show resolved Hide resolved
@bgould
Copy link
Member

bgould commented Jan 5, 2025

Overall this is working great for me on arduino-nano33 to implement the "BTHome" protocol using this program - https://github.com/bgould/tinygo-float-sensor/blob/main/main.go ... Home Assistant is able to automatically discover the device and receive sensor values from the advertising data every 100ms. Going to let it run for a while and log data for a bit, but so far it is working well. Will also try this on pico-w and let you know the results.

Was also able to confirm using AdvertisementType option appears to working as expected using nRF Connect. At some point it might be nice to add this option for SoftDevice as well.

Thank you @deadprogram, this is really good :)

…figure()

followed by adv.Start() followed by adv.Stop() multiple times. This is required
in order to update advertising ServiceData.

Signed-off-by: deadprogram <[email protected]>
…otherwise different adverting types do not work correctly

Signed-off-by: deadprogram <[email protected]>
@deadprogram
Copy link
Member Author

Will also try this on pico-w and let you know the results.

@bgould advertising is not working correctly on Pico-W at present. See #301

@deadprogram
Copy link
Member Author

At some point it might be nice to add this option for SoftDevice as well.

@bgould softdevice advertising is also currently non-functional. See #310

@deadprogram
Copy link
Member Author

I've addressed all of the feedback, and squashed commits as much as makes sense. Now going to merge this so further work can proceed.

Thanks very much to @colececil @aykevl @bgould for feedback and testing!

@deadprogram deadprogram merged commit afac5f0 into dev Jan 5, 2025
4 checks passed
@deadprogram deadprogram deleted the hci-service-data branch January 5, 2025 09:18
@deadprogram
Copy link
Member Author

FYI for @colececil @bgould or anyone else interested in BTHome, I just started this little helper package https://github.com/hybridgroup/go-bthome

@colececil
Copy link

That's awesome @deadprogram! Thanks!

@bgould
Copy link
Member

bgould commented Jan 5, 2025

At some point it might be nice to add this option for SoftDevice as well.

@bgould softdevice advertising is also currently non-functional. See #310

FWIW ... advertisements using SoftDevice are working with this program on feather-nrf52840 - https://github.com/bgould/tinygo-float-sensor/blob/main/main.go ... that said I am able to reproduce the crash described in #310 on the same board, but it does not appear to be directly related to advertisement data.

@deadprogram
Copy link
Member Author

@bgould that is interesting to know, might help to track down the problem there...

@colececil
Copy link

@deadprogram Good news! I updated my project (https://github.com/colececil/automatic-soil-monitor) to use the revised API here, along with the BTHome helper package you created, and it's still working as expected! And it's working well with Home Assistant:

image

@deadprogram
Copy link
Member Author

That is good to hear, thank you for testing @colececil !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants