Skip to content

Instantly share code, notes, and snippets.

@JasperE84
Created February 17, 2019 13:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JasperE84/3c0fe2be1535f2738ded885b9edf4922 to your computer and use it in GitHub Desktop.
Save JasperE84/3c0fe2be1535f2738ded885b9edf4922 to your computer and use it in GitHub Desktop.
An UWP PointOfService class for keeping Bluetooth barcode scanners connected
namespace MyApp.Services.BarcodeServices
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.PointOfService;
using Windows.UI.Xaml;
using static MyApp.Helpers.BarcodeHelpers;
using static MyApp.Services.LoggingServices.LoggingService;
public class BarcodeData
{
public string DataString;
public string DataType;
public string Label;
}
public class BarcodeService
{
private readonly List<ClaimedBarcodeScanner> claimedScannerList = new List<ClaimedBarcodeScanner>();
private readonly List<BarcodeScanner> scannerList = new List<BarcodeScanner>();
private DeviceWatcher btWatcher;
private List<string> scannerSuspendCache;
public BarcodeService()
{
Instance = this;
Application.Current.Suspending += new SuspendingEventHandler(this.App_Suspending);
Application.Current.Resuming += new EventHandler<object>(this.App_ResumingAsync);
this.StartService();
}
public event EventHandler<BarcodeData> DataReceived;
public static BarcodeService Instance { get; set; }
public static AppLogger<BarcodeService> Log => new AppLogger<BarcodeService>();
/// <summary>
/// Starts DeviceWatcher for PosConnectionTypes.Bluetooth and hooks up Added event.
/// </summary>
public void StartService()
{
Log.Information("BarcodeService.StartService() Starting DeviceWatcher for PosConnectionTypes.Bluetooth");
// TODO: Add non bluetooth support
var btSelector = BarcodeScanner.GetDeviceSelector(PosConnectionTypes.Bluetooth);
this.btWatcher = DeviceInformation.CreateWatcher(btSelector);
this.btWatcher.Added += this.BtWatcher_AddedAsync;
this.btWatcher.EnumerationCompleted += (DeviceWatcher sender, object args) =>
{
Log.Information("BarcodeService.DeviceWatcher EnumerationCompleted");
};
this.btWatcher.Start();
}
/// <summary>
/// Stops DeviceWatcher and disposes all (Claimed)BarcodeScanner objects.
/// </summary>
public void StopService()
{
Log.Information($"BarcodeService.StopService() Stopping DeviceWatcher");
this.btWatcher.Added -= this.BtWatcher_AddedAsync;
Log.Debug($"BarcodeService.StopService() Disposing objects in this.claimedScannerList...");
try
{
foreach (var claimedScanner in this.claimedScannerList?.ToList())
{
this.DisposeClaimedScanner(claimedScanner);
}
}
catch (Exception e)
{
Log.Warning(e, $"BarcodeService.StopService() Error while disposing claimedScannerList items");
}
Log.Debug($"BarcodeService.StopService() Disposing objects in this.scannerList...");
try
{
foreach (var scanner in this.scannerList?.ToList())
{
this.DisposeScanner(scanner);
}
}
catch (Exception e)
{
Log.Warning(e, $"BarcodeService.StopService() Error while disposing scannerList items");
}
this.btWatcher.Stop();
}
/// <summary>
/// Disposes a specific (Claimed)BarcodeScanner
/// </summary>
/// <param name="deviceId"></param>
public void UnsetScannerByDeviceId(string deviceId)
{
Log.Debug($"BarcodeService.UnsetScannerByDeviceId() Disposing objects in this.claimedScannerList with DeviceId {deviceId}...");
try
{
foreach (var claimedScanner in this.claimedScannerList.Where(x => x?.DeviceId.Equals(deviceId) ?? false || x == null).ToList())
{
this.DisposeClaimedScanner(claimedScanner);
}
}
catch (Exception e)
{
Log.Warning(e, $"BarcodeService.UnsetScannerByDeviceId() Error while disposing ClaimedScanner with scannerId: {deviceId}");
}
Log.Debug($"BarcodeService.UnsetScannerByDeviceId() Disposing objects in this.scannerList with DeviceId {deviceId}...");
try
{
foreach (var scanner in this.scannerList.Where(x => x?.DeviceId.Equals(deviceId) ?? false || x == null).ToList())
{
this.DisposeScanner(scanner);
}
}
catch (Exception e)
{
Log.Warning(e, $"BarcodeService.UnsetScannerByDeviceId() Error while disposing Scanner with scannerId: {deviceId}");
}
}
/// <summary>
/// Reconnects cached barcode scanners after application resume from suspended state and starts DeviceWatcher
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void App_ResumingAsync(object sender, object e)
{
if (this.scannerSuspendCache?.Count > 0)
{
Log.Information($"BarcodeService.App_Resuming() Reconnecting {this.scannerSuspendCache?.Count} cached DeviceId's...");
foreach (var s in this.scannerSuspendCache)
{
Log.Information($"BarcodeService.App_Resuming() Reconnecting to cached scanner {s}");
await this.TryClaimScannerByDeviceIdAsync(s);
}
}
Log.Information("BarcodeService.App_Resuming() Starting service");
this.StartService();
}
/// <summary>
/// Caches connected DeviceIds of BarcodeScanners and runs StopService() which stops DeviceWatcher and disposes all connected (Claimed)BarcodeScanners
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
{
Log.Information("BarcodeService.App_Suspending() Caching claimed scanners");
this.scannerSuspendCache = this.scannerList?.Select(x => x?.DeviceId).ToList();
this.StopService();
}
/// <summary>
/// Claims any BarcodeScanners found by DeviceWatcher which are not already in scannerSuspendedCache (prevent claiming scanners which are already claimed)
/// </summary>
/// <param name="sender"></param>
/// <param name="device"></param>
private async void BtWatcher_AddedAsync(DeviceWatcher sender, DeviceInformation device)
{
// Protect against race condition if the task runs after the app stopped the deviceWatcher.
if (sender == this.btWatcher)
{
Log.Information($"BarcodeService.BtWatcher_AddedAsync Added event triggered for deviceId: {device.Id} and devicName {device.Name}");
if (this.scannerSuspendCache?.Any(x => x == device.Id) ?? false)
{
Log.Information($"BarcodeService.BtWatcher_AddedAsync Scanner already in scannerList, not trying to claim deviceId: {device.Id} and devicName {device.Name}");
}
else
{
Log.Information($"BarcodeService.BtWatcher_AddedAsync New scanner found (not in scannerList) trying to claim deviceId: {device.Id} and devicName {device.Name}");
await this.TryClaimScannerByDeviceIdAsync(device.Id);
}
}
}
/// <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
/// </summary>
/// <param name="deviceId"></param>
private async Task TryClaimScannerByDeviceIdAsync(string deviceId)
{
try
{
if (!await this.ClaimScannerByDeviceIdAsync(deviceId))
{
Log.Debug($"BarcodeService.TryClaimScannerByDeviceIdAsync() Failed to connect, setting timer to reconnect.");
this.SetReclaimTimerForBtScanner(deviceId);
}
}
catch (Exception e)
{
Log.Warning(e, $"BarcodeService.TryClaimScannerByDeviceIdAsync() 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
{
Task.Run(async () => await this.TryClaimScannerByDeviceIdAsync(deviceId));
};
timer.Start();
}
// <summary>
/// Claims a BarcodeScanner with a specific DeviceId
/// </summary>
/// <param name="deviceId"></param>
/// <returns></returns>
private async Task<bool> ClaimScannerByDeviceIdAsync(string deviceId)
{
Log.Information($"BarcodeService.ClaimScannerByDeviceIdAsync() Getting scanner object with DeviceId: {deviceId}");
var scanner = await BarcodeScanner.FromIdAsync(deviceId);
if (scanner != null)
{
Log.Information($"BarcodeService.ClaimScannerByDeviceIdAsync() Got a BarcodeScanner object. Hooking up Scanner_StatusUpdated and adding Scanner to scannerList");
scanner.StatusUpdated += this.Scanner_StatusUpdated;
this.scannerList.Add(scanner);
return await this.ClaimScannerAsync(scanner);
}
else
{
Log.Warning($"BarcodeService.ClaimScannerByDeviceIdAsync() Couldn't get BarcodeScanner object from DeviceId, Device not found or access denied by API");
return false;
}
}
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
Log.Debug($"BarcodeService.ClaimBarcodeScannerAsync() Setting IsDecodeDataEnabled=true for DeviceId {scanner.DeviceId}");
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
Log.Debug($"BarcodeService.ClaimBarcodeScannerAsync() Running EnableAsync() for claimed scanner with DeviceId {scanner.DeviceId}");
await claimedScanner.EnableAsync();
Log.Debug($"BarcodeService.ClaimBarcodeScannerAsync() Adding ClaimedScanner to this.claimedScannerList (Scanner with DeviceId {scanner.DeviceId})");
this.claimedScannerList.Add(claimedScanner);
Log.Information("BarcodeService.ClaimBarcodeScannerAsync() Ready to scan. Device ID: " + claimedScanner.DeviceId);
return true;
}
}
private void DisposeClaimedScanner(ClaimedBarcodeScanner claimedScanner)
{
Log.Information($"BarcodeService.DisposeClaimedScanner() Disposing claimed scanner with DeviceId: {claimedScanner?.DeviceId}");
this.claimedScannerList.Remove(claimedScanner);
if (claimedScanner != null)
{
Log.Debug($"BarcodeService.DisposeClaimedScanner() Unhooking events for scanner with DeviceId: {claimedScanner?.DeviceId}");
// Detach the event handlers
claimedScanner.DataReceived -= this.ClaimedScanner_DataReceived;
claimedScanner.ReleaseDeviceRequested -= this.ClaimedScanner_ReleaseDeviceRequested;
// Release the Barcode Scanner and set to null
Log.Debug($"BarcodeService.DisposeClaimedScanner() Disposing ClaimedScanner object with DeviceId: {claimedScanner?.DeviceId}");
claimedScanner.Dispose();
claimedScanner = null;
}
}
private void DisposeScanner(BarcodeScanner scanner)
{
Log.Information($"BarcodeService.DisposeScanner() Disposing scanner with DeviceId: {scanner?.DeviceId}");
this.scannerList.Remove(scanner);
if (scanner != null)
{
scanner.StatusUpdated -= this.Scanner_StatusUpdated;
scanner.Dispose();
scanner = null;
}
}
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);
}
}
/// <summary>
/// Event handler for the DataReceived event fired when a barcode is scanned by the barcode scanner
/// </summary>
/// <param name="sender"></param>
/// <param name="args"> Contains the BarcodeScannerReport which contains the data obtained in the scan</param>
private void ClaimedScanner_DataReceived(ClaimedBarcodeScanner sender, BarcodeScannerDataReceivedEventArgs args)
{
var label = DataHelpers.GetDataLabelString(args.Report.ScanDataLabel, args.Report.ScanDataType);
var dataType = BarcodeSymbologies.GetName(args.Report.ScanDataType);
var dataString = DataHelpers.GetDataString(args.Report.ScanData);
Log.Debug($"BarcodeService triggered ClaimedScanner_DataReceived(), Label: {label}, DataType: {dataType}, DataString: {dataString}");
var data = new BarcodeData
{
Label = label,
DataType = dataType,
DataString = dataString
};
this.DataReceived?.Invoke(sender, data);
}
/// <summary>
/// Event handler for the Release Device Requested event fired when barcode scanner receives Claim request from another application
/// </summary>
/// <param name="sender"></param>
/// <param name="e"> Contains the ClamiedBarcodeScanner that is sending this request</param>
void ClaimedScanner_ReleaseDeviceRequested(object sender, ClaimedBarcodeScanner e)
{
try
{
// always retain the device
e.RetainDevice();
Log.Information("BarcodeService.ClaimedScanner_ReleaseDeviceRequested, Retaining the barcode scanner.");
}
catch (Exception ex)
{
Log.Warning(ex, "BarcodeService.ClaimedScanner_ReleaseDeviceRequested, Error on RetainDevice() API method");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment