Table of Contents
Introduction to the Unity Android Bluetooth Low Energy Plugin
The Unity Android Bluetooth Low Energy Plugin (what a mouthful) was a project that originally started as an assignment for an internship. This internship involved connecting wearables (fitness trackers, step counters, etc.) to a Virtual Reality headsets that the company offered in combination with their own VR application made in Unity.
So my first step was to find a common connection point that most wearables have in common. While some wearables had Wi-Fi capabilities or could even communicate with ANT, the majority of the wearables that my company had interest in supported Bluetooth Low Energy, which is luckily immensely documented! So with this information, I thought it’d be easy cruising from here. Just pick up some documentation, get started and…!
Unfortunately that wasn’t the case. Unity does not support Bluetooth out-of-the-box, which makes connecting BLE devices an even bigger problem. But it wouldn’t be an internship if all went well from the get-go, so I searched for articles to get started. My (internship’s) goal was to create a plugin that my company could drag and drop into their project to connect to BLE devices, get information and write data to the connected devices, all with an easy to use C# API.
The Problem
As described in the previous chapter; the original problem that arose pretty quickly was that Unity does not support Bluetooth natively, including it’s Android environment, so to add LE support to something that doesn’t support B, creating a BLE solution becomes a bit tougher.
My first step was to look at other existing solutions online, coming across several different projects that claimed to support BLE within Unity on an application build to run on Android devices, though only for a specific set of devices, mostly being passion projects involving custom build BLE servers that use Arduino, or other micro-controllers, as their foundation.
While informative, the projects (being passion projects in nature) did not contain any useful documentation or explanations about how their platform functions, nor how they got BLE to successfully work within Unity. This was until I found a solution offered in an article written by jcmurray2012, a developer that used to write blogs on the Blackberry Developers Blog, where (although most content lost due time) his diary details how he used a shared library in C that communicates with the Blackberry Bluetooth ecosystem and how it communicates this information back to Unity.
While C is not my preferred language of choice, his idea of using a shared library to access an OS’s Bluetooth capabilities got my thinking and brought me to a solution.
The solution
The solution was to create a shared library in Java as a jar
plugin which accesses the native Android Bluetooth environment. This library will then collect information and send them as JSON back to Unity using the UnitySendMessage
method, which can send information back to the current loaded scene through Unity’s nifty messaging system, which is accessible from any MonoBehaviour
that’s attached to a GameObject
.
So all there is to do is create bindings in the Java library that take a certain set of parameters, then passing the received back to Unity using Unity’s messaging system. Calling the methods inside the library from Unity is quite simple and can be done using the Android JNI
.
Designing
Since BLE isn’t based on a stream-like manner of communicating between server and client (like it’s regular Bluetooth family), communicating with it should be more of a queue based system, where actions that needs to be performed on the server are queued up on the client’s side, then executed sequentially.
From here, the idea of having a managed queue on the .NET side of things would be the easiest (mostly because the .NET side shouldn’t make unnecessary calls through the JNI to check a state). With this came the idea to apply a command-based pattern, to create a queue of commands and execute them sequentially, or keep them in parallel if that’s supported (like with discovering devices).
So each action that can be performed on BLE devices from a client would get it’s own command, which is then also bind to a method inside the Java library. This causes the logic inside the Unity plugin to be fairly small and only consist of information that the bound method would need and what the name of that method would be, so the abstract base of the command would look like this:
namespace Android.BLE.Commands{ public abstract class BleCommand { public float Timeout { get => _timeout; } protected float _timeout = 5f;
public readonly bool RunParallel = false; public readonly bool RunContinuously = false;
public BleCommand(bool runParallel = false, bool runContinuously = false) { RunParallel = runParallel; RunContinuously = runContinuously; }
public abstract void Start();
public virtual void End() { }
public virtual void EndOnTimeout() => End();
public virtual bool CommandReceived(BleObject obj) { return false; } }}
This is the base class that makes the Unity plugin tick. Every command that performs a BLE related task, like discovering devices, subscribing to a characteristic or connecting to a device is handled by a command that inherits from this base. These commands will then be queued to the BleManager
, which behaves as the Singleton
that handles the incoming communication from the Java library and passes it through the active BleCommand
s in the queue.
In the end, with a queue in the BleManager
that keeps track of the commands that need to executed in a queue-like fashion and a List
that keeps track of parallel and continuously running commands, the way to interact with a device would be:
// Create a new subscribe command with a device UUID, a service- and a characteristic short-hand UUIDvar subscribeToCharacteristic = new SubscribeToCharacteristic(_deviceUuid, "1101", "2101", (byte[] data) => { // Every time the characteristic gives new information, it'll be logged Debug.Log(Encoding.UTF8.GetString(data));});// Queue them to the BleManager, which will run it continuously and in parallelBleManager.Instance.QueueCommand(_subscribeToCharacteristic);
This method of providing a few provided commands, but leaving room for creating your own command (by keeping the BleCommand
abstract and public) should create an easy to use plugin that allows the user to expand upon it in their own way. During my internship I used an earlier version of this plugin to connect to a Xiaomi MiBand 3, which proved to be difficult, since it requires a set-pattern to authenticate before you can subscribe to- or read data from any characteristic. By creating a custom AuthenticateMiBand3
command that can easily be queued up if a discovered device had the same name as a MiBand 3, it made it extremely easy to authenticate the device, without needing an entire workaround or extra steps in the Java library.
Technical Background
Now to get a bit more technical, since what’s described above is build on top of a way of communicating the information between Android’s BLE- and Unity’s .NET ecosystem, there first needs to be a way to make this happen in the first place. I’ve already dropped a few hints as to how it works on the surface, but I’ll get a bit more technical here.
Communicating to Android
As I’ve mentioned before, the Unity plugin communicates to Android’s BLE capabilities using the Android JNI. The JNI allows Unity to interface to the Android Java VM using C# code; basically a communication layer between the Android VM that runs Java code for the application and Unity’s C# code, including any Java library that gets preloaded with your Unity application.
Now the Android JNI allows us to communicate from Unity to Android Java, so from here there are two paths to take:
- Create all BLE logic within Unity using C# by accessing Android’s BLE using methods available in the JNI.
Which creates a heavy burden on the JNI to constantly communicate from- and to Unity and Android. Since the JNI is not particularly fast, this will bottleneck quick successions of BLE interactions. Also, this will include a log of “guess” work, since Android allows it’s internal API to be accessed through Java / Kotlin code, so inside Unity you’d have to create instances of the correct classes, call their methods and pass along all the right parameters without any support for syntax highlighting, error handling, etc. So not a recommended way.
- Create all BLE logic that interacts with Android in a separate library that can easily access Android’s BLE API, then bind methods that expose that BLE API from Unity’s C# side.
Which is what I ended up doing. By creating another singleton in the Java library that Unity can easily call using the JNI, the commands can bind to the corresponding methods and read the callbacks that the BleAdapter
receives, process those values, and expose them using a C# Action
that’s passed along with the constructor of the command.
For a quick example, I’ll be showcasing the code from start to finish that allows a user to search for devices using commands and I’ll highlight the JNI parts on the way.
- Initializing the
BleManager
public void Initialize(){ if (!_initialized) { // Adapter...
if (_bleLibrary == null) { // Accesses a reference to the Java class 'UnityAndroidBLE' AndroidJavaClass librarySingleton = new AndroidJavaClass("com.velorexe.unityandroidble.UnityAndroidBLE"); // Calling the static 'getInstance' method returns a 'AndroidJavaObject' // This gives us an instance that contains all the methods to communicate with Android's BLE _bleLibrary = librarySingleton.CallStatic<AndroidJavaObject>("getInstance"); } }}
This gives the BleManager
a way of calling the methods that are available in the Java library using a AndroidJavaObject
. With this, every time a request comes in from a BleCommand
to call a method of the Java library, it can get passed to the Java library.
- Queueing a
DiscoverDevices
command
var discoverDevices = new DiscoverDevices(OnDeviceFound, 10000);BleManager.Instance.QueueCommand(discoverDevices);
// ...
private void OnDeviceFound(string device, string name) => Debug.Log($"Found device {device} with name {name}")
With this, the a DiscoverDevices
command is queued and will be executed if there aren’t any other commands queued. Since this isn’t the case, DiscoverDevices
’s Start()
method will be called immediately.
public override void Start() => BleManager.SendCommand("scanBleDevices", _discoverTime);
This line communicates to the BleManager
that it should call the Java method with the name scanBleDevices
, while passing along the parameter _discoverTime
, which details how long it should scan.
internal static void SendCommand(string command, params object[] parameters){ // ... _bleLibrary?.Call(command, parameters);}
Which, in turn, fires off this simple method to the Android Java library. On the Java side of things, a runner will be created that scans for devices in the vicinity and passes those back to Unity using a BleObject
(more about this in Communicating back to Unity).
When a BleObject
is received, the BleManager
will pass this bundle of information along every currently running BleCommand
in the parallel or current executing pool. In turn, each command will respond with either true
or false
signaling if they’ve used the information and that it’s only meant for that specific device.
public override bool CommandReceived(BleObject obj){ switch (obj.Command) { case "DiscoveredDevice": OnDeviceDiscovered?.Invoke(obj.Device, obj.Name); break; // ... }
return string.Equals(obj.Command, "FinishedDiscovering");}
The DiscoverDevices
will invoke the passed along Action
and provide the device’s UUID and name. With the UUID, the user could queue up a ConnectToDevice
command to connect to any of the devices that were discovered, or leave it up for the user.
Communicating back to Unity
So how does this information get back to Unity? Well I’ve already said it before, but it’s with Unity’s messaging system between MonoBehaviours
. Every Android library that gets compiled with your Unity application gets access to a library Unity containing some utilities, such as getting access to the application’s Context
, which is needed to scan for devices. But we’re not interested in that, what we’re looking for is the method provided by the UnityPlayer
singleton, which is UnitySendMessage
.
With this, a library can access the Unity messaging system, but only by sending information, not receiving it (since receiving requires it to be a MonoBehaviour
in a Unity Scene
). But since we’ve already got the receiving part done (JNI), we can fully use this to send BLE information back to Unity.
The BleObject
is a Serializable object in the Java library, which contains a set of properties with which the Unity side of things can manage all it’s interactions.
public class BleObject { public String command;
public String device; public String name;
public String service; public String characteristic;
public String base64Message;
public boolean hasError = false; public String errorMessage;
// ...}
Every time a method or action should provide some information to Unity, a BleObject
is created, serialized to JSON, then send off to Unity. Since Unity doesn’t magically know where to send the JSON data to, the message is send with a name of a GameObject
and the method name of the MonoBehaviour
that will receive the data.
public static void sendToUnity(BleObject obj) { UnityPlayer.UnitySendMessage(mUnityBLEReceiver, mUnityBLECommand, obj.toJson());}
Inside the Java library, a static
method called sendToUnity
is available, which accepts a BleObject
that should be send. The mUnityBLEReceiver
and mUnityBleCommand
are defined with the exact name of the GameObject
and the method name that will receive this message. In the case of my Unity plugin, this will is done in the BleAdapter
class.
namespace Android.BLE{ public class BleAdapter : MonoBehaviour { // Properties ...
private void Awake() => gameObject.name = "BleAdapter";
public void OnBleMessage(string jsonMessage) { BleObject obj = JsonUtility.FromJson<BleObject>(jsonMessage); if (obj.HasError) { OnErrorReceived?.Invoke(obj.ErrorMessage); UnityOnErrorReceived?.Invoke(obj.ErrorMessage); } else { OnMessageReceived?.Invoke(obj); UnityOnMessageReceived?.Invoke(obj); } }
// Delegates ... }}
This MonoBehaviour
will always set it’s own name to BleAdapter
and can receive the BleObject
as JSON through the OnBleMessage
method. So the Java library will always send it’s JSON data through this method. This adapter is always made with the BleManager
, unless it already exists, and will subscribe to it’s events. This way it can send the BleObject
through all the relevant commands.
Conclusion
In short: the Unity plugin communicates with a Java library through the Android JNI. The Java library has access to all of Android’s BLE API’s, which it exposes using methods that the Unity plugin can bind to with it’s commands. The Java library can communicate it’s data back to Unity using it’s messaging system. Both of these methods combined create a two-way communication which makes the entire Unity package tick.
This project was part of my thesis for SyncVR Medical, more details about this internship can be found on my Profile, but I’ve rewritten it to make it publicly available, as to not infringe on the company’s policies by releasing the library that I’ve build for them.
If you’ve got any questions, you can always reach out to me, I’m happy to answer any questions you have.
Repositories
The repositories mentioned in this blog. All the code referenced in this blog post come from these two repositories. Give them a star if you like them, maybe fork them if you have an idea on how to improve them.
Velorexe/Unity-Android-Bluetooth-Low-Energy
Velorexe/Unity-Android-Bluetooth-Low-Energy-Java-Library