Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
Itâs been a while since Iâve worked on a personal project, but Iâve been having an itch to make some new iOS apps and yesterday morning I decided to go ahead and hack something together.
I recently purchased an exercise bike called the BikeErg (I think the name has something to do with the rowing machines that the manufacturer also makes). The bike has a built-in computer that keeps track of things like watts (apparently cycling is a sport that has really good analytics since itâs easy to track raw power), calories burned, cadence and other stuff. You can view the data on the monitor or use an app like Zwift to do workouts.
Iâve been using the BikeErg to exercise pretty regularly now, and I tried a bunch of different apps that can connect to it. Zwift is pretty much the gold standard as it has many features like 3D avatars and environments, a rich community, and lots of different workout plans for you to try. Zwift integrates with apps like MyFitnessPal and Strava, too, so I can trick people into thinking that Iâve ridden in Central Park one day and London the next.
Studies have shown that riding a bike in a completely white room really builds your FTP
While I think the feature set of Zwift is really compelling, Iâm more of an old school app user. I donât really care about the online community. I donât really need to look at my avatar riding his bike around a futuristic city or an exploding volcano. I just want to do some directed workouts and maybe track my heart rate and my calories burned. The price of $15 a month is probably fine for people who use all of those features and get the value out of it, but I feel like I do not.
Itâs my virtual dude riding through a virtual New York with all his virtual pals
Just to be clear here, I do think app developers deserve to be paid for their work and itâs definitely within reason for Zwift to charge this subscription given the sheer amount of support they need to provide to all of their usersâ varying setups. After just implementing a small proof of concept, I have some mad respect for their dev team.
However, I am cheap and Iâm an iOS developer so I figured, âmaybe I can roll my own fake Zwift!â
Enter CoreBluetooth
The more I stare at this image the less sense it makes
Iâve been interested in Bluetooth development ever since CoreBluetooth was added to the iOS 5.0 SDK (I think the first supported device was the iPhone 4s). But every time I tried to sit down and read the documentation I got discouraged by the complexity and ended up getting distracted by some other new shiny API. Since I had a desired use case here: Make a Zwift alternative for myself, I was able to focus up some more and get something working.
While the Bluetooth protocol is incredibly flexible, that flexibility also makes it incredibly complicated to get even a simple proof of concept working. If you donât know what the special Bluetooth jargon means, it can seem really confusing. I still donât really understand all of it but Iâve managed to hack something together that will serve as a basis for my fake Zwift app.
Rather than bore you with the technical jargon and steps required to make this app, Iâd rather just go through my process of figuring it out, which may be slightly more interesting.
Of course itâs called a âManagerâ
So the first thing I did was go to this document (which I guess is deprecated now but I didnât notice that message when I was reading it) which goes over the Core Bluetooth framework.
I found out that I needed to create a CBCentralManager, so I did that and then I tried to scan for some Bluetooth devices:
let centralManager = CBCentralManager()self.centralManager.scanForPeripherals(withServices: nil, options:Â nil)
I immediately got an error that I couldnât do that since the centralManager wasnât powered on yet. Oops! I then set the delegate of the centralManager and waited for the method âcentralManagerDidUpdateStateâ to check that it was powered on before scanning.
I soon started getting a bunch of peripherals in my next delegate method, âcentralManager(_:didDiscover:advertisementData:rssi:)â
Among the things I found were my laptop (over and over again even though the scan was set to not allow duplicatesâŠ), someoneâs Bluetooth headset and various other things I couldnât identify. Success!
Once I filtered out the peripherals that kept on repeating, I was able to turn on the bike (by cycling a bit) and I got this message in my logs:
I successfully found my PM5. Now to connect to it and get the data. I ended up connecting to the PM5 based on the name. (After doing some reading it looks like I could connect based on the last service UUID of âCE060000-43E5-11E4-916C-0800200C9A66â).
I called the âconnectâ function of the centralManager and later got an error because the peripheral wasnât retained (I guess the Central doesnât keep a strong reference, which makes sense). I tried again, this time keeping a reference to the peripheral in an array.
Peripherals, Services and Characteristics
Once I connected, I had to discover the peripheralâs services. And once that succeeded I had to discover each serviceâs characteristics. Once you discover those characteristics you can set the peripheralâs servicesâ characteristics to ânotifyâ you when the characteristic changes. In more depth:
- Connect to peripheral using the centralâs âconnect(_:options:)â method and retain it
- Handle the âcentralManager(_:didConnect:)â delegate method where you set the peripheralâs delegate and call its âdiscoverServices(_:)â method
- Handle the âperipheral(_:didDiscoverServices:)â delegate method and call the peripheralâs âdiscoverCharacteristics(_:for:)â for each service you want to discover characteristics for (why not all of them at this point?)
- Handle the âperipheral(_:didDiscoverCharacteristicsFor:error:)â delegate method for each serviceâs characteristics you wanted to discover by calling the peripheralâs âsetNotifyValue(_:for:)â method on each serviceâs characteristic that you want notifications for.
- Optionally handle the âperipheral(_:didUpdateNotificationStateFor:error:)â method to see if you were able to successfully update the notification state for each peripheralâs serviceâs characteristic. In some cases I wasnât able to ask for updates, perhaps those characteristics are just static data?
- Handle the âperipheral(_:didUpdateValueFor:error:)â method to get the updated value for each characteristic that you wanted notifications for.
This all seems really convoluted to me and it was probably part of the reason that I always gave up on implementing Bluetooth in the past, but I think thatâs more of a symptom of the complexity of the Bluetooth protocol than the CoreBluetooth API.
Now all I needed to do was generate some data by cycling on the bike for a few seconds. I wasnât quite finished yet, though. When the characteristics are updated and you start getting notified, you can inspect the new values, but those values are just Data objects. Each characteristic can hold a number of values based on how the data is structured, and that is up to whoever is implementing the Bluetooth protocol.
I did some research and found this document that describes the Bluetooth specifications for the PM5Â device.
Just some really interesting light reading
In that document were some tables including the one above which describes the UUID for a characteristic that includes things like elapsed time, calories, and most importantly, watts. I discovered that the data was being encoded into bytes, so I took the raw Data object and split it into an array of 8-bit Integers. Once I started printing those arrays I saw something like this:
I originally printed out the Base 64 string representation of the Data before reading the doc, which was a lot less useful
Because the PM5 was originally set up for rowing machines, the documentation is a bit confusing. It refers to âstrokesâ which might line up with rpms on a bike? I was mainly interested in watts for my proof of concept so I found a few values in the document that mentioned watts. The table in the spec mentions âStroke Power Lo (watts)â and has a âStroke Power Hiâ (whatâs the difference?). I cobbled an interface together to test out my guess about the first value and hereâs the video result:
I took this video with an iPad and for once Iâm not ashamed of that. Also itâs shaky cause I was cycling.
Success! Iâm now able to connect my phone to my bike with my app. I have only gotten the wattage data from the bike so far, but reading through the spec it seems like there is a lot more I can pull via Bluetooth. I already know from using Zwift that I can get cadence from the bike, for example, and I saw a few other interesting things like calories, pace and distance traveled.
Every Journey Begins With a Single CoreBluetooth Implementation
I titled this blog post âPart 1â in a series but I donât know when the next step will be. My wishlist is:
- I want to eventually set up directed workouts in a similar fashion as Zwift
- I also want to be able to track my heart rate which I can do by writing an Apple Watch app for my existing app
- I want to be able to store my workout data and integrate with Apple Health
- I want to import workouts or at least create them inside of the app
- I want to chart the actual wattage of the bike against the guided wattage, and also show heart rate, and show histograms
- I want to avoid feature creep
I havenât figured out which order to do these things in but for now Iâll continue to use Zwift since I already paid for the membership. My next step is probably to break out the code for connecting to the PM5 into its own project and make all of the data from it available in an easy to consume form. Iâm kinda torn between that and just making the MVP for doing workouts.
If I had to estimate, I probably spent more than 3 hours working on this project so far and more on writing this blog post. If I was to value my time based on what my contracting rate would be Iâd probably be able to pay for more than a year of Zwift with it! So this project is really more about learning different iOS technologies than it is about saving money at this point.
If you found this blog post interesting let me know! I wanted to write down my process so I could remember it, but hopefully itâs useful to anyone trying to implement CoreBluetooth. I found a bunch of sample code that connects to heart rate monitors but I didnât find any that go through the process of writing code to a spec document. If you want to try to run this app yourself (and you happen to have the same exact bike as me), check out the source code here.
Making an iOS Zwift Clone to Save $15 a Month! Part 1: Core Bluetooth was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.