Skip to content

Discovering and Connecting to Nix Devices

Regardless of device type, each unique Nix device can be represented by an object that conforms to the IDeviceCompat interface (see Kotlin or Java APIs). To obtain instances of the device object, they need to first be discovered using the DeviceScanner (see Kotlin or Java APIs). They can also be recalled at a later time directly using their id property (for Bluetooth connected devices only).

Device discovery

The DeviceScanner implements the IDeviceScanner interface (see Kotlin or Java APIs) and searches for nearby Nix devices using Bluetooth. The DeviceScanner will also report devices connected via USB. Scanner events are reported via:

  • OnScannerStateChangeListener:
    • Optional callback interface which reports when the scanner starts and stops searching for devices.
    • API reference: Kotlin or Java.
    • This is set using setOnScannerStateChangeListener().
  • OnDeviceFoundListener:
    • Callback interface which is called by the scanner each time that an IDeviceCompat instance has been found.
    • API reference: Kotlin or Java.
    • Note that this will be called both when a new device is found and when the RSSI/signal strength of a previously discovered device has changed. It is possible to use the id parameter as a means to uniquely identify discovered devices.

Note

Devices can only be discovered or recalled while your SDK licence is in an LicenseManagerState/ACTIVE state; see LicenseManager.Shared.getState() for Kotlin or Java. Only device types supported by your license can be discovered and connected; see LicenseManager.Shared.getAllowedDeviceTypes() for Kotlin or Java.

To use the DeviceScanner:

  • Initialize a scanner instance
  • Optionally set a OnScannerStateChangeListener, if it is desired to track when the scanner starts and stops a device search.
  • Check the scanner state, which will be DeviceScannerState/IDLE if properly initialized.
    • The scanner state is available via IDeviceScanner.getState() (see Kotlin or Java APIs).
    • See a list possible states for Kotlin or Java.
    • If the state is DeviceScannerState/ERROR_LICENSE, check LicenseManager.Shared.getState() for further details.
  • Start the scanner using start() (see Kotlin or Java APIs).
    • By default, the scanner will search for a period DEFAULT_GENERAL_SCAN_PERIOD_MS, but this interval can be overridden by providing an argument for scanPeriodMs.
  • Observe discovered devices via the onScanResult() in the OnDeviceFoundListener interface. At this stage, it is valid to check a limited number of parameters on the device object:
    • getId(): Device identifier string (see Kotlin or Java APIs).
    • getRssi(): Device signal strength (-127 to 0). Will be 0 for USB connected devices (see Kotlin or Java APIs).
    • getInterfaceType(): Indicates whether the device was found via Bluetooth or USB connection (see Kotlin or Java APIs).
    • getType(): Indicates the specific device type (see Kotlin or Java APIs).
    • getName(): Full name of the device (e.g. 'Nix Spectro 2', see Kotlin or Java APIs).
  • The device search will run for the specified duration, after which onScannerStopped() is called. The device search can also be manually stopped using stop().
// Define the OnScannerStateChangeListener
val scannerStateListener = object : OnScannerStateChangeListener {
    override fun onScannerStarted(sender: IDeviceScanner) {
        // Scanner has started ...
    }

    override fun onScannerStopped(sender: IDeviceScanner) {
        // Scanner has stopped ...
    }
}

// Define the OnDeviceFoundListener
val deviceFoundListener = object : OnDeviceFoundListener {
    override fun onScanResult(sender: IDeviceScanner, device: IDeviceCompat) {
        // Nearby device found
        // Handle discovery here ...

        // Valid to query some parameters now:
        Log.d(TAG, String.format(
            "Found %s (%s) at RSSI %d", 
            device.id, 
            device.name, 
            device.rssi)
        )
    }
}

// Application context, in a Fragment use getContext()
val context: Context = applicationContext

// Initialize the scanner
val scanner = DeviceScanner(context)
scanner.setOnScannerStateChangeListener(scannerStateListener)

// Start the scanner
if (scanner.state == IDeviceScanner.DeviceScannerState.IDLE) {
    scanner.start(listener = deviceFoundListener)
} else {
    // Check the error state and handle accordingly ...
}    
// Define the OnScannerStateChangeListener
IDeviceScanner.OnScannerStateChangeListener stateChangeListener = 
    new IDeviceScanner.OnScannerStateChangeListener() 
{
    @Override
    public void onScannerStarted(@NonNull IDeviceScanner sender) {
        // Scanner has started
        // ...
    }

    @Override
    public void onScannerStopped(@NonNull IDeviceScanner sender) {
        // Scanner has stopped
        // ...
    }
};

// Define the OnDeviceFoundListener
IDeviceScanner.OnDeviceFoundListener deviceFoundListener = 
    new IDeviceScanner.OnDeviceFoundListener() 
{
    @Override
    public void onScanResult(
        @NonNull IDeviceScanner sender,
        @NonNull IDeviceCompat device
    ) {
        // Nearby device found
        // Handle discovery here ...

        // Valid to query some parameters now:
        Log.d(TAG, String.format(
            "Found %s (%s) with RSSI %d", 
            device.getId(), 
            device.getName(), 
            device.getRssi()));
    }
};

// Application context, in a Fragment use getContext()
Context context = getApplicationContext();

// Initialize the scanner
IDeviceScanner scanner = new DeviceScanner(context);
scanner.setOnScannerStateChangeListener(scannerStateListener);    

// Start the scanner
if (scanner.getState() == IDeviceScanner.DeviceScannerState.IDLE) {
    scanner.start(
        deviceFoundListener,
        IDeviceScanner.DEFAULT_GENERAL_SCAN_PERIOD_MS);
} else {
    // Check the error state and handle accordingly ...
}    

Recalling a known Nix device

For Bluetooth connected devices only, it is possible to construct an IDeviceCompat instance directly if the hardware address and device type is known. The constructor takes the hardware address and device names as arguments.

  • The device address must match the original value queried from getId(). Note that this corresponds to the hardware address of the advertising Bluetooth device
  • The device name must match the original value queried from getName(). Note that this corresponds to the device name as advertised on Bluetooth. If the DeviceType enum is known, this also corresponds to getFullName() (see Kotlin or Java APIs for DeviceType).
// Application context, in a Fragment use getContext()
val context: Context = applicationContext

// Hardware address of known device
val exampleAddress: String = "00:AA:11:BB:22:CC"

// Name of known device / type (must match advertised name)
val exampleName: String = "Nix Spectro 2"

// Create IDeviceCompat instance
val recalledDevice: IDeviceCompat = DeviceCompat(
    context = context,
    address = exampleAddress,
    name = exampleName
)
// Application context, in a Fragment use getContext()
Context context = getApplicationContext();

// Hardware address of known device
String exampleAddress = "00:AA:11:BB:22:CC";

// Name of known device / type (must match advertised name)
String exampleName = "Nix Spectro 2";

// Create IDeviceCompat instance
IDeviceCompat recalledDevice = new DeviceCompat(
    context, 
    exampleAddress, 
    exampleName, 
    Integer.MIN_VALUE);

Opening a connection

Once an IDeviceCompat instance has been obtained, a connection can be opened by calling connect() (see Kotlin or Java APIs). Connection state changes are provided via an OnDeviceStateChangeListener interface. The connection can be cancelled or closed normally by later calling disconnect().

The steps necessary for connecting include:

  1. Implement the OnDeviceStateChangeListener interface in your class
  2. Stop the DeviceScanner before opening a connection, if still running.
    • It is safe to call stop() on the DeviceScanner at any time, even if already stopped.
  3. Start the connection process by calling connect().
    • The connection process is asynchronous and connect() will return immediately. If a connection activity indicator is shown, it can be shown immediately before calling this method.
    • A call to connect() will always result in either the onConnected() or onDisconnected() callback being triggered.
    • Device operations are not possible until the onConnected() callback is received (successful connection)
    • If the connection process fails, the onDisconnected() callback will be invoked with an appropriate status code indicating the cause of the error.
    • onDisconnected() will also be called later whenever the device is disconnected. This can occur normally (i.e. - if the host application calls disconnect() on the IDeviceCompat instance), or abnormally (i.e. - if the connection is dropped due to an error or low Bluetooth signal strength).
    • Your license is linked to a specific allocation of Nix devices and will not operate with devices from another allocation.
      • At connection time, the SDK will read an allocation code stored on the Nix device and compare to the license information.
      • If this check does not pass, the SDK will contact a Nix authentication server to check if that device serial number is authorized.
      • If the device cannot be authenticated (i.e. – an unknown allocation code was found and an internet connection is unavailable), the device will be disconnected with the status ERROR_UNAUTHORIZED.
      • The internet connection is required only once every 30 days – once authorized, this status is saved, and connections can be made offline for this time period.
  4. When the user has finished using the device, the connection can be closed by calling disconnect().
// Define the OnDeviceStateChangeListener
val deviceStateListener = object : OnDeviceStateChangeListener {
    override fun onConnected(
        sender: IDeviceCompat
    ) {
        // Device has connected successfully
        // Device properties can now be queried
        // Device commands/operations can now run
        // ...
    }

    override fun onDisconnected(
        sender: IDeviceCompat,
        status: DeviceStatus
    ) {
        // Handle status codes here, if desired in your application
        // At minimum, should check for ERROR_UNAUTHORIZED and 
        // ERROR_LICENSE cases
        when (status) {
            // Device not authorized for this license UUID
            DeviceStatus.ERROR_UNAUTHORIZED -> {
                // ...
            }

            // There is an issue with the LicenseManager
            // Check LicenseManager.state
            DeviceStatus.ERROR_LICENSE -> {
                // ...
            }

            // Normal disconnect, triggered by device.disconnect()
            DeviceStatus.SUCCESS -> {
                // ...
            }

            // Nix device dropped the connection
            DeviceStatus.ERROR_DROPPED_CONNECTION -> {
                // ...
            }

            // Connection to Nix device timed out
            DeviceStatus.ERROR_TIMEOUT -> { 
                // ...
            }

            // Other internal errors
            DeviceStatus.ERROR_MAX_ATTEMPTS,
            DeviceStatus.ERROR_UNSUPPORTED_DEVICE,
            DeviceStatus.ERROR_INTERNAL -> { 
                // ...
            }
        }
    }

    override fun onBatteryStateChanged(
        sender: IDeviceCompat,
        newState: Int
    ) {
        super.onBatteryStateChanged(sender, newState)
        // Battery level has updated
        // ...
    }

    override fun onExtPowerStateChanged(
        sender: IDeviceCompat,
        newState: Boolean
    ) {
        super.onExtPowerStateChanged(sender, newState)
        // External power has been connected or disconnected
        // ...
    }        
}

// Device instance that has already been found by the DeviceScanner
var device: IDeviceCompat

// Initiate the connection
device.connect(deviceStateListener)
// Define the OnDeviceStateChangeListener
IDeviceCompat.OnDeviceStateChangeListener deviceStateListener = 
    new IDeviceCompat.OnDeviceStateChangeListener() 
{
    @Override
    public void onConnected(@NonNull IDeviceCompat sender) {
        // Device has connected successfully
        // Device properties can now be queried
        // Device commands/operations can now run
        // ...
    }

    @Override
    public void onDisconnected(
        @NonNull IDeviceCompat sender, 
        @NonNull DeviceStatus status) 
    {
        // Handle status codes here, if desired in your application
        // At minimum, should check for ERROR_UNAUTHORIZED and 
        // ERROR_LICENSE cases
        switch (status) {
            case ERROR_UNAUTHORIZED:
                // Device not authorized for this license UUID
                // ...
                break;
            case ERROR_LICENSE:
                // There is an issue with the LicenseManager
                // Check LicenseManager.Shared.getState()
                break;
            case SUCCESS:
                // Normal disconnect, triggered by device.disconnect()
                //...
                break;
            case ERROR_DROPPED_CONNECTION:
                // Nix device dropped the connection
                // ...
                break;
            case ERROR_TIMEOUT:
                // Connection to Nix device timed out
                // ...
                break;
            case ERROR_MAX_ATTEMPTS:
            case ERROR_UNSUPPORTED_DEVICE:
            case ERROR_INTERNAL:
                // Other internal errors
                // ...
                break;
        }
    }

    @Override
    public void onBatteryStateChanged(
        @NonNull IDeviceCompat sender, 
        int newState
    ) {
        // Battery level has updated
        // ...
    }

    @Override
    public void onExtPowerStateChanged(
        @NonNull IDeviceCompat sender, 
        boolean newState
    ) {
        // External power has been connected or disconnected
        // ...
    }
};

// Device instance that has already been found by the DeviceScanner
IDeviceCompat device;

// Initiate the connection
device.connect(deviceStateListener);

Next steps