Show / Hide 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`
  - Invoked when the scanner has finished initializing after calling `InitializeAsync()`
  - State is provided via `ScannerCreatedEventArgs` arguments.
- `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.
  - See a list possible states here.
  - If the state is `ErrorLicense`, check the `LicenseManager.State` for further details.
- 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:

- `Connected`
  - Invoked when the connection process has completed successfully
- `Disconnected`
  - Invoked when the Nix device has disconnected from the host
  - Provides a status indicating the reason for disconnecting via `DeviceStatusArgs`
- `BatteryStateChanged`
  - Invoked when the device battery level has updated
  - New battery state is provided via `BatteryStateEventArgs`
- `ExtPowerStateChanged`
  - Invoked when external power has been connected or disconnected from the Nix device
  - New external power state is provided via `ExtPowerStateEventArgs`

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

- Device Operations