Architecting iOS and Android Apps for IoT BLE Systems
When you buy a Fitbit, you’re not just paying for the wearable hardware. You’re buying a key that unlocks access to a greater integrated system. Before you can even start using the device, you must first download the app, pair it with your phone, and create a user login.
Even a decade ago, it might’ve been completely untenable to require users to have an expensive smartphone to use a product. But today’s wireless devices take advantage of the fact that nearly everyone has a full-fledged Bluetooth Low Energy (BLE) and Internet of Things (IoT) bridge device somewhere on their bodies.
Of course, while those who are developing connected products have more tools at their disposal than ever before, implementing such a system is far from simple. Let’s take a look at some crucial considerations when designing a system that relies on mobile phones as a data bridge between BLE devices and the cloud.
Design for iOS first
iOS has more built-in barriers than Android, so it’s important to learn ahead of time what these are and take them into account when designing your system requirements and architecture. For instance, when your app is not actively in use, iOS will usually put it into a suspended or even terminated state. Certain triggers, such as BLE connection events and notifications, can temporarily relaunch the app into the background to perform short tasks, but iOS will soon put the app back to sleep.
iOS may also choose to terminate the app entirely, and apps that regularly hog resources in the background will be prioritized for termination. Therefore, IoT bridge apps are common candidates for frequent termination. To accomodate, your iOS app may need to take measures to store certain data and state information in non-volatile memory.
Android’s quite a bit easier in this regard. For an app that needs to stay alive while the user isn’t interacting with it, the app simply needs to launch a foreground service. This usually keeps the app running indefinitely unless the user force-kills it. It also eliminates a lot of the complexity of repeatedly reawakening or relaunching your app that is present in iOS.
However, Android is not without its own IoT concerns. Android runs on an exponentially more diverse array of hardware than the singularly owned iOS. This presents some unique challenges for BLE apps in particular, as certain functionalities such as BLE bonding can be unreliable or downright contradictory from one brand or model to another. Therefore, we recommend selecting ahead of time a reasonable subset for which to target full support, based on user demographics. In the US, this often includes all Google and most Samsung devices; apps targeting other regions may need to test on Xiaomi, OnePlus, Vivo, and other OEM devices.
Interruptions in connectivity are not edge cases
When designing any system, it’s natural to start with the happy path — data is transferred from point A to point Z in one smooth assembly line, and error handling is just details to be worked out later. But with each wirelessly connected component you introduce into your system, the probability that all the key data-carrying elements will be alive and able to communicate with each other at the same time drops significantly.
You must assume from the start that, at any time, the BLE peripheral will go out of range of the phone and stay there, the app will be killed, or the phone will lose internet connection. Set realistic expectations of what your product will be able to do and design your requirements and protocols to be as adaptive as possible to these common scenarios.
Repackage your data as needed, and aim to be asynchronous
Given that interruptions are guaranteed, don’t focus on how to get data from point A to point Z in one go. What works best for BLE transfers (peripheral to phone) may not be the same for HTTP transfers (phone to cloud), both in terms of packaging data and timing.
It’s obvious that the more data you need to send over the air, the longer it will take and the more risk of interruption. And of course, it’s important to design your data transfer protocol to reduce overhead. But at the end of the day, you may still find yourself needing to move a lot of data along the pipeline.
So, how should you package bulk data?
In the case of a mobile app IoT bridge, you need to optimize both for the BLE transfer from peripheral to phone, and the HTTPS bridge from phone to cloud.
Depending on the BLE stacks of the phone and peripheral, your ATT packet size over BLE might be limited to anywhere between 23 and 512 bytes. On the networking side, sticking to that same packet size would be incredibly inefficient. Furthermore, unlike with BLE, iOS networking APIs do not have the power to keep your app alive longer than the Apple-guaranteed 10 seconds.
In fact, Apple specifically recommends that you chunk bulk networking transfers in as few requests as possible, and has APIs that support scheduling a large request to run in the background at iOS’s discretion to optimize resource use. Upon request completion, your app will be relaunched in the background to perform its next steps. Try to take advantage of this by designing your system to chunk received BLE data and upload to the cloud in fewer networking requests.
iOS BLE bulk data transfer in the background — it can be done!
Assuming your iOS app is usually in a backgrounded or suspended state, you can wake your app up with a BLE notification (or a connection event). Once your app is reawakened, Apple documentation states you have about 10 seconds to perform any actions before being resuspended.
While we’ve found this to be true, we’ve also had success keeping the app alive longer using repeated notifications or indications, either to directly send data to the phone or else as a heartbeat that allows your app to perform custom data-fetch commands. Though we’ve utilized this design to transfer data over BLE in the background for up to a couple of hours at a time, it’s NOT a guaranteed behavior and Apple could decide to crack down on this in future versions of iOS.
If security is a concern, then it’s one of the biggest concerns
Depending on the level of security your product requires, it may amount to more than just a simple encryption layer you can add on top of your existing system. Security requirements may drastically shape your architecture decisions, and you may find that some tradeoffs in performance are inevitable when security must be prioritized above all else.
For instance, if you’re designing a medical device that deals with sensitive patient data, HIPPA concerns mean that not only will the default encryption provided by BLE and TLS probably not suffice, but you may not even be able to store said data directly in the app’s non-volatile memory. This means your asynchronous IoT bridge needs to be redesigned as more of a live, secure tunnel.
Given all the concerns and recommendations we’ve discussed so far, this does indeed present a far greater challenge. It is doable, however.
Using the notification heartbeat technique mentioned earlier, we’ve been able to successfully and reliably keep the app alive in the background long enough to perform custom security handshakes at both the BLE and networking levels. During these security handshakes, the app can then transfer bulk data synchronously and securely from BLE peripheral to phone to cloud.
If you’re implementing any custom encryption layers, even simple ones, your encryption algorithm choices need to be evaluated at a systems level as well. Some embedded System on Chips (SoCs) support certain calculation types faster than others. On the mobile side, not all algorithms are natively supported, and finding trustworthy and reliable third-party libraries for both iOS and Android to do the job instead can be difficult.
Hacks are not solutions
If, deep into development, you find out your system requirements were based on false assumptions about what mobile operating systems will allow, it’s tempting to look for hacks and workarounds. Restricted or unrelated APIs can certainly do the trick, keeping your app alive longer.
Don’t do this.
Yes, Apple’s Core Location may be able to wake your app from the dead under circumstances that Core Bluetooth cannot. Yes, you may also be able to disguise your intent cleverly enough that you could sneak it through the Apple review process.
But it’s not a risk you want to bet your entire product on, and every new app and OS update will threaten to stop your product in its tracks. Even Google is increasingly cracking down on these kinds of practices in Android, which was previously much more lenient in this regard.
Instead, be upfront with your user. Be clear in the permissions alerts why you need certain services. Educate them about why they shouldn’t force-kill your app.
While you may assume that most users can’t be educated out of bad practices, and to an extent, you’re right, it’s more effective than you might think. It’s also simple and cost-effective. And in the end, accept that some users are inevitably going to get in the way of their own best user experience. As long as you’ve made reasonable mitigation efforts, that’s okay.
Testing comes first…and last…and in the middle
In a multi-component system such as this, testing is inherently more complex. Budget ahead for the fact that system components will not be completed at the same time. Think carefully about how intra-systems testing will work, both before and after all components are in place.
For each core system component, you’ll want to build testing tools to mock interactions with external components. For BLE peripheral firmware, we often build Windows-based testing suites in Python. For mobile apps, we may mock both the BLE peripheral and the cloud directly in the app project code and integrate these layers into unit tests and live tests.
Mind you, these tools themselves need to be validated. QA testing will continue to become longer and more complex, but it’s critically important. And of course, connectivity-independent logic such as protocol message creation and parsing can and should be unit tested.
With great power consumption, comes great user disappointment
Battery life is a common concern when designing a system like this. From the mobile perspective, we’ve fortunately found that there is more leeway with battery life of modern day smartphones than, say, embedded devices. Even for apps that do relatively heavy data transfer and processing, the OS often does a pretty good job of accommodating.
Of course a big part of this, on iOS especially, is that the more your app tries to dominate the phone’s resources from the background, the more the OS may try to restrict your app from operating. That said, you should always aim to avoid unnecessary BLE operations.
For instance, iOS and Android provide BLE APIs to automatically reconnect to a known device when it’s next detected that is far more efficient than actively scanning from the app. Also, keep in mind that older phones may be more susceptible to poor battery life in general, and you may notice more variation in performance across the many brands and models of Android phones.
From the BLE peripheral perspective, there are standard practices that can help your device conserve power when possible, such as increasing latency and allowing the device to “sleep” when idle.