Created
May 29, 2015 13:32
-
-
Save lawrencegripper/aca7b242c195f9ba7152 to your computer and use it in GitHub Desktop.
Detecting Taps or Shakes on Microsoft Band
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
using Microsoft.Band; | |
using Microsoft.Band.Sensors; | |
using Microsoft.Band.Tiles; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reactive.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Windows.UI.Popups; | |
namespace TestBand | |
{ | |
class BandAccessWrapper | |
{ | |
public IBandClient Client; | |
private string TileName; | |
public BandAccessWrapper(string appName) | |
{ | |
TileName = appName; | |
} | |
public async Task Connect() | |
{ | |
try | |
{ | |
// This method will throw an exception upon failure for a veriety of reasons, | |
// such as Band out of range or turned off. | |
var bands = await BandClientManager.Instance.GetBandsAsync(); | |
Client = await BandClientManager.Instance.ConnectAsync(bands[0]); | |
} | |
catch (Exception ex) | |
{ | |
var t = new MessageDialog(ex.Message, "Failed to Connect").ShowAsync(); | |
} | |
} | |
public async Task<BandTile> AddTile(BandTile tile, bool overwrite) | |
{ | |
IEnumerable<BandTile> tiles = await GetTiles(); | |
if (tiles.Any(x => x.Name == tile.Name)) | |
{ | |
var currentTile = tiles.Where(x => x.Name == tile.Name).Single(); | |
if (!overwrite) | |
{ | |
return currentTile; | |
} | |
await Client.TileManager.RemoveTileAsync(currentTile.TileId); | |
} | |
await Client.TileManager.AddTileAsync(tile); | |
tiles = await GetTiles(); | |
tile = tiles.Where(x => x.Name == TileName).Single(); | |
return tile; | |
} | |
public async Task<IObservable<BandSensorReadingEventArgs<IBandHeartRateReading>>> GetHeartRateStream() | |
{ | |
return await GetSensorStream<IBandHeartRateReading>(Client.SensorManager.HeartRate); | |
} | |
public async Task<IObservable<BandSensorReadingEventArgs<IBandAccelerometerReading>>> GetAccelerometerStream() | |
{ | |
return await GetSensorStream<IBandAccelerometerReading>(Client.SensorManager.Accelerometer); | |
} | |
public async Task<IObservable<TapEvent>> GetShakeOrTapStream(double threshold = 4, int timeSampleMs = 300) | |
{ | |
var accStream = await GetAccelerometerStream(); | |
return accStream | |
//Filter out any empty readings, band seems to report every other reading as all 0's | |
.Where(x => x.SensorReading.AccelerationX != 0) | |
//Get our readings, don't need the event args | |
.Select(x => x.SensorReading ) | |
//Scan over the readings creating an aggregate reading for motion on all axis. | |
//Output all axis and the aggregate motion | |
.Scan<IBandAccelerometerReading, ChangeInMotion>(null, (last, current) => | |
{ | |
//If we're the first the change is 0 | |
if (last == null) | |
{ | |
return new ChangeInMotion(0, current); | |
} | |
//Get difference in motion on all axis vs last reading as positive # then sum to get aggregate change in motion. | |
var aggregateChangeInMotion = (last.Reading.AccelerationX - current.AccelerationX) * -1 + (last.Reading.AccelerationY - current.AccelerationY) * -1 + (last.Reading.AccelerationZ - current.AccelerationZ) * -1; | |
return new ChangeInMotion(aggregateChangeInMotion, current); | |
}) | |
//Collect a set of results over a timespan, around 400ms worked for me to detect taps and shakes | |
.Buffer(new TimeSpan(0, 0, 0, 0, timeSampleMs)) | |
//Caculate the variance of the aggregate motion reading | |
.Select(x => | |
{ | |
var listOfAggregateMotion = x.Select(y => y.ChangeVsLastReading); | |
return new TapEvent(Variance(listOfAggregateMotion), x.Select(y=>y.Reading)); | |
}) | |
.Where(x => x.Variance > threshold); | |
} | |
private double Variance(IEnumerable<double> nums) | |
{ | |
if (nums.Count() > 1) | |
{ | |
// Get the average of the values | |
double avg = nums.Average(); | |
// Now figure out how far each point is from the mean | |
// So we subtract from the number the average | |
// Then raise it to the power of 2 | |
double sumOfSquares = 0.0; | |
foreach (int num in nums) | |
{ | |
sumOfSquares += Math.Pow((num - avg), 2.0); | |
} | |
// Finally divide it by n - 1 (for standard deviation variance) | |
// Or use length without subtracting one ( for population standard deviation variance) | |
return sumOfSquares / (double)(nums.Count() - 1); | |
} | |
else { return 0.0; } | |
} | |
public async Task<IObservable<BandSensorReadingEventArgs<T>>> GetSensorStream<T>(IBandSensor<T> manager) where T : IBandSensorReading | |
{ | |
var consent = manager.GetCurrentUserConsent(); | |
if (consent != UserConsent.Granted) | |
{ | |
await manager.RequestUserConsentAsync(); | |
} | |
var supportedIntervals = manager.SupportedReportingIntervals; | |
manager.ReportingInterval = supportedIntervals.First(); | |
var stream = CreateObservableFromSensorEvent<T>(manager); | |
return stream; | |
} | |
private IObservable<BandSensorReadingEventArgs<T>> CreateObservableFromSensorEvent<T>(IBandSensor<T> manager) where T : IBandSensorReading | |
{ | |
var obs = Observable.FromEvent< | |
EventHandler<BandSensorReadingEventArgs<T>>, | |
BandSensorReadingEventArgs<T>> | |
( | |
handler => | |
{ | |
EventHandler<BandSensorReadingEventArgs<T>> kpeHandler = (sender, e) => handler(e); | |
return kpeHandler; | |
}, | |
async x => | |
{ | |
manager.ReadingChanged += x; | |
await manager.StartReadingsAsync(); | |
}, | |
async x => | |
{ | |
manager.ReadingChanged -= x; | |
await manager.StopReadingsAsync(); | |
} | |
); | |
return obs; | |
} | |
private async Task<IEnumerable<BandTile>> GetTiles() | |
{ | |
IEnumerable<BandTile> tiles = await Client.TileManager.GetTilesAsync(); | |
return tiles; | |
} | |
public class ChangeInMotion | |
{ | |
public ChangeInMotion(double ChangeVsLastReading, IBandAccelerometerReading Reading) | |
{ | |
this.ChangeVsLastReading = ChangeVsLastReading; | |
this.Reading = Reading; | |
} | |
public double ChangeVsLastReading { get; set; } | |
public IBandAccelerometerReading Reading { get; set; } | |
} | |
public class TapEvent | |
{ | |
public TapEvent(double Variance, IEnumerable<IBandAccelerometerReading> Readings) | |
{ | |
this.Variance = Variance; | |
this.Readings = Readings; | |
} | |
public double Variance { get; set; } | |
public IEnumerable<IBandAccelerometerReading> Readings { get; set; } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment