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 2022.


Development Environment

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

Microsoft Windows

macOS

  • A mac that is compatible with the latest version of Xcode is required as the prerequisites. Review minimum requirements and supported SDKs for more details.
  • Install the latest version of Xcode.
  • Install Visual Studio 2022 for Mac 17.6 or higher version.
  • Ensure the below workloads are installed.

    • .NET
    • .NET MAUI
    • Android
    • iOS
    Figure 2: Workloads (.Net, .NET MAUI)

    Figure 3: Workloads (Android, iOS)


Create .NET MAUI Project

  • Open Visual Studio 2022, create New Project → Multiplatform → App → Select .NET MAUI App as the template for the project.

    Figure 4: Microsoft Visual Studio Project Template

  • Select the target .NET Framework as .NET 7.0.

    Figure 5: Target .NET Framework

  • Provide a project name and create the project.

    Figure 6: .NET MAUI Project Configurations

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

    Figure 7: Folder Structure

  • Add the provided Android binding dll(ZebraAndroidScannerLibrary.dll) and Android wrapper dll (ZebraBarcodeScannerSDK.dll) inside the Android directory.

    Figure 8: Folder Structure (Android)

  • Add the provided iOS binding dll (ScannerSDKiOSBinding.dll) and iOS wrapper dll (ZebraBarcodeScannerSDK.dll) inside the iOS directory.

    Figure 9: Folder Structure (iOS)

  • Right click on project file → Edit project file.

    Figure 10: Project Properties

  • Add the below lines inside the project file to add the libraries as references for the project.

    Figure 11: Binding Libraries

  • Update the Info.plist as described here by opening the Info.plist file through Xcode.

    Figure 12: Update info.plist Properties

  • Add the following entries under Required background modes property.

    • App communicates with an accessory.
    • App communicates using CoreBluetooth.
    • App shares data using CoreBluetooth.

    Figure 13: Background Properties

  • Add following entries under Supported external accessory protocols property.

    • com.zebra.scanner.SSI
    • com.motorolasolutions.CS4070_ssi

    Figure 14: Accessory Protocols

  • Select Privacy- Bluetooth Always Usage Description property and add Bluetooth is used to discover, connect and communicate with nearby devices as the property value. Save and close the opened Xcode file.

    Figure 15: Bluetooth Usage Description

  • 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;

SDK Version

Version information could be queried as follows.


//Create an instance of the ScannerSDK
ScannerSDK scannerSdk = new ScannerSDK();

//Get the scanner SDK version
string version = scannerSdk.Version;

Set Operation Mode

Set the operation mode of the reader.


//Create an instance of the Scanners
Scanners scanners = scannerSdk.ScannerManager;

//Set Operation Mode for Scanner SDK
scanners.SetOperationMode(OpMode.OPMODE_MFI);                 

The support is given for the following operation modes,

iOS

  • OPMODE_MFI
  • OPMODE_BTLE
  • OPMODE_MFI_BTLE

Android

  • OPMODE_SSI
  • OPMODE_SNAPI
  • OPMODE_BTLE

Enable Available Scanner Detection

Enable/Disable option for Enable Available Scanner Detection API.


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

// Disable available scanner detection for SDK.
scanners.EnableAvailableScannersDetection(false);                             

Subscribe for Events

Subscribe for events required by the SDK user.


// Subscribe to scanner appearance and scanner disappearance events.
scanners.SubscribeForEvents((int)Notifications.EVENT_SCANNER_APPEARANCE | (int)Notifications.EVENT_SCANNER_DISAPPERANCE)

Available events.

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

Get Available Scanner List

Query currently available scanner list as follows.

Scanners paired with the device via MFI mode are returned by the SDK if the operation mode is set to "OPMODE_MFI".

Scanners which are discoverable to the device are returned by the SDK if the operation mode is set to "OPMODE_BTLE".


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

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 Pairing Barcode Type 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 Pairing Barcode Type 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

Connect to the first available scanner.


// Connect to a given scanner
discoveredScannerList[0].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/disable the connected barcode scanner.


// Enable a connected barcode scanner. 'scanner' is a connected Scanner object
scanner.EnableScanner(); 

// Disable a connected barcode scanner. 'scanner' is a connected Scanner object
scanner.DisableScanner();

Enable Scanner Auto Re-Connection

The enabling/disabling of automatic re-connection of the scanner is accomplished here.


// 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);

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

The enable/disable of pick list mode can be achieved with the following API.


// 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 a symbology
SymbologyEnableDisable(symbologyIdList, true);

// Disable a symbology
SymbologyEnableDisable(symbologyIdList, false);

This will disable all symbologies.


// Disable all symbologies 
scanner.DisableAllSymbologies();

LED Blink On/Off

This code snippet will start/stop blinking the LED on the reader.


// 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

By listing the following attribute IDs in the inXml, the battery statistic data can be retrieved via the outXml.

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 5 for battery statistics attribute ids 
string[] batteryStatusAttributesIdList = {"30017","30018","30019","30029","30030"};

getBatteryStatistics(batteryStatusAttributesIdList);                                                                         

Firmware Update Event


// 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);
}

Read Weight

These APIs are only supported in the MP6000/MP7000 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();