1 | // -----------------------------------------------------------------------
|
---|
2 | // <copyright file="KinectSensorChooser.cs" company="Microsoft">
|
---|
3 | // Copyright (c) Microsoft Corporation. All rights reserved.
|
---|
4 | // </copyright>
|
---|
5 | // -----------------------------------------------------------------------
|
---|
6 |
|
---|
7 | namespace Microsoft.Kinect.Toolkit
|
---|
8 | {
|
---|
9 | using System;
|
---|
10 | using System.Collections.Generic;
|
---|
11 | using System.ComponentModel;
|
---|
12 | using System.Diagnostics;
|
---|
13 | using System.IO;
|
---|
14 | using Microsoft.Kinect;
|
---|
15 |
|
---|
16 | /// <summary>
|
---|
17 | /// Sensor chooser has three high-level states:
|
---|
18 | /// ChooserStatus.None - chooser hasn't been started or is stopped
|
---|
19 | /// ChooserStatus.SensorStarted - chooser has found and started a sensor for you.
|
---|
20 | /// ChooserStatus.[everything else] - chooser has not found you a sensor and here is why.
|
---|
21 | /// Because there may be multiple sensors connected to the system, multiple flags
|
---|
22 | /// may get set when the chooser cannot get you a sensor.
|
---|
23 | /// </summary>
|
---|
24 | [Flags]
|
---|
25 | public enum ChooserStatus
|
---|
26 | {
|
---|
27 | /// <summary>
|
---|
28 | /// Chooser has not been started or it has been stopped
|
---|
29 | /// </summary>
|
---|
30 | None = 0x00000000,
|
---|
31 |
|
---|
32 | /// <summary>
|
---|
33 | /// Don't have a sensor yet, some sensor is initializing, you may not get it
|
---|
34 | /// </summary>
|
---|
35 | SensorInitializing = 0x00000001,
|
---|
36 |
|
---|
37 | /// <summary>
|
---|
38 | /// This KinectSensorChooser has a connected and started sensor.
|
---|
39 | /// </summary>
|
---|
40 | SensorStarted = 0x00000002,
|
---|
41 |
|
---|
42 | /// <summary>
|
---|
43 | /// There are no sensors available on the system. If one shows up
|
---|
44 | /// we will try to use it automatically.
|
---|
45 | /// </summary>
|
---|
46 | NoAvailableSensors = 0x00000010,
|
---|
47 |
|
---|
48 | /// <summary>
|
---|
49 | /// Available sensor is in use by another application
|
---|
50 | /// </summary>
|
---|
51 | SensorConflict = 0x00000020,
|
---|
52 |
|
---|
53 | /// <summary>
|
---|
54 | /// The available sensor is not powered. If it receives power we
|
---|
55 | /// will try to use it automatically.
|
---|
56 | /// </summary>
|
---|
57 | SensorNotPowered = 0x00000040,
|
---|
58 |
|
---|
59 | /// <summary>
|
---|
60 | /// There is not enough bandwidth on the USB controller available
|
---|
61 | /// for this sensor.
|
---|
62 | /// </summary>
|
---|
63 | SensorInsufficientBandwidth = 0x00000080,
|
---|
64 |
|
---|
65 | /// <summary>
|
---|
66 | /// Available sensor is not genuine.
|
---|
67 | /// </summary>
|
---|
68 | SensorNotGenuine = 0x00000100,
|
---|
69 |
|
---|
70 | /// <summary>
|
---|
71 | /// Available sensor is not supported
|
---|
72 | /// </summary>
|
---|
73 | SensorNotSupported = 0x00000200,
|
---|
74 |
|
---|
75 | /// <summary>
|
---|
76 | /// Available sensor has an error
|
---|
77 | /// </summary>
|
---|
78 | SensorError = 0x00000400,
|
---|
79 | }
|
---|
80 |
|
---|
81 | /// <summary>
|
---|
82 | /// Component that automatically finds a Kinect and handles updates
|
---|
83 | /// to the sensor.
|
---|
84 | /// </summary>
|
---|
85 | public class KinectSensorChooser : INotifyPropertyChanged
|
---|
86 | {
|
---|
87 | private readonly object lockObject = new object();
|
---|
88 |
|
---|
89 | private readonly ContextEventWrapper<KinectChangedEventArgs> kinectChangedContextWrapper =
|
---|
90 | new ContextEventWrapper<KinectChangedEventArgs>(ContextSynchronizationMethod.Post);
|
---|
91 |
|
---|
92 | private readonly ContextEventWrapper<PropertyChangedEventArgs> propertyChangedContextWrapper =
|
---|
93 | new ContextEventWrapper<PropertyChangedEventArgs>(ContextSynchronizationMethod.Post);
|
---|
94 |
|
---|
95 | private readonly Dictionary<PropertyChangedEventHandler, EventHandler<PropertyChangedEventArgs>> changedHandlers =
|
---|
96 | new Dictionary<PropertyChangedEventHandler, EventHandler<PropertyChangedEventArgs>>();
|
---|
97 |
|
---|
98 | private bool isStarted;
|
---|
99 | private string requiredConnectionId;
|
---|
100 |
|
---|
101 | /// <summary>
|
---|
102 | /// Event triggered when the choosen KinectSensor changed. This is
|
---|
103 | /// roughly equivalent to monitoring the PropertyChanged event
|
---|
104 | /// and watching for the "Kinect" property.
|
---|
105 | /// </summary>
|
---|
106 | public event EventHandler<KinectChangedEventArgs> KinectChanged
|
---|
107 | {
|
---|
108 | // ContextEventWrapper<> is already thread safe so no locking
|
---|
109 | add { this.kinectChangedContextWrapper.AddHandler(value); }
|
---|
110 |
|
---|
111 | remove { this.kinectChangedContextWrapper.RemoveHandler(value); }
|
---|
112 | }
|
---|
113 |
|
---|
114 | public event PropertyChangedEventHandler PropertyChanged
|
---|
115 | {
|
---|
116 | // ContextEventWrapper<> needs EvendHandler<> defined
|
---|
117 | // handlers. PropertyChangedEventHandler is not like
|
---|
118 | // that so we wrap it in a handler that is. These
|
---|
119 | // handlers need to be tracked so the proper thing
|
---|
120 | // gets removed.
|
---|
121 | // Need to lock to protect the changedHandlers data.
|
---|
122 | add
|
---|
123 | {
|
---|
124 | lock (lockObject)
|
---|
125 | {
|
---|
126 | EventHandler<PropertyChangedEventArgs> handler = (sender, args) => value(sender, args);
|
---|
127 | changedHandlers[value] = handler;
|
---|
128 | this.propertyChangedContextWrapper.AddHandler(handler);
|
---|
129 | }
|
---|
130 | }
|
---|
131 |
|
---|
132 | remove
|
---|
133 | {
|
---|
134 | lock (lockObject)
|
---|
135 | {
|
---|
136 | EventHandler<PropertyChangedEventArgs> handler = changedHandlers[value];
|
---|
137 | changedHandlers.Remove(value);
|
---|
138 | this.propertyChangedContextWrapper.RemoveHandler(handler);
|
---|
139 | }
|
---|
140 | }
|
---|
141 | }
|
---|
142 |
|
---|
143 | /// <summary>
|
---|
144 | /// The DeviceConnectionId for the sensor. Set this to null to
|
---|
145 | /// have the KinectSensorChooser find the first available sensor.
|
---|
146 | /// </summary>
|
---|
147 | public string RequiredConnectionId
|
---|
148 | {
|
---|
149 | get
|
---|
150 | {
|
---|
151 | return requiredConnectionId;
|
---|
152 | }
|
---|
153 |
|
---|
154 | set
|
---|
155 | {
|
---|
156 | if (value == requiredConnectionId)
|
---|
157 | {
|
---|
158 | return;
|
---|
159 | }
|
---|
160 |
|
---|
161 | using (var callbackLock = new CallbackLock(lockObject))
|
---|
162 | {
|
---|
163 | // Check again in case someone else changed something
|
---|
164 | // while we were waiting on the lock.
|
---|
165 | if (value == requiredConnectionId)
|
---|
166 | {
|
---|
167 | // We are already looking for the sensor they want.
|
---|
168 | return;
|
---|
169 | }
|
---|
170 |
|
---|
171 | requiredConnectionId = value;
|
---|
172 |
|
---|
173 | if (requiredConnectionId == null)
|
---|
174 | {
|
---|
175 | // We went from having a required connection id
|
---|
176 | // to not having one. Nothing more to do.
|
---|
177 | return;
|
---|
178 | }
|
---|
179 |
|
---|
180 | if (this.Kinect != null && this.Kinect.DeviceConnectionId == requiredConnectionId)
|
---|
181 | {
|
---|
182 | // The sensor we have happens to be the one they asked for.
|
---|
183 | return;
|
---|
184 | }
|
---|
185 |
|
---|
186 | // We either have no sensor or the sensor we have isn't the one they asked for
|
---|
187 | TryFindAndStartKinect(callbackLock);
|
---|
188 | }
|
---|
189 | }
|
---|
190 | }
|
---|
191 |
|
---|
192 | /// <summary>
|
---|
193 | /// The sensor that this component has connected to.
|
---|
194 | /// When this changes clients will get INotifyPropertyChanged events.
|
---|
195 | /// </summary>
|
---|
196 | public KinectSensor Kinect { get; private set; }
|
---|
197 |
|
---|
198 | /// <summary>
|
---|
199 | /// The status of the current sensor or why we cannot retrieve a sensor.
|
---|
200 | /// When this changes clients will get INotifyPropertyChanged events.
|
---|
201 | /// </summary>
|
---|
202 | public ChooserStatus Status { get; private set; }
|
---|
203 |
|
---|
204 | /// <summary>
|
---|
205 | /// Starts choosing a sensor. In the typical case, a sensor will
|
---|
206 | /// be found and events will be fired before this function returns.
|
---|
207 | /// </summary>
|
---|
208 | public void Start()
|
---|
209 | {
|
---|
210 | if (!isStarted)
|
---|
211 | {
|
---|
212 | using (var callbackLock = new CallbackLock(lockObject))
|
---|
213 | {
|
---|
214 | // Check again in case someone else started while
|
---|
215 | // we were waiting on the lock.
|
---|
216 | if (!isStarted)
|
---|
217 | {
|
---|
218 | isStarted = true;
|
---|
219 |
|
---|
220 | KinectSensor.KinectSensors.StatusChanged += KinectSensorsOnStatusChanged;
|
---|
221 |
|
---|
222 | TryFindAndStartKinect(callbackLock);
|
---|
223 | }
|
---|
224 | }
|
---|
225 | }
|
---|
226 | }
|
---|
227 |
|
---|
228 | /// <summary>
|
---|
229 | /// Gives up the current sensor if it has one. Stops watching for new sensors.
|
---|
230 | /// </summary>
|
---|
231 | public void Stop()
|
---|
232 | {
|
---|
233 | if (isStarted)
|
---|
234 | {
|
---|
235 | using (var callbackLock = new CallbackLock(lockObject))
|
---|
236 | {
|
---|
237 | // Check again in case someone stopped us while
|
---|
238 | // we were waiting on the lock.
|
---|
239 | if (isStarted)
|
---|
240 | {
|
---|
241 | isStarted = false;
|
---|
242 |
|
---|
243 | KinectSensor.KinectSensors.StatusChanged -= KinectSensorsOnStatusChanged;
|
---|
244 |
|
---|
245 | SetSensorAndStatus(callbackLock, null, ChooserStatus.None);
|
---|
246 | }
|
---|
247 | }
|
---|
248 | }
|
---|
249 | }
|
---|
250 |
|
---|
251 | /// <summary>
|
---|
252 | /// Called to retry finding a sensor when the SensorConflict or
|
---|
253 | /// NoAvailableSensors flags are set.
|
---|
254 | /// </summary>
|
---|
255 | public void TryResolveConflict()
|
---|
256 | {
|
---|
257 | using (var callbackLock = new CallbackLock(lockObject))
|
---|
258 | {
|
---|
259 | TryFindAndStartKinect(callbackLock);
|
---|
260 | }
|
---|
261 | }
|
---|
262 |
|
---|
263 | private static ChooserStatus GetErrorStatusFromSensor(KinectSensor sensor)
|
---|
264 | {
|
---|
265 | ChooserStatus retval;
|
---|
266 | switch (sensor.Status)
|
---|
267 | {
|
---|
268 | case KinectStatus.Undefined:
|
---|
269 | retval = ChooserStatus.SensorError;
|
---|
270 | break;
|
---|
271 | case KinectStatus.Disconnected:
|
---|
272 | retval = ChooserStatus.SensorError;
|
---|
273 | break;
|
---|
274 | case KinectStatus.Connected:
|
---|
275 | // not an error state
|
---|
276 | retval = 0;
|
---|
277 | break;
|
---|
278 | case KinectStatus.Initializing:
|
---|
279 | retval = ChooserStatus.SensorInitializing;
|
---|
280 | break;
|
---|
281 | case KinectStatus.Error:
|
---|
282 | retval = ChooserStatus.SensorError;
|
---|
283 | break;
|
---|
284 | case KinectStatus.NotPowered:
|
---|
285 | retval = ChooserStatus.SensorNotPowered;
|
---|
286 | break;
|
---|
287 | case KinectStatus.NotReady:
|
---|
288 | retval = ChooserStatus.SensorError;
|
---|
289 | break;
|
---|
290 | case KinectStatus.DeviceNotGenuine:
|
---|
291 | retval = ChooserStatus.SensorNotGenuine;
|
---|
292 | break;
|
---|
293 | case KinectStatus.DeviceNotSupported:
|
---|
294 | retval = ChooserStatus.SensorNotSupported;
|
---|
295 | break;
|
---|
296 | case KinectStatus.InsufficientBandwidth:
|
---|
297 | retval = ChooserStatus.SensorInsufficientBandwidth;
|
---|
298 | break;
|
---|
299 | default:
|
---|
300 | throw new ArgumentOutOfRangeException("sensor");
|
---|
301 | }
|
---|
302 |
|
---|
303 | return retval;
|
---|
304 | }
|
---|
305 |
|
---|
306 | private void KinectSensorsOnStatusChanged(object sender, StatusChangedEventArgs e)
|
---|
307 | {
|
---|
308 | if (e != null)
|
---|
309 | {
|
---|
310 | if ((e.Sensor == this.Kinect) || (this.Kinect == null))
|
---|
311 | {
|
---|
312 | using (var callbackLock = new CallbackLock(lockObject))
|
---|
313 | {
|
---|
314 | // Check again in case things changed while we were
|
---|
315 | // waiting on the lock.
|
---|
316 | if ((e.Sensor == this.Kinect) || (this.Kinect == null))
|
---|
317 | {
|
---|
318 | // Something about our sensor changed or we don't have a sensor.
|
---|
319 | TryFindAndStartKinect(callbackLock);
|
---|
320 | }
|
---|
321 | }
|
---|
322 | }
|
---|
323 | }
|
---|
324 | }
|
---|
325 |
|
---|
326 | /// <summary>
|
---|
327 | /// Helper to update our external status.
|
---|
328 | /// </summary>
|
---|
329 | /// <param name="callbackLock">Used to delay notifications to the client until after we exit the lock.</param>
|
---|
330 | /// <param name="newKinect">The new sensor</param>
|
---|
331 | /// <param name="newChooserStatus">The status we want to report to clients</param>
|
---|
332 | private void SetSensorAndStatus(CallbackLock callbackLock, KinectSensor newKinect, ChooserStatus newChooserStatus)
|
---|
333 | {
|
---|
334 | KinectSensor oldKinect = Kinect;
|
---|
335 | if (oldKinect != newKinect)
|
---|
336 | {
|
---|
337 | if (oldKinect != null)
|
---|
338 | {
|
---|
339 | oldKinect.Stop();
|
---|
340 | }
|
---|
341 |
|
---|
342 | Kinect = newKinect;
|
---|
343 |
|
---|
344 | callbackLock.LockExit += () => this.kinectChangedContextWrapper.Invoke(this, new KinectChangedEventArgs(oldKinect, newKinect));
|
---|
345 | callbackLock.LockExit += () => RaisePropertyChanged("Kinect");
|
---|
346 | }
|
---|
347 |
|
---|
348 | if (Status != newChooserStatus)
|
---|
349 | {
|
---|
350 | Status = newChooserStatus;
|
---|
351 |
|
---|
352 | callbackLock.LockExit += () => RaisePropertyChanged("Status");
|
---|
353 | }
|
---|
354 | }
|
---|
355 |
|
---|
356 | /// <summary>
|
---|
357 | /// Called when we don't have a sensor or possibly have the wrong sensor
|
---|
358 | /// and we want to see if we can get one.
|
---|
359 | /// </summary>
|
---|
360 | private void TryFindAndStartKinect(CallbackLock callbackLock)
|
---|
361 | {
|
---|
362 | if (!isStarted)
|
---|
363 | {
|
---|
364 | // We aren't started so we don't need to be finding anything.
|
---|
365 | Debug.Assert(Status == ChooserStatus.None, "isStarted and Status out of sync");
|
---|
366 | return;
|
---|
367 | }
|
---|
368 |
|
---|
369 | if (Kinect != null && Kinect.Status == KinectStatus.Connected)
|
---|
370 | {
|
---|
371 | if (requiredConnectionId == null)
|
---|
372 | {
|
---|
373 | // we already have an appropriate sensor
|
---|
374 | Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state");
|
---|
375 | return;
|
---|
376 | }
|
---|
377 |
|
---|
378 | if (Kinect.DeviceConnectionId == requiredConnectionId)
|
---|
379 | {
|
---|
380 | // we already have the requested sensor
|
---|
381 | Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state");
|
---|
382 | return;
|
---|
383 | }
|
---|
384 | }
|
---|
385 |
|
---|
386 | KinectSensor newSensor = null;
|
---|
387 | ChooserStatus newStatus = 0;
|
---|
388 |
|
---|
389 | if (KinectSensor.KinectSensors.Count == 0)
|
---|
390 | {
|
---|
391 | newStatus = ChooserStatus.NoAvailableSensors;
|
---|
392 | }
|
---|
393 | else
|
---|
394 | {
|
---|
395 | foreach (KinectSensor sensor in KinectSensor.KinectSensors)
|
---|
396 | {
|
---|
397 | if (requiredConnectionId != null && sensor.DeviceConnectionId != requiredConnectionId)
|
---|
398 | {
|
---|
399 | // client has set a required connection Id and this
|
---|
400 | // sensor does not have that Id
|
---|
401 | newStatus |= ChooserStatus.NoAvailableSensors;
|
---|
402 | continue;
|
---|
403 | }
|
---|
404 |
|
---|
405 | if (sensor.Status != KinectStatus.Connected)
|
---|
406 | {
|
---|
407 | // Sensor is in some unusable state
|
---|
408 | newStatus |= GetErrorStatusFromSensor(sensor);
|
---|
409 | continue;
|
---|
410 | }
|
---|
411 |
|
---|
412 | if (sensor.IsRunning)
|
---|
413 | {
|
---|
414 | // Sensor is already in use by this application
|
---|
415 | newStatus |= ChooserStatus.NoAvailableSensors;
|
---|
416 | continue;
|
---|
417 | }
|
---|
418 |
|
---|
419 | // There is a potentially available sensor, try to start it
|
---|
420 | try
|
---|
421 | {
|
---|
422 | sensor.Start();
|
---|
423 | }
|
---|
424 | catch (IOException)
|
---|
425 | {
|
---|
426 | // some other app has this sensor.
|
---|
427 | newStatus |= ChooserStatus.SensorConflict;
|
---|
428 | continue;
|
---|
429 | }
|
---|
430 | catch (InvalidOperationException)
|
---|
431 | {
|
---|
432 | // TODO: In multi-proc scenarios, this is getting thrown at the start before we see IOException. Need to understand.
|
---|
433 | // some other app has this sensor.
|
---|
434 | newStatus |= ChooserStatus.SensorConflict;
|
---|
435 | continue;
|
---|
436 | }
|
---|
437 |
|
---|
438 | // Woo hoo, we have a started sensor.
|
---|
439 | newStatus = ChooserStatus.SensorStarted;
|
---|
440 | newSensor = sensor;
|
---|
441 | break;
|
---|
442 | }
|
---|
443 | }
|
---|
444 |
|
---|
445 | SetSensorAndStatus(callbackLock, newSensor, newStatus);
|
---|
446 | }
|
---|
447 |
|
---|
448 | private void RaisePropertyChanged(string propertyName)
|
---|
449 | {
|
---|
450 | // We should never be in a lock at this point.
|
---|
451 | this.propertyChangedContextWrapper.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
---|
452 | }
|
---|
453 | }
|
---|
454 | }
|
---|