Autoclaim POS Bluetooth Barcode scanner when in reach

219 Views Asked by At

I have a requirement for an UWP app to claim any paired Bluetooth scanner device upon app start, but also after the Bluetooth scanner power cycles, or comes back in range after being out of range. The users shouldn't even have to think about connecting/claiming. Turning on the scanner device and having the UWP app running should do it.

I haven't found any reliable method or event to automatically detect when a Bluetooth scanner is in range and 'ready to be claimed'. (For instance after going out/in of reach or after turning on or power cycling the Bluetooth scanner device.)

Basically this resulted in the following code, in which my code continually tries to claim any Bluetooth scanner detected by DeviceWatcher. It will do so until it succeeds. For any unclaimed scanners in the DeviceWatcher, claim attempts are made on a timer. Also I have to use a rather ugly try/catch construction to prevent unsuccessful ClaimScannerAsync() calls from crashing the app, as this method doesn't seem to throw it's own inherited Exception class.

This does seem to work, but I do wonder if there's a better method for this.

Is there a better way to check when ready-to-claim Bluetooth scanners are in range?

Please take the claiming process for Bluetooth barcode scanners into consideration.

  1. First pair the Bluetooth in Windows settings, the device will then be available and show up as paired. Even if it is turned off.
  2. Only after actually claiming the scanner in an app, the device will show up as connected.

(I'm pasting the code I described for reference and for other people who might have the same requirement)

private readonly List<BarcodeScanner> btScannerList = new List<BarcodeScanner>();
private readonly List<ClaimedBarcodeScanner> ClaimedScannerList = new List<ClaimedBarcodeScanner>();

/// <summary>
/// Function to claim scanner with retry timer in case of claim failure (used to reclaim scanner when scanner can go out/in of range or turned on/off)
/// In this case a device removed event is not fired by DeviceWatcher
/// This function is called for each bluetooth scanner detected by DeviceWatcher
/// </summary>
/// <param name="deviceId"></param>
private async void TryClaimBtScannerByDeviceId(string deviceId)
{
    try
    {
        if (!await this.ClaimBtScannerByDeviceId(deviceId))
        {
            Log.Debug($"BarcodeService.TryClaimBtScannerByDeviceId Failed to reconnect, setting timer to reconnect.");
            this.SetReclaimTimerForBtScanner(deviceId);
        }
    }
    catch
    {
        Log.Debug($"BarcodeService.TryClaimBtScannerByDeviceId Exception while trying to reconnect (probably a timeout), setting timer to reconnect.");
        this.SetReclaimTimerForBtScanner(deviceId);
    }
}

private void SetReclaimTimerForBtScanner(string deviceId)
{
    var timer = new System.Timers.Timer
    {
        Interval = 3000,
        AutoReset = false
    };
    timer.Elapsed += delegate { this.TryClaimBtScannerByDeviceId(deviceId); };
    timer.Start();
}

private async Task<bool> ClaimBtScannerByDeviceId(string deviceId)
{
    var scanner = await BarcodeScanner.FromIdAsync(deviceId);
    scanner.StatusUpdated += this.Scanner_StatusUpdated;
    this.btScannerList.Add(scanner);
    return await this.ClaimScannerAsync(scanner);
}

private void Scanner_StatusUpdated(BarcodeScanner sender, BarcodeScannerStatusUpdatedEventArgs args)
{
    if (args.Status == BarcodeScannerStatus.OffOrOffline || args.Status == BarcodeScannerStatus.Offline || args.Status == BarcodeScannerStatus.Off)
    {
        Log.Information($"BarcodeService.DeviceWatcher StatusUpdated to off or offline, setting reclaim timer for deviceId: {sender.DeviceId} -> {args.Status}");
        var deviceId = sender.DeviceId;
        this.UnsetScannerByDeviceId(deviceId);
        this.SetReclaimTimerForBtScanner(deviceId);
    }
}

private async Task<bool> ClaimScannerAsync(BarcodeScanner scanner)
{
    Log.Information($"BarcodeService.ClaimBarcodeScannerAsync() Trying to (re)claim scanner with DeviceId: {scanner.DeviceId} for exclusive use...");

    // after successful creation, claim the scanner for exclusive use and enable it so that data reveived events are received.
    var claimedScanner = await scanner.ClaimScannerAsync();

    if (claimedScanner == null)
    {
        Log.Warning($"BarcodeService.ClaimBarcodeScannerAsync() Couldn't claim barcode scanner for exclusive use (deviceid {scanner.DeviceId})");
        return false;
    }
    else
    {
        Log.Information($"BarcodeService.ClaimBarcodeScannerAsync() Claimed scanner for exclusive use. Setting up event handlers...");

        // It is always a good idea to have a release device requested event handler. If this event is not handled, there are chances of another app can
        // claim ownsership of the barcode scanner.
        claimedScanner.ReleaseDeviceRequested += this.ClaimedScanner_ReleaseDeviceRequested;

        // after successfully claiming, attach the datareceived event handler.
        claimedScanner.DataReceived += this.ClaimedScanner_DataReceived;

        // Ask the API to decode the data by default. By setting this, API will decode the raw data from the barcode scanner and
        // send the ScanDataLabel and ScanDataType in the DataReceived event
        claimedScanner.IsDecodeDataEnabled = true;

        // enable the scanner.
        // Note: If the scanner is not enabled (i.e. EnableAsync not called), attaching the event handler will not be any useful because the API will not fire the event
        // if the claimedScanner has not beed Enabled
        await claimedScanner.EnableAsync();

        this.ClaimedScannerList.Add(claimedScanner);
        Log.Information("BarcodeService.ClaimBarcodeScannerAsync() Ready to scan. Device ID: " + claimedScanner.DeviceId);
        return true;
    }
}

public void UnsetScannerByDeviceId(string deviceId)
{
    try
    {
        foreach (var claimedScanner in this.ClaimedScannerList.Where(x => x.DeviceId.Equals(deviceId)).ToList())
        {
            this.DisposeClaimedScanner(claimedScanner);
        }

        foreach (var scanner in this.btScannerList.Where(x => x.DeviceId.Equals(deviceId)).ToList())
        {
            this.DisposeScanner(scanner);
        }
    }
    catch
    {
        Log.Warning($"BarcodeService.UnsetScannerByDeviceId() Error while disposing scanner with scannerId: {deviceId}");
    }

}

private void DisposeScanner(BarcodeScanner scanner)
{
    scanner.StatusUpdated -= this.Scanner_StatusUpdated;
    scanner.Dispose();
    this.btScannerList.Remove(scanner);
    scanner = null;
}

private void DisposeClaimedScanner(ClaimedBarcodeScanner claimedScanner)
{
    // Detach the event handlers
    claimedScanner.DataReceived -= this.ClaimedScanner_DataReceived;
    claimedScanner.ReleaseDeviceRequested -= this.ClaimedScanner_ReleaseDeviceRequested;

    // Release the Barcode Scanner and set to null
    claimedScanner.Dispose();
    this.ClaimedScannerList.Remove(claimedScanner);
    claimedScanner = null;
}
0

There are 0 best solutions below