Skip to content

Instantly share code, notes, and snippets.

@lawrencegripper
Created May 29, 2015 13:32
Show Gist options
  • Save lawrencegripper/aca7b242c195f9ba7152 to your computer and use it in GitHub Desktop.
Save lawrencegripper/aca7b242c195f9ba7152 to your computer and use it in GitHub Desktop.
Detecting Taps or Shakes on Microsoft Band
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