Skip to content

# Device Operations

## Taking measurements

Once the device is connected and in an idle state, it is possible to take measurements by calling `measure()` (see Kotlin or Java APIs).

- The varargs `modes` argument may be omitted to run measurements in all modes supported by the device. This is the recommended option.
- If the varargs `modes` arguments are supplied, measurements will be provided only for the specified scan modes. In most cases, this will not speed up the measurement cycle time, so this is not recommended.
- The measurement operation is asynchronous and will return immediately. While the measurement is in progress, no other commands can be sent to the Nix device.
- Every call to `measure()` will result in a callback with either a success or error code.
- Results of the measurement operation are provided via the `onDeviceResult()` callback in an `OnDeviceResultListener` interface (Kotlin or Java). This callback provides two arguments:
  - A `CommandStatus` status code (see Kotlin or Java APIs), which will be `SUCCESS` for a successful operation.
  - A map containing the measurement values as `IMeasurementData` instances. The keys in this map correspond to the `ScanMode` enum (see Kotlin or Java APIs) for each measurement value.
  - See Handling Measurement Data for notes on interpreting the measurement data.
  - The supported scan modes for a particular device are provided in the list from `getSupportedModes()` on the `IDeviceCompat` instance. Support for a scan mode can also be checked explicitly by calling `isModeSupported()` on the `IDeviceCompat` instance. The expected results by device are shown below:

Device Type | M0 | M1 | M2
--- | --- | --- | ---
Mini | ☐ | ☐ | ☑
Mini 2 | ☐ | ☐ | ☑
Mini 3 | ☐ | ☐ | ☑
Pro | ☐ | ☐ | ☑
Pro 2 | ☐ | ☐ | ☑
QC | ☐ | ☐ | ☑
Spectro 2 (F1.0.0) | ☑ | ☑ | ☐
Spectro 2 (F2.0.0) | ☑ | ☑ | ☑
Spectro L | ☑ | ☑ | ☑

Kotlin Java

    // Device instance
    var device: IDeviceCompat

    // Define callback for measurement
    val measureListener = object : OnDeviceResultListener {
        override fun onDeviceResult(
            status: CommandStatus,
            measurements: Map<ScanMode, IMeasurementData>?
        ) {
            when (status) {
                CommandStatus.SUCCESS -> {
                    // Successful operation
                    // Handle measurement data here
                    // ...
                }

                CommandStatus.ERROR_NOT_READY -> {
                    // Did not complete because the device was busy
                    // ...
                }

                CommandStatus.ERROR_NOT_SUPPORTED -> {
                    // Did not complete because an unsupported scan mode was
                    // specified
                    // ...
                }

                CommandStatus.ERROR_LOW_POWER -> {
                    // Did not complete because the battery level is too low
                    // ...
                }

                CommandStatus.ERROR_TIMEOUT -> {
                    // Timeout when waiting for result
                    // ...
                }

                CommandStatus.ERROR_AMBIENT_LIGHT -> {
                    // Did not complete because of ambient light leakage
                    // ...
                }

                CommandStatus.ERROR_LICENSE -> {
                    // Scan did not complete because of a license issue
                    // Check LicenseManager.state
                    // ...
                }

                else -> {
                    // Did not complete because of other internal error
                    // ...
                }
            }
        }
    }

    // Run the measurement
    device.measure(measureListener)

    // Device instance
    IDeviceCompat device;

    // Define callback for measurement
    OnDeviceResultListener measureListener = (commandStatus, measurements) -> {
        switch (commandStatus) {
            case SUCCESS:
                // Successful operation
                // Handle measurement data here
                // ...
                break;
            case ERROR_NOT_READY:
                // Did not complete because the device was busy
                // ...
                break;
            case ERROR_NOT_SUPPORTED:
                // Did not complete because an unsupported scan mode was
                // specified
                // ...
                break;
            case ERROR_LOW_POWER:
                // Did not complete because the battery level is too low
                // ...
                break;
            case ERROR_TIMEOUT:
                // Timeout when waiting for result
                // ...
                break;
            case ERROR_AMBIENT_LIGHT:
                // Did not complete because of ambient light leakage
                // ...
                break;
            case ERROR_LICENSE:
                // Scan did not complete because of a license issue
                // Check LicenseManager.Shared.state
                // ...
                break;
            default:
                // Did not complete because of other internal error
                // ...
                break;
        }
    };

    // Run the measurement
    device.measure(measureListener);

## Performing in-field profiling / calibration with reference tile

Certain device types support in-field profiling or calibration using a provided reference tile. This process requires first decoding a string from the QR code attached to the reference tile and then directing the user to measure the same tile. Any necessary adjustments/corrections are performed internally by the SDK and/or the Nix device firmware itself.

- The in-field calibration procedure may be performed as often or seldom as desired by the user. However, the flag `getFieldCalibrationDue()` indicates whether or not in-field calibration is recommended for the connected device.
  - This flag is updated after each regular measurement
  - This flag is determined automatically by both elapsed time and ambient temperature change
  - Calling `invalidateFieldCalibration()` will force the calibration record to expire on QC and Spectro 2 devices, so that `getFieldCalibrationDue()` returns `true`
- The in-field calibration procedure can exit with status `ERROR_SCAN_DELTA` if a large difference is detected between the tile reference and measurement.
  - This status indicates an unexpected tile measurement, possibly due to:
    - User error (e.g. - a surface other than the reference tile was measured). In this case, the user should repeat the operation
    - The reference tile is damaged
    - The device has drifted too far from factory calibration for the field calibration to be reliable
  - The failure threshold can vary by device type and can be checked by calling `getFieldCalibrationMaxDelta()`
  - The failure threshold can be overridden by calling `setFieldCalibrationMaxDelta()` but this is NOT RECOMMENDED
- The flag `getSupportsFieldCalibration()` indicates whether or not a connected device supports this feature. The expected result by device type is shown below:

Device Type | Supported
--- | ---
Mini | ☐
Mini 2 | ☐
Mini 3 | ☑
Pro | ☐
Pro 2 | ☐
QC | ☑
Spectro 2 | ☑
Spectro L | ☑

The steps to perform in-field profiling include:

1. Decode the QR code printed on the device reference tile and interpret as a string. Do not parse this data further.
2. Check if the parsed data is valid using `isTileStringValid()` on the `IDeviceCompat` instance. If valid, continue to the next step.
3. Instruct the user to place the device on the reference tile.
4. Using the decoded tile string, run the in-field calibration routine by calling `runFieldCalibration()` on the `IDeviceCompat` instance and wait for callback (see Kotlin or Java APIs).
   - This operation is asynchronous and returns immediately. While the operation is in progress, no other commands can be sent to the Nix device (wait for the callback)
   - Every call to `runFieldCalibration()` will result in a callback with either a success or error code.

Kotlin Java

    // Device instance
    var device: IDeviceCompat

    // String decoded from the reference tile
    val tileString: String

    // Define callback for field calibration
    val calibrateListener = object : OnDeviceResultListener {
        override fun onDeviceResult(
            status: CommandStatus,
            measurements: Map<ScanMode, IMeasurementData>?
        ) {
            when (status) {
                CommandStatus.SUCCESS -> {
                    // Successful operation
                    // ...
                }

                CommandStatus.WARNING_TEMPERATURE -> {
                    // Completed successfully, but ambient temperature is outside
                    // of the recommended range
                    // ...
                }

                CommandStatus.ERROR_SCAN_DELTA -> {
                    // Operation failed because the measured value of the
                    // reference tile is too far from the expected value
                    // (high delta E)
                    // ...
                }

                CommandStatus.ERROR_NOT_READY -> {
                    // Did not complete because the device was busy
                    // ...
                }

                CommandStatus.ERROR_NOT_SUPPORTED -> {
                    // Did not complete because this device does not support
                    // this command
                    // ...
                }

                CommandStatus.ERROR_INVALID_ARGUMENT -> {
                    // Did not complete because the tileString is invalid
                    // ...
                }

                CommandStatus.ERROR_LOW_POWER -> {
                    // Did not complete because the battery level is too low
                    // ...
                }

                CommandStatus.ERROR_TIMEOUT -> {
                    // Timeout when waiting for result
                    // ...
                }

                CommandStatus.ERROR_AMBIENT_LIGHT -> {
                    // Did not complete because of ambient light leakage
                    // ...
                }

                CommandStatus.ERROR_LICENSE -> {
                    // Scan did not complete because of a license issue
                    // Check LicenseManager.state
                    // ...
                }

                else -> {
                    // Did not complete because of other internal error
                    // ...
                }
            }
        }
    }

    // Run field calibration scan
    device.runFieldCalibration(calibrateListener, tileString)

    // Device instance
    IDeviceCompat device;

    // String decoded from the reference tile
    String tileString;

    // Define callback for field calibration
    OnDeviceResultListener calibrateListener =
        (commandStatus, measurements) ->
    {
        switch (commandStatus) {
            case SUCCESS:
                // Successful operation
                // ...
                break;
            case WARNING_TEMPERATURE:
                // Completed successfully, but ambient temperature is outside
                // of the recommended range
                // ...
                break;
            case ERROR_SCAN_DELTA:
                // Operation failed because the measured value of the reference
                // tile is too far from the expected value (high delta E)
                // ...
                break;
            case ERROR_NOT_READY:
                // Did not complete because the device was busy
                // ...
                break;
            case ERROR_NOT_SUPPORTED:
                // Did not complete because this device does not support this
                // command
                // ...
                break;
            case ERROR_INVALID_ARGUMENT:
                // Did not complete because the tileString is invalid
                // ...
                break;
            case ERROR_LOW_POWER:
                // Did not complete because the battery level is too low
                // ...
                break;
            case ERROR_TIMEOUT:
                // Timeout when waiting for result
                // ...
                break;
            case ERROR_AMBIENT_LIGHT:
                // Did not complete because of ambient light leakage
                // ...
                break;
            case ERROR_LICENSE:
                // Scan did not complete because of a license issue
                // Check LicenseManager.Shared.state
                // ...
                break;
            default:
                // Did not complete because of other internal error
                // ...
                break;
        }
    };

    // Run field calibration scan
    device.runFieldCalibration(calibrateListener, tileString);

## Other device options

Some Nix devices support certain `boolean` options. These options are set to be enabled by default (if supported) and do not usually need to be changed. Current state and device support can be checked at runtime on the `IDeviceCompat` instance:

- Applying temperature compensation
  - Current state: `getTemperatureCompensationEnabled()`
  - Is supported?: `getSupportsTemperatureCompensation()`
- Applying in-field calibration
  - Current state: `getFieldCalibrationEnabled()`
  - Is supported?: `getSupportsFieldCalibration()`
- Using on-device haptic feedback
  - Current state: `getHapticFeedbackEnabled()`
  - Is supported?: `getSupportsHapticFeedback()`
- Using on-device RGB feedback
  - Current state: `getRgbFeedbackEnabled()`
  - Is supported?: `getSupportsRgbFeedback()`

A summary of device support is shown below, but can be checked at runtime.

Device type | Temp comp. | Field cal. | Haptic | RGB
--- | --- | --- | --- | ---
Mini | ☐ | ☐ | ☐ | ☐
Mini 2 | ☑ | ☐ | ☐ | ☐
Mini 3 | ☑ | ☑ | ☐ | ☑
Pro | ☐ | ☐ | ☐ | ☐
Pro 2 | ☑ | ☐ | ☐ | ☐
QC | ☑ | ☑ | ☐ | ☐
Spectro 2 | ☑ | ☑ | ☑ | ☑
Spectro L | ☑ | ☑ | ☑ | ☑

Changing any of these options may require communication with the device itself and is an asynchronous operation, with the status of the operation provided via a `onDeviceResult()` callback in the specified `OnDeviceResultListener` interface.

- To enable or bypass temperature compensation, use `setTemperatureCompensationEnabled()`
- To enable or bypass applying in-field calibration, use `setFieldCalibrationEnabled()`
- To enable or disable haptic feedback, use `setHapticFeedbackEnabled()`
- To enable or disable RGB feedback, use `setRgbFeedbackEnabled()`

## Other device properties

The `IDeviceCompat` interface defines other properties that can be read once the device has connected and reached an idle state. These include, but are not limited to:

Kotlin Java

- `extPowerState`: status of external power connection / charging
- `batteryLevel`: current battery level, `null` otherwise
- `firmwareVersion`: device firmware revision
- `hardwareVersion`: device hardware revision
- `softwareVersion`: device software revision
- `serialNumber`: device serial number
- `note`: device allocation / production code
- `providesSpectral`: indicates whether or not this device provides spectral data
- `providesDensity`: indicates whether or not this device provides density data
- `supportedModes`: list of supported measurement modes for this device
- `supportedReferences`: list of supported reference white values provided by the measurement data from this device

- `getExtPowerState()`: status of external power connection / charging
- `getBatteryLevel()`: current battery level, `null` otherwise
- `getFirmwareVersion()`: device firmware revision
- `getHardwareVersion()`: device hardware revision
- `getSoftwareVersion()`: device software revision
- `getSerialNumber()`: device serial number
- `getNote()`: device allocation / production code
- `getProvidesSpectral()`: indicates whether or not this device provides spectral data
- `getProvidesDensity()`: indicates whether or not this device provides density data
- `getSupportedModes()`: list of supported measurement modes for this device
- `getSupportedReferences()`: list of supported reference white values provided by the measurement data from this device

Warning

The `providesDensity` and `providesSpectral` properties indicate whether or not the hardware device is capable of providing these data types. However, these measurements will only be available if these features are enabled by the current license. See Other license properties for more details.

## Next steps

- Handling Measurement Data