Table of Contents

Discovering and Connecting to Nix Devices

Device discovery

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

The DeviceScanner implements the IDeviceScanner interface and searches for nearby Nix devices using Bluetooth and USB. Scanner events include:

  • ScannerCreated
  • ScannerStarted
    • Invoked when the scanner has started searching for devices
  • ScannerStopped
    • Invoked when the scanner has stopped searching for devices
  • ScanResult
    • Invoked each time that an IDeviceCompat instance has been found
    • Device information is provided via ScanResultEventArgs arguments.
    • 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 Active (see LicenseManager.State). Only device types supported by your license can be discovered and connected (see LicenseManager.AllowedDeviceTypes).

To use the DeviceScanner:

  • Create a new scanner instance and define event handlers.
  • Call InitializeAsync() and await its completion.
  • Check the scanner State, which will be Idle if properly initialized.
  • The scanner can be started using Start()
    • By default, the scanner will search for a period DefaultGeneralScanPeriodMs
    • This interval can be overridden by providing an argument for scanPeriodMs.
  • Discovered devices will be reported via the ScanResult event.
    • At this stage, it is valid to check a limited number of parameters:
      • Id: Device identifier string
      • Rssi: Device signal strength (-127 to 0). Will be 0 for USB connected devices.
      • InterfaceType: Indicates whether the device was found via Bluetooth or USB connection.
      • Type: Indicates the specific device type.
      • Name: Full name of the device (e.g. 'Nix Spectro 2')
    • Note that USB connected devices will be reported only once, immediately after Start() is called
  • The device search will run for the specified duration, after which the ScannerStopped event is invoked. The device search can also be manually stopped by calling Stop().
  • To recall a specific Bluetooth connected device in a later app session, you can await SearchForIdAsync(). This will run the DeviceScanner searching for a specific device by its identifier.
// DeviceScanner event handlers
void OnScannerCreated(
    object sender,
    ScannerCreatedEventArgs args)
{
    // Scanner has initialized
    // ...
}

void OnScannerStarted(
    object sender,
    EventArgs args)
{
    // Scanner has started 
    // ...
}

void OnScannerStopped(
    object sender,
    EventArgs args)
{
    // Scanner has stopped
    // ...
}

void OnScanResult(
    object sender,
    ScanResultEventArgs args)
{
    if (args.Device is IDeviceCompat device)
    {
        // Found a device
        // ... 
        Debug.WriteLine(
            $"Found {device?.Id} " + 
            "({device?.Name}) " + 
            "at RSSI {device?.Rssi}");
    }
}

// New DeviceScanner instance
IDeviceScanner scanner = new DeviceScanner();
scanner.ScannerCreated += OnScannerCreated;
scanner.ScannerStarted += OnScannerStarted;
scanner.ScannerStopped += OnScannerStopped;
scanner.ScanResult += OnScanResult;

// Initialize the scanner
// Can await state here, or check via event handler
var state = await scanner.InitializeAsync();

// Start the scanner
if (state == DeviceScannerState.Idle)
{
    scanner.Start();
}
else
{
    // Check the error state and handle accordingly ...
}
Tip

If Bluetooth is disabled or unavailable, it will not be possible to run the device scanner normally. However, USB connected devices can separately be listed by calling ListUsbDevicesAsync(). This task provides a list of USB connected devices and does not invoke the ScanResult event.

Opening a connection

Once an IDeviceCompat instance has been obtained, a connection can be opened by calling ConnectAsync(). Connection state changes are provided via the following events:

The steps necessary for connecting include:

  1. Implement device event handlers in your class
  2. Stop the DeviceScanner before opening a connection, if still running.
    • It is safe to call Stop() at any time, even if already stopped
  3. Start the connection process by calling by calling ConnectAsync()
    • The connection process is asynchronous. ConnectAsync() returns a task that can be awaited.
    • A call to ConnectAsync() will always result in either the Connected or Disconnected event being invoked.
    • Device operations are not possible until ConnectAsync() provides a Success result (or equivalently, until the Connected event is invoked).
    • If the connection process fails, the Disconnected event will be invoked with an appropriate status code indicating the cause of the error.
    • Disconnected will also be called later whenever the device is disconnected. This can occur normally (i.e. - if the host device calls Disconnect()), 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 ErrorUnauthorized.
      • 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().
    • This method call can also be used to cancel an ongoing connection operation.
// Using a device instance previously found by `DeviceScanner`
IDeviceCompat Device;

// Define device state event handlers
void OnConnected(
    object sender,
    EventArgs args)
{
    // Device has connected successfully
    // ...
}

void OnDisconnected(
    object sender,
    DeviceStatusArgs args)
{
    // Device has disconnected
    DeviceStatus status = args.Status;
    Debug.WriteLine($"OnDisconnected() with status {status}");

    // Remove device event handlers
    RemoveDeviceEvents();

    // Handle status codes here, if desired in your application
    // At minimum, should check for ErrorUnauthorized and ErrorLicense cases
    switch (status)
    {
        case DeviceStatus.ErrorUnauthorized:
            // Device not authorized for this license UUID
            // ...
            break;
        case DeviceStatus.ErrorLicense:
            // There is an issue with the LicenseManager
            // Check LicenseManager.State
            break;
        case DeviceStatus.Success:
            // Normal disconnect, triggered by device.Disconnect()
            // ...
            break;
        case DeviceStatus.ErrorDroppedConnection:
            // Nix device dropped the connection
            // ...
            break;
        case DeviceStatus.ErrorTimeout:
        case DeviceStatus.ErrorMaxAttempts:
            // Connection to Nix device timed out
            // ...
            break;
        default:
            // Other internal errors
            // ...
            break;
    }
    // Other steps as necessary in your application
    // ...
}

void OnBatteryStateChanged(
    object sender,
    BatteryStateEventArgs args)
{
    // Battery level has updated
    // ...    
    int newState = args.NewState;
    Debug.WriteLine($"OnBatteryStateChanged to {newState}");
}

void OnExtPowerStateChanged(
    object sender,
    ExtPowerStateEventArgs args)
{
    // External power has been connected or disconnected
    // ...    
    int newState = args.NewState;
    Debug.WriteLine($"OnExtPowerStateChanged to {newState}");
}

// Helpers to add/remove device event handlers
void AddDeviceEvents() 
{
    if (Device != null)
    {
        Device.Connected += OnConnected;
        Device.Disconnected += OnDisconnected;
        Device.BatteryStateChanged += OnBatteryStateChanged;
        Device.ExtPowerStateChanged += OnExtPowerStateChanged;
    }
}

void RemoveDeviceEvent()
{
    if (device != null)
    {
        Device.Connected -= OnConnected;
        Device.Disconnected -= OnDisconnected;
        Device.BatteryStateChanged -= OnBatteryStateChanged;
        Device.ExtPowerStateChanged -= OnExtPowerStateChanged;
    }
}

// Add device handlers
AddDeviceEvents()

// Start connection process
var connectTask = Device.ConnectAsync();

// Show progress indicator here if desired
// ...

// Wait for completion
var state = await connectTask;

Bluetooth compatibility notes

  • Availability of Bluetooth functions may depend on your application being built natively for the computer architecture used at runtime. An architecture mismatch may result in the IDeviceScanner initializing with status ErrorBluetoothUnavailable, even if Bluetooth hardware is present in the system. This is related to an underlying limitation in the Windows Runtime API as described here.
    • When the NixUniversalSDK is called from a UWP or WinUI application, there is no architecture requirement.
      • Builds made for AnyCPU, x86, x64, and ARM64 should support Bluetooth device discovery regardless of the computer architecture
    • When the NixUniversalSDK is called from a desktop application, Bluetooth functions are only available when running on native architecture.
      • x86 application builds support Bluetooth functions when running on x86 hardware
      • x64 application builds support Bluetooth functions when running on x64 hardware
      • x86 application builds do not support Bluetooth functions when running on x64 hardware
    • If the IDeviceScanner initializes to an error state, it cannot be used to discover Bluetooth devices. However, USB attached devices can still be listed by calling ListUsbDevicesAsync() even when the IDeviceScanner is in an error state.
  • Bluetooth connection speed is greatly improved when running your application on Windows 11 as compared to Windows 10. This is related to enhancements that were made in the Windows Runtime API for Windows 11. The NixUniversalSDK automatically requests the highest Bluetooth connection speeds available from the system at runtime.

Next steps