Android USB Communications Interface Design

Introduction

The VictoR SDK will provide end-users with an Android Communications library that enables them to communicate with a VictoR device over various communication protocols within their Android-based applications.. One of the supported protocols of this library will be the Universal Serial Bus (USB) protocol. The following design explains how support for the USB protocol will be implemented for Android.

Requirements

Related Jira Issues

https://unity3d.atlassian.net/browse/FNSDKEXT-165

Constraints

Performance

Calls to a VictoR device are time intensive I/O operations. The Communications Interface should not block and force the end-user to wait for those operations to complete. It should instead return the results asynchronously through callbacks.

Operating System

This Communications Interface implementation will be provided as an Android Library written in Java and compiled into an Android Archive (AAR), enabling VictoR device application development for hardware targets running Android 10 and above.

Ease of Use

Interacting with a USB device is a complicated multi-step process for almost every interaction. The Communications Interface should abstract this process away from the end-user so that they don’t need to know all of the OS-specific implementation details for communicating with a VictoR device over USB. All they have to do is use the simple set of functions provided by the interface.

Detailed Design

Architecture

The Android USB Communications API will be composed primarily of two classes: VictorUsbDevice and VictorUsbDeviceManager. These classes are implementations of the abstract VictorDevice and VictorDeviceManager interfaces provided by the VictoR Core library. The VictorUsbDevice class represents the connection interface to an individual VictoR device over a physical USB cable. This class handles all interactions with that device over the USB communications protocol, including connecting/disconnecting, reading/writing data, and subscribing/unsubscribing from events for that device like attribute (characteristic) changes and connection state changes. These interactions are implemented using a combination of the USB API provided by the Android SDK and the API provided by the USB Serial for Android library, as discussed below in the Dependencies section. The details of the specific function calls required are outlined below in the Implementation section. The VictorUsbDeviceManager manages all VictoR device connection interfaces through VictorUsbDevice objects that it creates, maintains, and destroys as needed. This class acts as the primary interface for end-users to discover devices and manage connections to them. In addition, the VictorUsbDeviceManager provides a wrapper around the functions that the VictorUsbDevice class offers, requiring only that the end-user provide the address for the specific device they are interested in interacting with, so that they don’t need to have any knowledge of the underlying organization of the devices within the manager. The class diagram in Figure 1 below illustrates the architecture of the Android USB Communications API.

Figure 1: Android USB Communications API Class Diagram

 

Dependencies

The Android SDK does not provide out of the box support for communication with a serial device over USB. It only provides several generic USB device classes that can be used to build a serial device abstraction. Fortunately, the USB Serial for Android library already provides a serial device abstraction using these classes. This library will be utilized to reduce development time and interface complexity.

Implementation

The following outline describes the function calls to the Android SDK and USB Serial for Android library that will be used, as well as a general outline of the algorithm for the function using them, for all of the functions provided by the Android USB Communications API.

VictorUsbDevice

  • Connect

    • Open a UsbDeviceConnection on the UsbSerialDriver’s UsbDevice using UsbManager.openDevice

    • Get the correct UsbSerialPort from the UsbSerialDriver using UsbSerialDriver.getPorts

      • Most devices only have one port

      • Preliminary prototyping has shown that the VictoR device exposes two ports

      • The correct port appears to be the 2nd port (port 1, not port 0)

    • Open the port, providing the open UsbDeviceConnection using UsbSerialPort.open

  • Disconnect

    • As with connect, get the correct UsbSerialPort from the UsbSerialDriver using UsbSerialDriver.getPorts

    • Close the port using UsbSerialPort.close

      • This also closes the open UsbDeviceConnection

  • Read

    • Send a read request using UsbSerialPort.write

    • Read the response using UsbSerialPort.read

  • Write

    • Send a write request using UsbSerialPort.write

    • Verify the write was successful by reading the response using UsbSerialPort.read

  • Subscribe to Connection State Changes

    • Add the provided user callback to the set of connection state change callbacks

      • When the device connects or disconnects, invoke each callback in the set with the corresponding connection state

    • To detect connection state changes, a thread will be needed to continuously poll the serial port status

      • If the device is disconnected, an attempt to reconnect should be made.

  • Unsubscribe from Connection State Changes

    • Remove the provided user callback from the set of connection state change callbacks

  • Subscribe to Attribute Changes

    • Send a subscribe request using UsbSerialPort.write

    • Verify the subscribe was successful by reading the response using UsbSerialPort.read

      • This is not possible as it appears the VictoR firmware doesn’t currently support this.

      • Success will instead indicate that the request was successfully communicated to the device, not necessarily that the device acknowledged it.

  • Unsubscribe from Attribute Changes

    • Send an unsubscribe request using UsbSerialPort.write

    • Verify the unsubscribe was successful by reading the response using UsbSerialPort.read

      • This is not possible as it appears the VictoR firmware doesn’t currently support this.

      • Success will instead indicate that the request was successfully communicated to the device, not necessarily that the device acknowledged it.

VictorUsbDeviceManager

  • Scan for Devices

    • Using the Android SDK:

    • Using the USB Serial for Android library:

      • Create a ProbeTable with the Vendor ID and Product ID of the VictoR device

      • Create a custom UsbSerialProber using this custom ProbeTable

      • Use this custom UsbSerialProber to find all UsbSerialDrivers using UsbSerialProber.findAllDrivers

        • findAllDrivers is not a timed, asynchronous operation

        • UsbSerialProber has no concept of a scan time

        • This class will ignore the scan time field of the VictorDeviceScanParameters provided to it

        • API documentation for this class will make note of this fact

      • Track all UsbSerialDrivers discovered in a map keyed on the device name (address) using UsbDevice.getDeviceName()

    • Use the Android Intent system to request permission to communicate with the UsbDevice within each UsbSerialDriver as outlined here

  • Get all Devices

    • Maintain a map of all UsbSerialDrivers that have been scanned, connected, or disconnected at any point

  • Get Scanned Devices

    • Maintain a map of all UsbSerialDrivers that have been discovered during the last scan

    • Clear the map each time a scan is performed

  • Get Connected Devices

    • Maintain a map of all UsbSerialDrivers that are currently connected

  • Connect

    • Find the specified device from the map of connected devices

    • If it can be found, then call Device.Connect

  • Disconnect

    • Find the specified device from the map of connected devices

    • If the device is found, then call Device.Disconnect

  • Read

    • Find the specified device from the map of connected devices

    • If the device is found, then call Device.Read

  • Write

    • Find the specified device from the map of connected devices

    • If the device is found, then call Device.Write

  • Subscribe to Connection State Changes

    • Find the specified device from the map of connected devices

    • If the device is found, then call Device.SubscribeConnectionStateChange

  • Unsubscribe from Connection State Changes

    • Find the specified device from the map of connected devices

    • If the device is found, then call Device.UnsubscribeConnectionStateChange

  • Subscribe to Attribute Changes

    • Find the specified device from the map of connected devices

    • If the device is found, then call Device.SubscribeAttributeChange

  • Unsubscribe from Attribute Changes

    • Find the specified device from the map of connected devices

    • If the device is found, then call Device.UnsubscribeAttributeChange

Unity Project Integration

  • Unity natively supports integration of AAR plug-ins and JAR plug-ins.

  • The Android USB Communications API will be imported as an AAR plug-in and wrapped in C# scripts using the AndroidJavaObject and AndroidJavaClass classes.

  • The USB Serial For Android library will be imported as a JAR plug-in and referenced by the Android USB Communications AAR plug-in.

  • Any Java Future<T> objects will be translated to C# Task<T> objects

  • Any Java Callback Interfaces will be translated to C# Action<T> function calls.

Glossary

Term

Description

Term

Description

API

Application Programming Interface

AAR

Android Archive

End-User

A developer utilizing the VictoR SDK libraries for software application development

I/O

Input/Output

OS

Operating System

SDK

Software Development Kit

USB

Universal Serial Bus

UUID

Universally Unique Identifier