How to Beg/Borrow/Steal Your Way to a Cross Platform Bluetooth LE Library

Let me be clear.

I do not want to maintain a Bluetooth LE library for any platform, much less as many platforms as possible.

Unfortunately, my circumstances are such that this isn’t a choice I get to make. It’s either build and maintain a library, or depend on whatever I can find elsewhere. There wasn’t, and to this day, still isn’t anything elsewhere that met my needs.

If I do have to build the library, I do not want to do it for free. I also don’t want to support or think about the library, therefore I do not want to do it for pay. As I hate support and divided attention more than I like money (and maybe some poor suckers who also do things for free will show up and help), I am stuck doing it for free.

Thus begins the story of yet another reluctant developer gluing things together until they curse themselves into being an open source maintainer.

The Birth of btleplug

It’s December 2019, and I’m just finished up the new Rust version of Buttplug, the sex toy control library I maintain. Before this point, there’d been 2 full implementations of Buttplug, in C# and Javascript. I’d gotten tired of maintaining side-by-side versions of the library, and also just liked programming in Rust more than C# and Javascript, so I’d decided to collapse the hardware accessing portion of the library to only be implemented in one language. Rust v1.36 had stabilized months earlier, giving us the wonderful tool/godawful mistake that is async Rust, which I thought would be handy for my extremely I/O bound needs.

That porting process is another story full of questionable choices I’ll be going over in detail in another post soon.

The issue with sex toys is that most modern toys communicate via Bluetooth LE 1. The manufacturers assume the main use of these toys will happen on mobile phones, so their communication choices are WiFi or Bluetooth, and WiFi has many, many issues of its own. Therefore, Bluetooth tends to just be the default strategy.

Of course, I wanted to access these toys from anywhere on desktop or mobile. With Buttplug, I’d so far managed this on Windows via the C# UWP Bluetooth LE APIs that landed in 2017, and on the web via WebBluetooth in Chrome based browsers, which sort of covers non-windows systems as long as the applications using the library were web based.

Moving to Rust got me the comfy language I wanted to work in, and a way to possibly compile the same codebase to desktop, mobile, and the Web. I just needed a way to access Bluetooth from it.

I created a repo and got on my way. Of course, this library was specifically to support my Buttplug library work, so I called the new BLE repo btleplug 2.

In the Beginning, There was Linux

Why start with a repo? Why not see what’s out there first?

Well, I did just that, and it was obvious I was going to be required to fork something.

I wanted a Bluetooth LE library in Rust. A lot of brave souls had started them, few had made it very far into implementation, and almost everyone had decided to go with Linux and BlueZ. I assumed Linux would be the platform where I would have the least users3, so while I was glad I wouldn’t have to think about that implementation much as it’d probably already be done, I needed something with an API I could extend quickly over to Windows.

After digging through multiple dead crates, I happened upon Rumble. This seemed to be a fairly complete implementation of a Bluetooth LE library, even though it only supported Linux at the time. It had a nice surface API and easy integration points.

The last commit had been 6 months prior to this point, meaning the project hadn’t completely entered bitrot yet, though the main body of work on rumble had ended about a year before, in December 2018. As I saw it in December 2019, the issues and PRs were stacking up, which had the feeling of maintenance being dropped. I pulled my own version of the repo, and got to integrating PRs that had been sitting for a while.

Windows by Luck

Windows support being my ultimate goal here, rumble benefited from the fact that someone had already implemented windows UWP support as a pull request in mid-20184. This PR had never been integrated in the original repo, so I started on that.

Luckily, the repo came in fairly easily, and mostly worked on the first try. By mid January 2020, I had a system that worked for Linux and Windows. This solidified my choice to just continue btleplug where rumble had left off, so I posted a note to their issues to let the project know I was forking.

I now owned a Bluetooth LE library. Oh boy.

macOS, the Ice Bath in a Hotel Bathtub Way

The last hurdle for desktop support was macOS (I say, awaiting the boos and jeers from everyone wanting to use their sextoys with some non-macOS flavor of BSD). That meant I needed a CoreBluetooth implementation.

Before this, the last time I’d dealt with Carbon was sometime around 2004, and I’d never touched Cocoa or ObjC. For this project I was going to have to rely on the work of someone with actual skill on those platforms.

Luckily, open source came to the rescue once again, this time in the form of the Mozilla Servo project.

During my time as the Device Interfaces Lead on Firefox at Mozilla, I’d tried to bring in some initiatives to get hardware access happening in Firefox, via some of the APIs that were showing up at the time in Chromium. This was, of course, met with some resistance as the APIs were seen to be insecure and generally a bad idea. While some problems like WebMIDI did finally make it into the browser, WebBluetooth remains Chromium only for major browser implementation.

The Servo project did not have these restrictions however, and ended up with a WebBluetooth implementation for Linux and macOS, written in Rust. I took my trusty code scalpel and ripped the macOS implementation out of servo, integrating it with the API frontend I’d gotten from rumble. After a health application of super glue, a few stitches, and multiple minutes doing a mad scientist laugh, I had a bluetooth LE library that ran on all desktop platforms.

Async Despite Best Efforts

By the end of January 2020, I had a non-async Bluetooth library that worked on Windows, macOS, and Linux. That was good enough for me, and I proceeded to only make spot-fixes for the next year.

Being the attention whore that I am, I’d also announced a few places that btleplug was a thing, which made other people want to use it, and therefore I now had issues and pull requests waiting. I’m really good at ignoring those things though.

Fast forward to December 2020. I’m getting requests to add more devs to the project to move things along quicker, but no one seems to know much about Bluetooth LE and I don’t want the library moving in directions that might break it for me, so I stand firm on my “I have forked and therefore you may also fork if you don’t like things” philosophy. The problem now is that Buttplug Rust is async, btleplug is not, and it’s starting to cause conflicts that I do need to fix.

Also, if I don’t make the library async, less people on Hacker News will be mad at me, and that’s a number that should always go up, not down.

I would much rather pay attention to Buttplug than btleplug though, so I continue doing so. I hold the line that, if btleplug is going to update to async and break API, it should move the whole surface API with it into something not quite so BlueZ focused. The project up to this point has been wedging other desktop APIs into a BlueZ shaped hole. Getting a truly universal API would require sitting down, mapping out functionality on all desired platforms, and coming up with the best generalization that all platforms could use. I wanted that work done, but I really did not want to do it.

At this point, two things happened:

I had multiple users and developers now pushing for async, as well as my own needs, but I continue my “planning before execution” line.

Then I got a PR for a finished version of the async API. I had some initial issues with it, mostly aligned on the “everyone everywhere is hamstrung because we’re doing this like BlueZ” lines. Via the arguments of multiple devs and my own realization that it was done and I didn’t have to do it, I relented, and by July 2021, we had an async bluetooth library.

The Mysterious Android Superhero

Things stayed quiet for the next few months. We had multiple maintainers on the project finally, the library mostly did what it was supposed to without issue, and our userbase slowly but surely grew.

Because I like to keep things chaotic, I decided Buttplug needed to be on mobile.

Android was yet another platform I’d never worked on outside of the AOSP/Kernel level on FirefoxOS. I had no idea how we’d bind async Rust into the Java system for accessing Bluetooth.

Good thing there’s random people on the internet that really enjoy implementing this kind of stuff, and apparently they know how to find me. In July 2021, an anonymous developer on github5 reached out to me, saying they’d been working on a version of Buttplug that would work on Android, including all of the BluetoothLE bindings needed. Sure enough, they’d built a whole stack capable of communication with Bluetooth devices on Android, working all the way up through my library.

In August 2021, they disappeared, going silent on all communication streams. I have yet to hear from them again.

This left me with a mostly done Android library, but not a lot of information on how to even compile it, much less test it. I let the Android project sit dormant for a while, waiting for the return of a savior that never arrived.

In early 2022, I gave up waiting and took the project on myself, getting a very basic android app up that bound to a native Buttplug library using the btleplug android code, and connected to toys. This landed to our main library in June 2022, and has been used by a few projects since then (including Buttplug, which now ships as part of Intiface Central on Android and iOS).

iOS Just Works

But what about iOS?

We got that for free with CoreBluetooth back in January 2020, it’d just never been tested up until 2022. The only additions needed were mostly to permissions in projects using the library, but almost nothing to the library itself. While working with Apple’s software stack and development environment continues to be an enduring nightmare, this was one of the few times where I got an easy win.

lol.

Where’s WebBluetooth?

We got a PR for WebBluetooth in August 2021. Unfortunately at this point the PR needs a pretty big polish to be compatible with the API changes since.

The PR sits waiting because I implemented WebBluetooth bindings in wasm-bindgen and had already built a simple Bluetooth LE manager directly into Buttplug, without needing btleplug. The idea of trying to bring in another platform’s worth of maintenance in btleplug has been too much for now. While I love supporting as many platforms as possible, I’m currently the only maintainer on btleplug who’s dealt with wasm compilation and WebBluetooth interaction, so it’s a question of energy that I have not, and currently do not have.

Btleplug as it Stands

4 years later, and here I sit, running a Bluetooth LE library that compiles to and ships on 5 platforms while still basically being designed for BlueZ, without having written any of the platform code fully by myself, or at any point ever doing a majority of implementation for any specific platform. Thanks to a load of other developers who actually know what they’re doing and an apparently endless bottle of code superglue, we now have a library with a few hundred stars on github and about the best platform support of any Bluetooth LE library, even if the API surface isn’t the easiest in the world to use.

This is not me patting myself on the back as much as it is realizing that this was all only achievable via open source, but also damn, it was a lot of work.

I stay in touch with maintainers from libraries like SimpleBLE and Bluey, eagerly awaiting the day I can finally just use their great work and drop btleplug. Not sure that’ll ever happen, but it’s nice to hope.

Until then, I’ll probably continue to randomly decide bluetooth is interesting for a weekend or two and try to kick out updates/bugfixes a few times a year. Is this going to ever be some bulletproof end-all-be-all for bluetooth? God, no. Does what I need it to, though.


Want to get posts like this in your mailbox instead of having to read a website? Subscribe to our newsletter!


  1. There are toys that talk over WiFi, USB, HID, and Serial, and Buttplug does support that hardware, but it’s less than 5% of our supported hardware base. Bluetooth LE support is our big draw. 
  2. As of this writing, the btleplug naming choice has cost outside companies using my software at least 2 government contracts rejections. As for why it’s under the deviceplug org: The original idea was to spin out the hardware/message core of Buttplug into a generic userland HID system of sorts. To this day that has not happened and just thinking about it makes me tired. 
  3. This assumption turned out to not be accurate. Linux is actually the second least used platform we support as of this writing. For desktop, Windows makes up about 92% of our installs, Linux 5%, macOS 3%. 
  4. The company that had done this, Timeular, makes a neat little dice thing that I ended up buying in support of them putting in that PR. It’s 3 years later and I think I’ve turned it on once, but I still maintain that I will one day support it as a sensor in Buttplug. 
  5. I maintain that Buttplug may be one of the biggest reasons for people to make anonymous accounts on github. Not everyone wants to risk their career working on libraries like this, so people have to create NSFW github accounts to work on my projects, much like they do social media accounts to post nudes.