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

Parsing advertisement data into elements #34

Open
NobodysNightmare opened this issue Dec 9, 2018 · 2 comments
Open

Parsing advertisement data into elements #34

NobodysNightmare opened this issue Dec 9, 2018 · 2 comments

Comments

@NobodysNightmare
Copy link

Hi there,

tl; dr: I want to offer making a contribution to the gem, in case you are interested and still maintaining it.

thanks for this gem, I am going to use it to scan custom BTLE advertisements I am sending out from an ESP32.

Since my custom advertisements were not yet supported, I first looked into BlueZScanner, to see how advertisements get parsed. While I could immediately see my advertisements using BlueZScanner.each_advertisement, the data looked off:

  • I could see the last few Bytes of my device identifier
  • after that came my custom payload (manufacturer data)

I noticed that the scanner is depending on fixed offsets in the ad_data, which seems to collide with my beacon announcing its device name.

I scribbled together some parsing code, which will (hopefully) correctly unpack the ad_data into separate AD elements:

def scan_with_elements(device_id)
  ScanBeacon::BlueZ.scan(device_id) do |mac, data, rssi|
    yield nil unless data

    elements = []
    while data.size > 0
      length, type = data.unpack('CC')
      elements << {
        type: type,
        data: data[2..length]
      }

      data = data[(length + 1)..-1]
    end
    yield mac, elements, rssi
  end
end

which gives me correct output for my beacon:

[{:type=>9, :data=>"NN Sensor"}, {:type=>255, :data=>"\xFF\xFF\x03\xD7\x00\x8A\x02"}]

Now to my question: Would you be interested in integrating that into your gem? In that case I would try to prepare a pull request that makes this parsing available. However, in case you are not interested, I'd just have a "local" method for my own project.

Disclaimer: I have my Bluetooth LE knowledge from blog articles and I am probably still not knowing all the relevant details...

@syoder
Copy link
Member

syoder commented Dec 10, 2018

Quick answer: yes, if there is a change you'd like to make to this gem - especially if it would be helpful for more than just your particular use case - we'd be happy to get a PR.

The BluezScanner code ignores the first 4 bytes of data because that would normally contain a flags PDU and this code doesn't care about those flags. One code change that could be made here is to check for a flags PDU and if one is found begin looking for the "real" data at offset 4 as the code currently does - if one is not found, begin at offset 0. Alternatively, you could have your ESP32 prefix your advertisement with a flags PDU (a common one would be the 3 bytes: [0x02, 0x01, 0x06]).

Currently this gem makes the assumption that you are scanning for manufacturer or service advertisements. If you'd like to scan for "complete local name" advertisements as well as your example parsing code above seems to do, we could modify this if statement to make this work a little better.

if ad_type == 0x03 # service ad
  yield(ad_data[9..-1], mac, rssi, ad_type)
else
  yield(ad_data[5..-1], mac, rssi, ad_type)
end

That would make it so that it only skips ahead to ignore the service UUID list when it knows it has a service ad. Then when it gets a "local name" ad, it wouldn't accidentally chop it up by skipping ahead to byte 9. With this change to the if statement (and with the flags PDU resolved one way or another), you should be able to write a BeaconParser for both "local name" ads as well as your custom mfg ad.

Does this make sense?

@NobodysNightmare
Copy link
Author

Hey, sorry for not replying in a long time. I have to admit, the thing that confuses me the most (e.g. also in your code snippet) is the use of fixed offsets. As far as I understand the bluetooth spec (the very few parts I read... actually lots of blog posts), the advertisement data can contain multiple elements, that can occur in any order, some elements might be omitted and some elements have a dynamic length.

Therefore using the fixed offsets in the actual code and your example above seems to only target very specific kinds of beacons. I feel like I am missing an important point here...

Regarding my proposal for a Pull Request: I am not sure if there would be a good general way to integrate my parser method into the general code base without introducing breaking changes. Also I could only test anything on Linux.

I could provide my element-based iterator as an alternative method in the BlueZScanner class, but since it would not be an effectively used part of the code, it might not really fit this gem very well.

What do you think?

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

No branches or pull requests

2 participants