-
-
Notifications
You must be signed in to change notification settings - Fork 120
Expand file tree
/
Copy pathDeviceListener.cs
More file actions
217 lines (172 loc) · 7.79 KB
/
DeviceListener.cs
File metadata and controls
217 lines (172 loc) · 7.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
using Device.Net.Exceptions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using timer = System.Timers.Timer;
namespace Device.Net
{
/// <summary>
/// Handles connection of devices.
/// <para>This is not the recommended approach. Please use <see cref="DeviceManager"/> where possible.</para>
/// </summary>
public sealed class DeviceListener : IDeviceListener
{
#region Fields
private bool _IsDisposed;
private readonly timer _PollTimer;
private readonly SemaphoreSlim _ListenSemaphoreSlim = new(1, 1);
private readonly ILogger _logger;
/// <summary>
/// This is the list of Devices by their filter definition. Note this is not actually keyed by the connected definition.
/// </summary>
private readonly Dictionary<string, IDevice> _CreatedDevicesByDefinition = new();
#endregion
#region Public Properties
public IDeviceFactory DeviceFactory { get; }
#endregion
#region Events
public event EventHandler<DeviceEventArgs> DeviceInitialized;
public event EventHandler<DeviceEventArgs> DeviceDisconnected;
#endregion
#region Constructor
/// <summary>
/// Handles connecting to and disconnecting from a set of potential devices by their definition
/// </summary>
/// <param name="loggerFactory"><see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.iloggerfactory"/></param>
public DeviceListener(
IDeviceFactory deviceFactory,
int? pollMilliseconds = 1000,
ILoggerFactory loggerFactory = null)
{
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<DeviceListener>();
DeviceFactory = deviceFactory ?? throw new ArgumentNullException(nameof(deviceFactory));
_PollTimer = new timer(pollMilliseconds.Value);
_PollTimer.Elapsed += PollTimer_Elapsed;
}
#endregion
#region Event Handlers
private async void PollTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (_IsDisposed)
return;
await CheckForDevicesAsync().ConfigureAwait(false);
}
#endregion
#region Public Methods
/// <summary>
/// Starts the polling for devices if polling is being used.
/// </summary>
public void Start()
{
if (_PollTimer == null)
{
throw new ValidationException(Messages.ErrorMessagePollingNotEnabled);
}
_PollTimer.Start();
}
public async Task CheckForDevicesAsync(CancellationToken cancellationToken = default)
{
try
{
if (_IsDisposed) return;
await _ListenSemaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
var connectedDeviceDefinitions = (await DeviceFactory.GetConnectedDeviceDefinitionsAsync(cancellationToken).ConfigureAwait(false)).ToList();
//Iterate through connected devices
foreach (var connectedDeviceDefinition in connectedDeviceDefinitions)
{
//TODO: What to do if there are multiple?
IDevice device = null;
if (_CreatedDevicesByDefinition.ContainsKey(connectedDeviceDefinition.DeviceId))
{
device = _CreatedDevicesByDefinition[connectedDeviceDefinition.DeviceId];
}
if (device == null)
{
//Need to use the connected device def here instead of the filter version because the filter version won't have the id or any details
device = await DeviceFactory.GetDeviceAsync(connectedDeviceDefinition, cancellationToken).ConfigureAwait(false);
if (device == null)
{
_logger.LogWarning("A connected device with id {deviceId} was detected but the factory didn't create an instance of it. Bad stuff is going to happen now.", connectedDeviceDefinition.DeviceId);
}
else
{
_CreatedDevicesByDefinition.Add(connectedDeviceDefinition.DeviceId, device);
}
}
else
{
if (device.IsInitialized) continue;
}
_logger.LogDebug("Attempting to initialize with DeviceId of {deviceId}", device.DeviceId);
//The device is not initialized so initialize it
await device.InitializeAsync(cancellationToken).ConfigureAwait(false);
//Let listeners know a registered device was initialized
DeviceInitialized?.Invoke(this, new DeviceEventArgs(device));
_logger.LogDebug(Messages.InformationMessageDeviceConnected, device.DeviceId);
}
var removeDeviceIds = new List<string>();
//Iterate through registered devices
foreach (var deviceId in _CreatedDevicesByDefinition.Keys)
{
var device = _CreatedDevicesByDefinition[deviceId];
if (connectedDeviceDefinitions.Any(cdd => cdd.DeviceId == deviceId)) continue;
if (!device.IsInitialized) continue;
//Let listeners know a registered device was disconnected
//NOTE: let the rest of the app know before disposal so that the app can stop doing whatever it's doing.
DeviceDisconnected?.Invoke(this, new DeviceEventArgs(device));
//The device is no longer connected so close it
device.Close();
removeDeviceIds.Add(deviceId);
_logger.LogDebug(Messages.InformationMessageDeviceListenerDisconnected);
}
foreach (var deviceId in removeDeviceIds)
{
_ = _CreatedDevicesByDefinition.Remove(deviceId);
}
_logger.LogDebug(Messages.InformationMessageDeviceListenerPollingComplete);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
//Log and move on
_logger.LogError(ex, Messages.ErrorMessagePollingError);
//TODO: What else to do here?
}
finally
{
if (!_IsDisposed)
_ = _ListenSemaphoreSlim.Release();
}
}
public void Stop() => _PollTimer.Stop();
public void Dispose()
{
if (_IsDisposed)
{
_logger.LogWarning(Messages.WarningMessageAlreadyDisposed, "Listener");
return;
}
_IsDisposed = true;
_logger.LogInformation(Messages.InformationMessageDisposingDevice, "Listener");
Stop();
_PollTimer?.Dispose();
foreach (var key in _CreatedDevicesByDefinition.Keys)
{
_CreatedDevicesByDefinition[key].Dispose();
}
_CreatedDevicesByDefinition.Clear();
_ListenSemaphoreSlim.Dispose();
DeviceInitialized = null;
DeviceDisconnected = null;
GC.SuppressFinalize(this);
}
#endregion
}
}