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
: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.
- Callback interface which is called by the scanner each time that an
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. - 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 forscanPeriodMs
.
- By default, the scanner will search for a period
- Observe discovered devices via the
onScanResult()
in theOnDeviceFoundListener
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 usingstop()
.
// 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 theDeviceType
enum is known, this also corresponds togetFullName()
(see Kotlin or Java APIs forDeviceType
).
// 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:
- Implement the
OnDeviceStateChangeListener
interface in your class - Stop the
DeviceScanner
before opening a connection, if still running.- It is safe to call
stop()
on theDeviceScanner
at any time, even if already stopped.
- It is safe to call
- 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 theonConnected()
oronDisconnected()
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 callsdisconnect()
on theIDeviceCompat
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.
- The connection process is asynchronous and
- 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);