Created
February 17, 2019 13:15
-
-
Save JasperE84/3c0fe2be1535f2738ded885b9edf4922 to your computer and use it in GitHub Desktop.
An UWP PointOfService class for keeping Bluetooth barcode scanners connected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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