Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Next »

Unity Version: 2020.3.27f1

Supported ICD Rev: G
Supported VictoR Firmware Revision:

  • Application Firmware Rev: 03.054

  • Co-Processor Firmware Rev: 01.001

  • Kernel Module Firmware Rev: 01.001

  • BLE Firmware Rev: 03.017

Supported Build Types:

  • Universal Windows Platform

  • Android
    NOTE: As a result of using the Windows BLE stack, Unity projects built with the VictoR SDK can only be run on the target platform during development. Running the application within the editor is currently not supported.

To enable development for the VictoR device in the Unity engine, a Unity package is available which contains the relevant Android and Windows libraries used to communicate with a VictoR device, along with example C# scripts and Unity gameobjects and example scene that demonstrate one of many ways users could incorporate the VictoR SDK into their Unity project. The following will walk users through importing the Unity package into their project and give a general overview of the provided scripts, gameobjects, and example scene.

Importing the VictoR SDK Unity Package

The VictoR SDK Unity package is of .unitypackage file type. Users can import this package into an existing Unity project by navigating to Assets/Import Package/Custom Package:

Once the Unity package has been imported, users should have a set of new assets available in their project’s Assets folder. This should include:

  1. A plugins folder containing the necessary libraries to build either a UWP application or an Android application

  2. Prefabs for a basic UI to allow for…

    1. Scanning for VictoR devices

    2. Connecting to scanned devices

    3. Reading the Device Configuration and Device Information service on the device

    4. Writing to the Device Configuration service

    5. Disconnecting from connected VictoR devices

  3. Scripts utilizing the Windows and Android VictoR SDK, along with prefabs using said scripts

  4. An example scene bringing the above items together for a buildable UWP or Android application

Prefabs and Scripts

The prefabs for the example project can be split between two spaces: UI and VictoR SDK.

VictoR SDK Prefabs

The VictoR SDK prefab makes use of the libraries contained in the Plugins folder to communicate with a VictoR device, through the attached VictorController script. The libraries used are determined by the platform the project has been set to. Currently, Universal Windows platform and Android are supported. The VictorController script also serves as both an example on how to use the Victor SDK for both platforms and is the primary script users should utilize when interfacing with the VictoR device from within a Unity project. It’s primary function is to use the appropriate device manager (Windows or Android SDK), dependent on the platform the application is running on. Once the appropriate device manager is instantiated, calls to Scan, Connect, Read/Write, and Subscribe/Unsubscribe can be made through the VictorController. The following will walk through how the Windows and Android DeviceManagers are handled through a Unity project.

UWP

When building a project for UWP, the VictorSDK classes can be used directly to communicate with VictoR devices. As a result, the VictorController script simply uses these classes directly. Users can reference the Victor SDK Getting Started Guide for Windows for example code on how to work with the Windows edition of the SDK.

Android

When building a project for Android, the VictorSDK for Android requires an additional layer of bindings in order to interface with the SDK in C#. The VictorAndroidBLEDeviceManager script under the Scripts folder provides these bindings and is what users should leverage when making calls to the Android SDK. The example code below simply gives users a look into how these bindings are done. The core of this layer resides in Unity’s AndroidJNIModule in the UnityEngine library. This allows for using android java objects and making calls on said objects. The following demonstrates how these calls are made as they relate to the VictoR SDK:

Scanning For Devices

In order to scan for devices using the Android SDK from within a Unity project, users must first create an AndroidJavaObject representing the Java SDK’s VictorBleDeviceManager. In the example script, this is done inside the constructor. Once the device manager object has been created, the actual scan method is able to use the AndroidJavaObject to call the “scanForDevices” method. As this is a CompletableFuture on the Java side, this call is made inside of a Task on the C# side. An important item to note is that in order to properly call this function, users must first attach the current thread to a Java VM. Remember that the Unity package contains a VictorAndroidBLEDeviceManager script that has performed this binding already for the user.

public class VictorAndroidBleDeviceManager : IVictorDeviceManager
{
    AndroidJavaClass m_AJCUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    AndroidJavaObject m_AJOActivity;
    AndroidJavaObject m_AJODeviceManager;
    
    public VictorAndroidBleDeviceManager()
    {
        m_AJCUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        m_AJOActivity = m_AJCUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        m_AJODeviceManager = new AndroidJavaObject("com.fn.victorcommunications.VictorBleDeviceManager", m_AJOActivity);
    }
    
    public async Task<HashSet<VictorDeviceInfo>> ScanForDevices()
    {
        AndroidJavaObject ajoScannedDevices = await Task<AndroidJavaObject>.Run(() =>
        {
            AndroidJNI.AttachCurrentThread();
            AndroidJavaObject ajo = m_AJODeviceManager.Call<AndroidJavaObject>("scanForDevices");
            return ajo.Call<AndroidJavaObject>("get");
        });
    
        return ToHashSet(ajoScannedDevices);
    }
}

Now that this function has been defined for an Android project, users can then call this scan function directly in their project’s code

public async Task Scan(int scan)
{
    VictorDeviceScanParameters scanParameters = new VictorDeviceScanParameters();
    scanParameters.ScanTime = scan;
    scanParameters.DeviceName = "victor";
    scanParameters.CaseSensitive = false;

    var result = await m_deviceManager.ScanForDevices(scanParameters);
}

Connecting To A Device

Connecting to a device follows a similar pattern as scanning for devices. A binding to the Android SDK must first be defined to call the connect method. This is already defined in the VictorAndroidBLEDeviceManager script

public async Task<bool> Connect(string deviceAddress)
{
    return await Task<bool>.Run(() =>
    {
        AndroidJNI.AttachCurrentThread();
        AndroidJavaObject ajo = m_AJODeviceManager.Call<AndroidJavaObject>("connect", deviceAddress);
        return ajo.Call<AndroidJavaObject>("get").Call<bool>("booleanValue");
    });
}

The most important item of note here is the return of the function. The Android SDK’s Connect method returns a Boolean wrapped in a CompletableFuture.

@Override
@RequiresPermission(value = "android.permission.BLUETOOTH_CONNECT")
public CompletableFuture<Boolean> connect(String deviceAddress)
{
    VictorBleDevice device = devices.get(deviceAddress);
    if (device == null)
    {
        logAttemptAborted(deviceAddress, "Connect");
        return CompletableFuture.completedFuture(false);
    }
    else
    {
        return device.connect();
    }
}

In order to retrieve this return in the java binding, it is important to call the CompletableFuture’s “get” method to retrieve the return value, as well as the “booleanValue” method of the Java side Boolean to retrieve the underlying bool. Now that this function has been defined in our Unity project, we can simply call the Connect function on our VictorAndroidBLEDeviceManager object.

public async Task<bool> Connect(string deviceID)
{
    return await m_deviceManager.Connect(deviceID);
}

Callbacks

Callbacks when using the Android SDK requires not only a binding to the underlying Java call, but also defining the Callback abstract class in the Android SDK. Once again, the VictorAndroidBLEDeviceManager provides an example for how both of these are handled. First, the VictorDeviceCallback in the Android SDK is defined on the C# side. In the provided example, the VictorEventHandler and VictorEventHandlerMap in the Windows SDK is leveraged to manage passed in events.

class VictorDeviceCallback : AndroidJavaProxy
{
    public Action<VictorDeviceInfo> m_DeviceFoundCallback;

    public Dictionary<string, VictorEventHandler<VictorConnectionStateChangedEventArgs>> m_ConnectionStateChangedEventDict = new Dictionary<string, VictorEventHandler<VictorConnectionStateChangedEventArgs>>();

    public Dictionary<string, VictorEventHandlerMap> m_eventHandlerMap = new Dictionary<string, VictorEventHandlerMap>();

    public VictorDeviceCallback() : base("com.fn.victorcore.VictorDeviceCallback") {}

    public void onDeviceFound(AndroidJavaObject ajoDeviceInfo)
    {
        VictorDeviceInfo victorDeviceInfo = new VictorDeviceInfo(ajoDeviceInfo.Call<string>("getName"),
            ajoDeviceInfo.Call<string>("getAddress"));
        m_DeviceFoundCallback?.Invoke(victorDeviceInfo);
    }

    public void onConnectionStateChange(AndroidJavaObject ajoDeviceInfo, bool connected)
    {
        VictorDeviceInfo victorDeviceInfo = new VictorDeviceInfo(ajoDeviceInfo.Call<string>("getName"),
            ajoDeviceInfo.Call<string>("getAddress"));

        if (m_ConnectionStateChangedEventDict.ContainsKey(victorDeviceInfo.Address))
        {
            m_ConnectionStateChangedEventDict[victorDeviceInfo.Address]?.OnEvent(this, new VictorConnectionStateChangedEventArgs(victorDeviceInfo, connected));
        }
    }

    public void onAttributeChange(AndroidJavaObject ajoDeviceInfo, AndroidJavaObject ajoUUID, byte[] data)
    {
        VictorDeviceInfo victorDeviceInfo = new VictorDeviceInfo(ajoDeviceInfo.Call<string>("getName"),
            ajoDeviceInfo.Call<string>("getAddress"));
        Guid guid = new Guid(ajoUUID.Call<string>("toString"));
        VictorAttributeChangedEventArgs victorAttributeChangedEventArgs = new VictorAttributeChangedEventArgs(victorDeviceInfo, guid, data);

        if (m_eventHandlerMap.ContainsKey(victorDeviceInfo.Address) && m_eventHandlerMap[victorDeviceInfo.Address].Contains(guid))
        {
            m_eventHandlerMap[victorDeviceInfo.Address]?[guid]?.OnEvent(this, victorAttributeChangedEventArgs);
        }
    }
}

Now that this callback has been defined, this object can be passed to the Android SDK’s version of the Subscribe and Unsubscribe functions.

public async Task<bool> SubscribeAttributeChange(string deviceAddress, Guid attribute, EventHandler<VictorAttributeChangedEventArgs> callback)
{
    bool success = await Task<bool>.Run(() =>
    {
        AndroidJNI.AttachCurrentThread();
        AndroidJavaObject ajoUUID = new AndroidJavaClass("java.util.UUID").CallStatic<AndroidJavaObject>("fromString", attribute.ToString());
        return m_AJODeviceManager.Call<AndroidJavaObject>("subscribeAttributeChange", deviceAddress, ajoUUID, m_victorDeviceCallback).Call<AndroidJavaObject>("get").Call<bool>("booleanValue");
    });

    if (success)
    {
        if (!m_victorDeviceCallback.m_eventHandlerMap.ContainsKey(deviceAddress))
        {
            Dictionary<Guid, VictorEventHandler<VictorAttributeChangedEventArgs>> eventHandlerDict = new Dictionary<Guid, VictorEventHandler<VictorAttributeChangedEventArgs>>();
            eventHandlerDict.Add(attribute, new VictorEventHandler<VictorAttributeChangedEventArgs>());
            m_victorDeviceCallback.m_eventHandlerMap.Add(deviceAddress, new VictorEventHandlerMap(eventHandlerDict));
        }

        if (m_victorDeviceCallback.m_eventHandlerMap[deviceAddress].Contains(attribute))
        {
            m_victorDeviceCallback.m_eventHandlerMap[deviceAddress][attribute].VictorEvent += callback;
        }
    }

    return success;
}

With both items defined, users can create their own Event to pass to the VictorAndroidBLEDeviceManager to be notified of attribute changes (for attributes that have the Notify flag enabled), connection state events, and device found events. It is important to remember that these callbacks will occur in a separate thread and should be especially noted if working on Unity Gameobjects that rely on these callbacks for modifications, as many operations on Unity Gameobjects can only be done on the main thread.

UI Prefabs

The UI prefab consists of a collection of controls that allow for scanning for devices, connecting/disconnecting to/from the device, and reading/writing to the device. Additionally, a Log prefab is used by the VictorUIManager script to print out a number of informational, warning, and error logs in the scroll view of the UI. Any reads made on the device will be printed out in the scroll view, as well as notifications of the state of a scan, connect, or disconnect call.

Building The Unity Project

Windows

Latest Windows SDK Supported: 10.1.19041.685

Below lists the settings used to build and run a UWP project in Unity when using the Victor SDK package.

NOTE: When attempting to build an app that intends to communicate with the device over USB, it is important that permissions for accessing serial devices has been added to the .appxmanifest for the resulting UWP build. Subsequent builds in the same folder will not overwrite this entry, however it is important to keep this in mind for any fresh builds.

Android

Latest Android SDK Supported: 32

Below lists the settings used to build and run a an Android project in Unity when using the Victor SDK package. It is important to note that a device plugged into the development machine has been detected and will be the target device the application runs on once the build succeeds and deploys to the device.

  • No labels