source: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Kinect.Toolkit/KinectSensorChooser.cs@ 28895

Last change on this file since 28895 was 28895, checked in by davidb, 10 years ago

Base Kinect Project

File size: 16.9 KB
Line 
1// -----------------------------------------------------------------------
2// <copyright file="KinectSensorChooser.cs" company="Microsoft">
3// Copyright (c) Microsoft Corporation. All rights reserved.
4// </copyright>
5// -----------------------------------------------------------------------
6
7namespace 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}
Note: See TracBrowser for help on using the repository browser.