Programmer's Guide for .NET MAUI Wrapper for Scanner SDK (Android and iOS)

Overview

This section provides step-by-step instructions on developing .NET MAUI Framework based Scanner applications for Android and iOS with Microsoft Visual Studio Code.


Development Environment

Please refer the instructions provided below for configuring development environment in the respective platform.

Microsoft Windows

macOS


Create a .NET MAUI Project with Visual Studio Code.

  • Open Visual Studio Code, with the .NET MAUI extension installed and configured on .NET 9 environment.
  • Use the Solution Explorer's "Create .NET Project" button or Command Palette's ".NET: New Project."
    Figure 1: Create .NET project

  • Choose the .NET MAUI project type.
    Figure 2: Select the project type as .NET MAUI

  • Provide a project name and create the project.
    Figure 3: Add a name for the project

  • Create two directories namely “Android” and “iOS” at the same location as the project solution (*.sln) file.
    Figure 4: Folder Structure

  • Add the provided Android binding dll(ScannerSDKAndroidBinding.dll), Android wrapper dll (ZebraBarcodeScannerSDK.dll) and Android native Scanner SDK(barcode_scanner_library_v2.6.25.0-release.aar) inside the Android directory.
    Figure 5: Folder Structure (Android)

  • Add the provided iOS binding dll (ScannerSDKiOSBinding.dll), iOS wrapper dll (ZebraBarcodeScannerSDK.dll) and the resource folder(ScannerSDKiOSBinding.resources) which includes the iOS native Scanner SDK inside the iOS directory.
    Figure 6: Folder Structure (iOS)

  • Open created project on Visual Studio code and select the project file.
    Figure 7: Folder Structure (iOS)

  • Add the below lines inside the project file to add the libraries as references for the project.
    
    <ItemGroup>
        <Reference Include="ScannerSDKAndroidBinding" Condition="'$(TargetFramework)' == 'net9.0-android'">
            <HintPath>..\Android\ScannerSDKAndroidBinding.dll</HintPath>
        </Reference>
        <Reference Include="ZebraBarcodeScannerSDK" Condition="'$(TargetFramework)' == 'net9.0-android'">
            <HintPath>..\Android\ZebraBarcodeScannerSDK.dll</HintPath>
        </Reference>
        <Reference Include="ScannerSDKiOSBinding" Condition="'$(TargetFramework)' == 'net9.0-ios'">
            <HintPath>..\iOS\ScannerSDKiOSBinding.dll</HintPath>
        </Reference>
        <Reference Include="ZebraBarcodeScannerSDK" Condition="'$(TargetFramework)' == 'net9.0-ios'">
            <HintPath>..\iOS\ZebraBarcodeScannerSDK.dll</HintPath>
        </Reference>
    </ItemGroup>  
    
    
  • Update the Info.plist as described here by opening the Info.plist.
    Figure 8: Open Info.plist file

  • Add the following background modes under UIBackgroundModes key in the Info.plist.
    • App communicates with an accessory.
    • App communicates using CoreBluetooth.
    • App shares data using CoreBluetooth.
    
    <key>UIBackgroundModes</key>
    <array>
        <string>external-accessory</string>
        <string>bluetooth-central</string>
        <string>bluetooth-peripheral</string>
    </array>                                                                
    
    
  • Add following entries under UISupportedExternalAccessoryProtocols key in the Info.plist.
    • com.zebra.scanner.SSI
    • com.motorolasolutions.CS4070_ssi
    
    <key>UISupportedExternalAccessoryProtocols</key>
    <array>
        <string>com.zebra.scanner.SSI</string>
        <string>com.motorolasolutions.CS4070_ssi</string>
    </array>
    
    
  • Set Property List Key NSBluetoothAlwaysUsageDescription to use the device's Bluetooth interface. Description value should be a string.

    Example: "Zebra Scanner Control App uses Bluetooth to find, connect and communicate with nearby Zebra devices. Please grant access".

    
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>Zebra Scanner Control App uses Bluetooth to find, connect and communicate with nearby Zebra devices. Please grant access.</string>
    
    
  • Update AndroidManifest.xml with all the required permissions for Android.

    
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
    
    
  • Implement necessary run time permissions.

    
    //Check status of Android runtime permissions
    
    private async Task<bool> CheckPermissions()
    {
        PermissionStatus bluetoothStatus = await CheckBluetoothPermissions();
        return IsGranted(bluetoothStatus);
    }                                                                    
    
    
    
    //Checking the permission status on version vise
    
    private async Task<PermissionStatus> CheckBluetoothPermissions()
    {
        PermissionStatus bluetoothStatus = PermissionStatus.Granted;
        if (DeviceInfo.Platform == DevicePlatform.Android)
        {
            if (DeviceInfo.Version.Major >= 12)
            {
                bluetoothStatus = await CheckPermissions<BluetoothPermissionsUpper>();
            }
            else
            {
                bluetoothStatus = await CheckPermissions<BluetoothPermissionsLower>();
            }
        }
        return bluetoothStatus;
    }                                                                            
    
    
    
    //Check status for all the permissions at once
    
    private async Task<PermissionStatus> CheckPermissions<TPermission>() where TPermission : Permissions.BasePermission, new()
    {
        PermissionStatus status = await Permissions.CheckStatusAsync<TPermission>();
        if (status != PermissionStatus.Granted)
        {
            status = await Permissions.RequestAsync<TPermission>();
        }
        return status;
    }  
    
    
    
    //Get permission grant status
    
    private static bool IsGranted(PermissionStatus status)
    {
        return status == PermissionStatus.Granted || status == PermissionStatus.Limited;
    }                 
    
    
    
    //Permissions for upper Android versions
    
    internal class BluetoothPermissionsUpper : BasePlatformPermission 
    {
        public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string permission, bool isRuntime)>
        {
            (global::Android.Manifest.Permission.BluetoothAdvertise, true),
            (global::Android.Manifest.Permission.BluetoothScan, true),
            (global::Android.Manifest.Permission.BluetoothConnect, true),
            (global::Android.Manifest.Permission.AccessFineLocation, true),
            (global::Android.Manifest.Permission.PostNotifications, true),
        }.ToArray();
    }                                     
    
    
    
    //Permissions for lower Android versions
    
    internal class BluetoothPermissionsLower : BasePlatformPermission
    {
        public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string permission, bool isRuntime)>
        {
            (global::Android.Manifest.Permission.AccessFineLocation, true),
            (global::Android.Manifest.Permission.AccessCoarseLocation, true)
        }.ToArray();
    }                                                            
    
    

Wrapper APIs

Namespace

Import the Scanner SDK namespace before making API calls.


using ZebraBarcodeScannerSDK;

Accessing the SDK

First, an instance of the Zebra Scanner SDK needs to be created. Scanner management related APIs can be accessed through Scanners object which can be obtained through the ScannerManager property in the ScannerSDK instance.


// Create an instance of the Zebra Scanner SDK
ScannerSDK scannerSDK = new ScannerSDK();

// Obtain Scanners object through the ScannerManager property in the ScannerSDK instance. 
Scanners scanners = scannerSDK.ScannerManager;

// Get the Zebra Scanner SDK version
string version = scannerSDK.Version;

Set Operation Mode

Zebra Scanner SDK is designed to support interaction with scanners operating in different modes based on the platform. The SDK can be configured to enable communication with a particular type of scanner by setting the operation mode.

Use the SetOperationMode function to set the required operational mode based on the platform.

The support is given for the following operation modes,

iOS

  • OPMODE_MFI
  • OPMODE_BTLE
  • OPMODE_MFI_BTLE

Android

  • OPMODE_SSI
  • OPMODE_SNAPI
  • OPMODE_BTLE

// 'scanners' is the Scanners object which is obtained through the ScannerManager property in the ScannerSDK instance. 
// configure the sdk to support both MFi and BT LE modes on iOS

scanners.SetOperationMode(OpMode.OPMODE_MFI_BTLE);                

Enable Available Scanner Detection

The SDK supports automatic detection of appearance and disappearance of available scanners. When the "Available scanners detection" option is enabled, the SDK updates its internal list of available scanners and deliver a corresponding asynchronous notification once it detects appearance or disappearance of a particular scanner.

Use the EnableAvailableScannersDetection method to actively detect appearance and disappearance of scanners.


// Enable available scanner detection 
scanners.EnableAvailableScannersDetection(true);                          

Event Handling

The SDK supports a set of asynchronous notifications to inform the application about scanner related events. This includes connectivity related events (eg: the appearance of a scanner) and scanner action events (eg: receiving barcode data).

Available events for both iOS and Android.

  • EVENT_SCANNER_APPEARANCE
  • EVENT_SCANNER_DISAPPERANCE
  • EVENT_SESSION_ESTABLISHMENT
  • EVENT_SESSION_TERMINATION
  • EVENT_BARCODE
  • EVENT_RAW_DATA

Available events only for Android.

  • EVENT_IMAGE
  • EVENT_VIDEO

Subscribe for Events

Subscribe for specific asynchronous events using the SubscribeForEvents method to specify which events should be reported by the SDK.


// Subscribe to scanner appearance/disappearance, session establishment/termination and barcode event notifications.
scanners.SubscribeForEvents((int)(Notifications.EVENT_SCANNER_APPEARANCE | Notifications.EVENT_SCANNER_DISAPPEARANCE | Notifications.EVENT_SESSION_ESTABLISHMENT | Notifications.EVENT_SESSION_TERMINATION | Notifications.EVENT_BARCODE));

Scanner Appeared Event

This event occurs when the presence of a scanner appears.


// ScannerAppeared is a event handler that needs to be implemented
scanners.Appeared += ScannerAppeared;

Scanner Disappeared Event

This event occurs when a scanner is no longer present.


// ScannerDisappeared is a event handler that needs to be implemented 
scanners.Disappeared += ScannerDisappeared;

Communication Session Established Event

This event occurs when communication is established with a scanner.


// ScannerConnected is a event handler that needs to be implemented
scanners.Connected += ScannerConnected;

Communication Session Terminated Event

This event occurs when communication with a scanner is terminated.


// ScannerDisconnected is a event handler that needs to be implemented
scanners.Disconnected += ScannerDisconnected;

Barcode Data Received Event

This event occurs when barcode data is read and received.


// ScannerBarcodeData is a event handler that needs to be implemented
scanners.BarcodeData += ScannerBarcodeData;

Get Available Scanner List

An "available" scanner is a scanner that is paired to the host device via Bluetooth.

GetAvailableScanners method can be used to get the currently available scanner list based on the operation mode has been set.

An "active" scanner is a scanner that is paired to the host device via Bluetooth, and has established a communication session with the SDK. GetActiveScanners method can be used to get the currently connected scanner list.


// Get available scanner list
List<Scanner> discoveredScannerList = scanners.GetAvailableScanners();

// Get active scanner list
List<Scanner> activeScannerList = scanners.GetActiveScanners();

Connect to a Scanner

There are two methods to establish a connection with a scanner,

  1. Using Scan-To-Connect (STC) Pairing Barcode.
  2. Using the Manual pairing method.

Connect a Scanner Using Scan-To-Connect (STC) Pairing Barcode

Table 1 Pairing Barcode Type Attribute Values

Description Attribute Values
STC type BARCODE_TYPE_STC

Table 2 Bluetooth Protocol Attribute Values

Description Attribute Values
SSI over Bluetooth Low Energy SSI_BT_LE
SSI over Bluetooth Classic (Android Only) SSI_BT_CRADLE_HOST

Table 3 Scanner Configuration Attribute Values

Description Attribute Values
Keep current settings KEEP_CURRENT
Set factory default settings SET_FACTORY_DEFAULTS

The following code example demonstrates how to get the STC pairing barcode.


sdkInstance = new ScannerSDK();

imageData = sdkInstance.GetBluetoothParingBarcode(PairingBarcodeType.BARCODE_TYPE_STC, BluetoothProtocol.SSI_BT_LE, ScannerConfiguration.SET_FACTORY_DEFAULTS);

stcImage.Source = ImageSource.FromStream(() => new MemoryStream(imageData));

When the bluetooth protocol is SSI_BT_CRADLE_HOST bluetooth mac address is required as an additional parameter. This is only applicable for Android.


sdkInstance = new ScannerSDK();

string bluetoothAddress = "xx:xx:xx:xx:xx:xx";

imageData = sdkInstance.GetBluetoothParingBarcode(PairingBarcodeType.BARCODE_TYPE_STC, BluetoothProtocol.SSI_BT_CRADLE_HOST, ScannerConfiguration.KEEP_CURRENT, bluetoothAddress);

stcImage.Source = ImageSource.FromStream(() => new MemoryStream(imageData));

After scanning the STC pairing barcode, the scanner will be paired and get connected. Then the EVENT_SESSION_ESTABLISHMENT event gets triggered.

Connect a Scanner Using Manual Pairing Method

Establishing a connection with an available scanner can be done through Connect method.


// discoveredScannerList is the available scanner list get through GetAvailableScanners method
Scanner availableScanner = discoveredScannerList[0];

// Connect to a given scanner
availableScanner.Connect();

Disconnect from a Scanner

Disconnect a scanner.


// Disconnect from a given scanner
discoveredScannerList[0].Disconnect();

Enable/Disable Bluetooth scanner discovery

The enable/disable of Bluetooth scanner discovery process is handled as below.


// Enable the state of Bluetooth scanner discovery
scanner.EnableBluetoothScannerDiscovery(true);

// Disable the state of Bluetooth scanner discovery 
scanner.EnableBluetoothScannerDiscovery(false);

Enable/Disable Scanner

Enable or disable the barcode scanning of a connected scanner.


// Enabling barcode scanning of a connected scanner. 'scanner' is a connected Scanner object
scanner.EnableScanner(); 

// Disabling barcode scanning of a connected scanner. 'scanner' is a connected Scanner object
scanner.DisableScanner();

Automatically Reconnect to a Scanner

"Automatic Session Reestablishment" option is used to automatically reconnect to a scanner which had disappeared unexpectedly. If the "Automatic Session Reestablishment" option is enabled for a specific scanner, the SDK automatically attempts to reestablish a connection with the scanner when it becomes available again.

Use the EnableAutoReconnection function to enable or disable automatic reconnection for a specific scanner.


// Enable automatic scanner re-connection. 'scanner' is a connected Scanner object
scanner.EnableAutoReconnection(true);

// Disable automatic scanner re-connection. 'scanner' is a connected Scanner object 
scanner.EnableAutoReconnection(false);

Automatically Connect to the Last Connected Scanner on App Relaunch

This API is used to enable or disable the auto reconnection to the last connected scanner on app relaunch, and it expects a Boolean value as the parameter. This API can be called using a 'Scanners' object.


// Enable the auto reconnection to the last connected scanner on app relaunch.
// 'scanners' is the Scanners object which can be got through the ScannerManager property in the ScannerSDK instance.
scanners.AutoReconnectToLastConnectedScannerOnAppRelaunch(true);

Get Scanner Asset Information

A connected scanner Asset information can be retrieved through this API.


// Get scanner asset information from a connected scanner
AssetInformation assetInformation = scanner.ScannerAssetInformation();

Console.WriteLine("Configuration name :" + assetInformation.ConfigurationName);
Console.WriteLine("Serial number :" + assetInformation.SerialNumber);
Console.WriteLine("Model number :" + assetInformation.ModelNumber);
Console.WriteLine("Firmware version :" + assetInformation.FirmwareVersion);
Console.WriteLine("Manufactured date" + assetInformation.ManufacturedDate);

Enable/Disable Pick List Mode

Pick List Mode feature allows for more precise scanning in environments where multiple barcodes are present. This ensures that only the intended barcode is scanned. EnablePickListMode method can be used to enable the pick list mode whereas DisablePickListMode can be used to disable the feature.


// Enable pick list mode
scanner.EnablePickListMode();

// Disable pick list mode
scanner.DisablePickListMode();

Enable/Disable Symbology

This will enable/disable symbology. Appendix : Table 1 includes the symbology attribute values.


// Enable/Disable symbology 
void SymbologyEnableDisable(string[] symbologyIdList, bool symbologyStatus)
{
    string symbologyIdAttributeList = "";
    foreach (string symblogyId in symbologyIdList)
    {
        symbologyIdAttributeList += "<attribute>" + 
                                        "<id>" + symblogyId + "</id>" +
                                        "<datatype>F</datatype>" + 
                                        "<value>" + symbologyStatus + "</value>" +
                                    "</attribute>";
    }

    string inXml = "<inArgs>" + 
                        "<scannerID>" + connectedScanner.Id + "</scannerID>" +
                        "<cmdArgs>" +
                            "<arg-xml>" +
                                "<attrib_list>" +
                                    symbologyIdAttributeList +
                                "</attrib_list>" +
                            "</arg-xml>" +
                        "</cmdArgs>" +
                    "</inArgs>";

    connectedScanner.ExecuteCommand(OpCode.RSM_ATTRIBUTE_SET, inXml);
}

// Symbology list. Refer Table 1 in the Appendix of .NET MAUI Wrapper for Scanner SDK for symbology attribute values.
string[] symbologyIdList = { "1", "2", "8" };

// Enable the given symbology list
SymbologyEnableDisable(symbologyIdList, true);

// Disable the given symbology list
SymbologyEnableDisable(symbologyIdList, false);

This will disable all symbologies.


// Disable all symbologies 
scanner.DisableAllSymbologies();

LED Blink On/Off

Below code snippet will demonstrate how to start/ stop blinking the LED on the scanner.


// LED On
string inXml = "<inArgs>" + 
                    "<scannerID>" + connectedScanner.Id + "</scannerID>" + 
                    "<cmdArgs>" + 
                        "<arg-int>85</arg-int>" + 
                    "</cmdArgs>" + 
                "</inArgs>";
connectedScanner.ExecuteCommand(OpCode.RSM_SET_ACTION, inXml);

// LED Off
string inXml1 = "<inArgs>" + 
                    "<scannerID>" + connectedScanner.Id + "</scannerID>" + 
                    "<cmdArgs>" + 
                        "<arg-int>90</arg-int>" + 
                    "</cmdArgs>" + 
                "</inArgs>";
connectedScanner.ExecuteCommand(OpCode.RSM_SET_ACTION, inXml);

Get Battery Statistics

ExecuteCommand API with RSM_ATTRIBUTE_GET OpCode and by listing the following attribute IDs in the input xml, the battery statistic data can be retrieved via the output xml.

Table 4 Battery Statistic Attribute Values

Description Attribute Values
Voltage 30010
Current 30011
State of Charge 30012
State of Heath Meter 30013
Temperature Present 30016
Model Number 30017
Manufacture Date 30018
Firmware Version 30019
Full Charge Capacity 30020
Charge Cycle Consumed 30021
Remaining Time To Complete Charging 30023
Temperature Highest 30024
Temperature Lowest 30025
Charge Status 30026
Remaining Capacity 30027
Design Capacity 30029
Serial Number 30030


// Get battery statistics
void getBatteryStatistics(string[] batteryStatisticsAttributesIDs)
{
    string batteryStatusAttributes = "";
    for (int i = 0; i < batteryStatisticsAttributesIDs.Length; i++)
    {
        if (i == batteryStatisticsAttributesIDs.Length - 1)
        {
            batteryStatusAttributes += batteryStatisticsAttributesIDs[i];
        }
        else    
        {
            batteryStatusAttributes += batteryStatisticsAttributesIDs[i] + ",";
        }
    }

    //In put xml
    string inXml = "<inArgs>" + 
                        "<scannerID>" + connectedScanner.Id + "</scannerID>" +
                        "<cmdArgs>" +
                            "<arg-xml>" +
                                "<attrib_list>" +
                                    batteryStatusAttributes +
                                "</attrib_list>" +
                            "</arg-xml>" +
                        "</cmdArgs>" +
                    "</inArgs>";

    //Out put result 
    string outXml = connectedScanner.ExecuteCommand(OpCode.RSM_ATTRIBUTE_GET, inXml);
    Console.WriteLine("Get Battery Statistics Result : ", outXml);
}

// Refer Table 4 for battery statistics attribute ids 
string[] batteryStatusAttributesIdList = {"30017","30018","30019","30029","30030"};

getBatteryStatistics(batteryStatusAttributesIdList);                                                                         

NOTE: For a list of a scanner's supported attribute (parameter) numbers and definitions, refer to the Product Reference Guide for that model scanner, available from the Zebra Support website at http://www.zebra.com/support. Attributes include configuration parameters, monitored data, and asset tracking information.

References:

  • Find or Search for Scanner Parameters in 123Scan Config File.
  • List of Programmable RSM Scanner Parameters Dictionary Table Definition.

Firmware Update

ScannerFirmwarerUpdate API can be used to update the firmware of a scanner using a plugin file and this API expects the path to the firmware plugin file as a string value.


string filePath = "file path to the plugin file";
scanner.ScannerFirmwareUpdate(filePath);

Firmware Update Event

This event occurs when firmware update is in progress.


// Subscribe for the FirmwareUpdate event
scanners.FirmwareUpdate += FirmwareUpdate;

// Event handler for firmware update. 
private async void firmwareUpdateEvent(FirmwareUpdateEvent firmwareUpdateEvent)
{
    int maxRecord = firmwareUpdateEvent.MaxRecords;
    int currentRecord = firmwareUpdateEvent.CurrentRecord;
    double total = Convert.ToDouble(maxRecord);
    double current = Convert.ToDouble(currentRecord);
    double percentage = ((current / total) * 100);

    Console.WriteLine("Firmware update percentage " + percentage);
}

Abort Firmware Update

AbortFirmwarerUpdate API is used to abort ongoing firmware update.


// Abort ongoing firmware update.
// 'scanner' is a connected Scanner object.
scanner.AbortFirmwareUpdate();

Start New Firmware

Once the firmware update process is finished, it is required for the firmware to be launched on to the scanner and be rebooted. StartNewFirmware method is used to launch the new firmware to the scanner.


// 'scanner' is a connected Scanner object.
scanner.StartNewFirmware();

Read Weight

These APIs are only supported in the MP70XX/MP72XX scanners connected through SNAPI mode on Android. These can be used to achieve functions such as Enable/Disable Scale, Reset Scale, Zero Scale and Read weight.


// Enable the scale
String status = scanner.ScaleEnable();

// Disable the scale
String status = scanner.ScaleDisable();

// Reset the scale
String status = scanner.ResetScale();

// Set zero scale
String status = scanner.ZeroScale();

// Read Weight
WeightInfo weight = scanner.ReadWeight();