// -----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// -----------------------------------------------------------------------
namespace Microsoft.Kinect.Toolkit
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using Microsoft.Kinect;
///
/// Sensor chooser has three high-level states:
/// ChooserStatus.None - chooser hasn't been started or is stopped
/// ChooserStatus.SensorStarted - chooser has found and started a sensor for you.
/// ChooserStatus.[everything else] - chooser has not found you a sensor and here is why.
/// Because there may be multiple sensors connected to the system, multiple flags
/// may get set when the chooser cannot get you a sensor.
///
[Flags]
public enum ChooserStatus
{
///
/// Chooser has not been started or it has been stopped
///
None = 0x00000000,
///
/// Don't have a sensor yet, some sensor is initializing, you may not get it
///
SensorInitializing = 0x00000001,
///
/// This KinectSensorChooser has a connected and started sensor.
///
SensorStarted = 0x00000002,
///
/// There are no sensors available on the system. If one shows up
/// we will try to use it automatically.
///
NoAvailableSensors = 0x00000010,
///
/// Available sensor is in use by another application
///
SensorConflict = 0x00000020,
///
/// The available sensor is not powered. If it receives power we
/// will try to use it automatically.
///
SensorNotPowered = 0x00000040,
///
/// There is not enough bandwidth on the USB controller available
/// for this sensor.
///
SensorInsufficientBandwidth = 0x00000080,
///
/// Available sensor is not genuine.
///
SensorNotGenuine = 0x00000100,
///
/// Available sensor is not supported
///
SensorNotSupported = 0x00000200,
///
/// Available sensor has an error
///
SensorError = 0x00000400,
}
///
/// Component that automatically finds a Kinect and handles updates
/// to the sensor.
///
public class KinectSensorChooser : INotifyPropertyChanged
{
private readonly object lockObject = new object();
private readonly ContextEventWrapper kinectChangedContextWrapper =
new ContextEventWrapper(ContextSynchronizationMethod.Post);
private readonly ContextEventWrapper propertyChangedContextWrapper =
new ContextEventWrapper(ContextSynchronizationMethod.Post);
private readonly Dictionary> changedHandlers =
new Dictionary>();
private bool isStarted;
private string requiredConnectionId;
///
/// Event triggered when the choosen KinectSensor changed. This is
/// roughly equivalent to monitoring the PropertyChanged event
/// and watching for the "Kinect" property.
///
public event EventHandler KinectChanged
{
// ContextEventWrapper<> is already thread safe so no locking
add { this.kinectChangedContextWrapper.AddHandler(value); }
remove { this.kinectChangedContextWrapper.RemoveHandler(value); }
}
public event PropertyChangedEventHandler PropertyChanged
{
// ContextEventWrapper<> needs EvendHandler<> defined
// handlers. PropertyChangedEventHandler is not like
// that so we wrap it in a handler that is. These
// handlers need to be tracked so the proper thing
// gets removed.
// Need to lock to protect the changedHandlers data.
add
{
lock (lockObject)
{
EventHandler handler = (sender, args) => value(sender, args);
changedHandlers[value] = handler;
this.propertyChangedContextWrapper.AddHandler(handler);
}
}
remove
{
lock (lockObject)
{
EventHandler handler = changedHandlers[value];
changedHandlers.Remove(value);
this.propertyChangedContextWrapper.RemoveHandler(handler);
}
}
}
///
/// The DeviceConnectionId for the sensor. Set this to null to
/// have the KinectSensorChooser find the first available sensor.
///
public string RequiredConnectionId
{
get
{
return requiredConnectionId;
}
set
{
if (value == requiredConnectionId)
{
return;
}
using (var callbackLock = new CallbackLock(lockObject))
{
// Check again in case someone else changed something
// while we were waiting on the lock.
if (value == requiredConnectionId)
{
// We are already looking for the sensor they want.
return;
}
requiredConnectionId = value;
if (requiredConnectionId == null)
{
// We went from having a required connection id
// to not having one. Nothing more to do.
return;
}
if (this.Kinect != null && this.Kinect.DeviceConnectionId == requiredConnectionId)
{
// The sensor we have happens to be the one they asked for.
return;
}
// We either have no sensor or the sensor we have isn't the one they asked for
TryFindAndStartKinect(callbackLock);
}
}
}
///
/// The sensor that this component has connected to.
/// When this changes clients will get INotifyPropertyChanged events.
///
public KinectSensor Kinect { get; private set; }
///
/// The status of the current sensor or why we cannot retrieve a sensor.
/// When this changes clients will get INotifyPropertyChanged events.
///
public ChooserStatus Status { get; private set; }
///
/// Starts choosing a sensor. In the typical case, a sensor will
/// be found and events will be fired before this function returns.
///
public void Start()
{
if (!isStarted)
{
using (var callbackLock = new CallbackLock(lockObject))
{
// Check again in case someone else started while
// we were waiting on the lock.
if (!isStarted)
{
isStarted = true;
KinectSensor.KinectSensors.StatusChanged += KinectSensorsOnStatusChanged;
TryFindAndStartKinect(callbackLock);
}
}
}
}
///
/// Gives up the current sensor if it has one. Stops watching for new sensors.
///
public void Stop()
{
if (isStarted)
{
using (var callbackLock = new CallbackLock(lockObject))
{
// Check again in case someone stopped us while
// we were waiting on the lock.
if (isStarted)
{
isStarted = false;
KinectSensor.KinectSensors.StatusChanged -= KinectSensorsOnStatusChanged;
SetSensorAndStatus(callbackLock, null, ChooserStatus.None);
}
}
}
}
///
/// Called to retry finding a sensor when the SensorConflict or
/// NoAvailableSensors flags are set.
///
public void TryResolveConflict()
{
using (var callbackLock = new CallbackLock(lockObject))
{
TryFindAndStartKinect(callbackLock);
}
}
private static ChooserStatus GetErrorStatusFromSensor(KinectSensor sensor)
{
ChooserStatus retval;
switch (sensor.Status)
{
case KinectStatus.Undefined:
retval = ChooserStatus.SensorError;
break;
case KinectStatus.Disconnected:
retval = ChooserStatus.SensorError;
break;
case KinectStatus.Connected:
// not an error state
retval = 0;
break;
case KinectStatus.Initializing:
retval = ChooserStatus.SensorInitializing;
break;
case KinectStatus.Error:
retval = ChooserStatus.SensorError;
break;
case KinectStatus.NotPowered:
retval = ChooserStatus.SensorNotPowered;
break;
case KinectStatus.NotReady:
retval = ChooserStatus.SensorError;
break;
case KinectStatus.DeviceNotGenuine:
retval = ChooserStatus.SensorNotGenuine;
break;
case KinectStatus.DeviceNotSupported:
retval = ChooserStatus.SensorNotSupported;
break;
case KinectStatus.InsufficientBandwidth:
retval = ChooserStatus.SensorInsufficientBandwidth;
break;
default:
throw new ArgumentOutOfRangeException("sensor");
}
return retval;
}
private void KinectSensorsOnStatusChanged(object sender, StatusChangedEventArgs e)
{
if (e != null)
{
if ((e.Sensor == this.Kinect) || (this.Kinect == null))
{
using (var callbackLock = new CallbackLock(lockObject))
{
// Check again in case things changed while we were
// waiting on the lock.
if ((e.Sensor == this.Kinect) || (this.Kinect == null))
{
// Something about our sensor changed or we don't have a sensor.
TryFindAndStartKinect(callbackLock);
}
}
}
}
}
///
/// Helper to update our external status.
///
/// Used to delay notifications to the client until after we exit the lock.
/// The new sensor
/// The status we want to report to clients
private void SetSensorAndStatus(CallbackLock callbackLock, KinectSensor newKinect, ChooserStatus newChooserStatus)
{
KinectSensor oldKinect = Kinect;
if (oldKinect != newKinect)
{
if (oldKinect != null)
{
oldKinect.Stop();
}
Kinect = newKinect;
callbackLock.LockExit += () => this.kinectChangedContextWrapper.Invoke(this, new KinectChangedEventArgs(oldKinect, newKinect));
callbackLock.LockExit += () => RaisePropertyChanged("Kinect");
}
if (Status != newChooserStatus)
{
Status = newChooserStatus;
callbackLock.LockExit += () => RaisePropertyChanged("Status");
}
}
///
/// Called when we don't have a sensor or possibly have the wrong sensor
/// and we want to see if we can get one.
///
private void TryFindAndStartKinect(CallbackLock callbackLock)
{
if (!isStarted)
{
// We aren't started so we don't need to be finding anything.
Debug.Assert(Status == ChooserStatus.None, "isStarted and Status out of sync");
return;
}
if (Kinect != null && Kinect.Status == KinectStatus.Connected)
{
if (requiredConnectionId == null)
{
// we already have an appropriate sensor
Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state");
return;
}
if (Kinect.DeviceConnectionId == requiredConnectionId)
{
// we already have the requested sensor
Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state");
return;
}
}
KinectSensor newSensor = null;
ChooserStatus newStatus = 0;
if (KinectSensor.KinectSensors.Count == 0)
{
newStatus = ChooserStatus.NoAvailableSensors;
}
else
{
foreach (KinectSensor sensor in KinectSensor.KinectSensors)
{
if (requiredConnectionId != null && sensor.DeviceConnectionId != requiredConnectionId)
{
// client has set a required connection Id and this
// sensor does not have that Id
newStatus |= ChooserStatus.NoAvailableSensors;
continue;
}
if (sensor.Status != KinectStatus.Connected)
{
// Sensor is in some unusable state
newStatus |= GetErrorStatusFromSensor(sensor);
continue;
}
if (sensor.IsRunning)
{
// Sensor is already in use by this application
newStatus |= ChooserStatus.NoAvailableSensors;
continue;
}
// There is a potentially available sensor, try to start it
try
{
sensor.Start();
}
catch (IOException)
{
// some other app has this sensor.
newStatus |= ChooserStatus.SensorConflict;
continue;
}
catch (InvalidOperationException)
{
// TODO: In multi-proc scenarios, this is getting thrown at the start before we see IOException. Need to understand.
// some other app has this sensor.
newStatus |= ChooserStatus.SensorConflict;
continue;
}
// Woo hoo, we have a started sensor.
newStatus = ChooserStatus.SensorStarted;
newSensor = sensor;
break;
}
}
SetSensorAndStatus(callbackLock, newSensor, newStatus);
}
private void RaisePropertyChanged(string propertyName)
{
// We should never be in a lock at this point.
this.propertyChangedContextWrapper.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}