Skip to content

Commit

Permalink
Merge pull request #16 from MatthewGerber/speed-probe
Browse files Browse the repository at this point in the history
Refine speed probe to not use location probe (use GPS receiver instead)
  • Loading branch information
MatthewGerber committed Feb 17, 2015
2 parents 3b1a40f + 225809b commit 0b58cfd
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 97 deletions.
2 changes: 1 addition & 1 deletion Sensus.Android/AndroidSensusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public override StartCommandResult OnStartCommand(Intent intent, StartCommandFla
{
bool repeating = intent.GetBooleanExtra(AndroidSensusServiceHelper.INTENT_EXTRA_SENSUS_CALLBACK_REPEATING, false);
_wakeLock.Acquire();
_sensusServiceHelper.RaiseCallbackAsync(callbackId, repeating, () => _wakeLock.Release());
_sensusServiceHelper.RaiseCallbackAsync(callbackId, repeating, _wakeLock.Release);
}
}

Expand Down
2 changes: 1 addition & 1 deletion SensusService/Probes/Context/LightDatum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public double Brightness
[JsonIgnore]
public override string DisplayDetail
{
get { return "Brightness: " + _brightness; }
get { return "Brightness: " + Math.Round(_brightness, 2); }
}

public LightDatum(Probe probe, DateTimeOffset timestamp, double brightness)
Expand Down
2 changes: 1 addition & 1 deletion SensusService/Probes/Device/BatteryDatum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public double Level
[JsonIgnore]
public override string DisplayDetail
{
get { return "Level: " + _level; }
get { return "Level: " + Math.Round(_level, 2); }
}

public BatteryDatum(Probe probe, DateTimeOffset timestamp, double level)
Expand Down
6 changes: 1 addition & 5 deletions SensusService/Probes/ListeningProbe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,7 @@ public sealed override void Stop()

protected sealed override void StoreDatum(Datum datum)
{
DateTimeOffset lastStoreTime = DateTimeOffset.MinValue;
if (MostRecentDatum != null)
lastStoreTime = MostRecentDatum.Timestamp;

float storesPerSecond = 1 / (float)(DateTimeOffset.UtcNow - lastStoreTime).TotalSeconds;
float storesPerSecond = 1 / (float)(DateTimeOffset.UtcNow - MostRecentStoreTimestamp).TotalSeconds;
if (storesPerSecond <= _maxDataStoresPerSecond)
base.StoreDatum(datum);
}
Expand Down
106 changes: 55 additions & 51 deletions SensusService/Probes/Location/GpsReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ public static GpsReceiver Get()

private Geolocator _locator;
private int _desiredAccuracyMeters;
private bool _sharedReadingIsComing;
private ManualResetEvent _sharedReadingWait;
private Position _sharedReading;
private DateTimeOffset _sharedReadingTimestamp;
private bool _readingIsComing;
private ManualResetEvent _readingWait;
private Position _reading;
private DateTimeOffset _readingTimestamp;
private int _readingTimeoutMS;
private int _minimumTimeHint;
private int _minimumDistanceHint;

Expand Down Expand Up @@ -107,10 +108,11 @@ public bool ListeningForChanges
private GpsReceiver()
{
_desiredAccuracyMeters = 10;
_sharedReadingIsComing = false;
_sharedReadingWait = new ManualResetEvent(false);
_sharedReading = null;
_sharedReadingTimestamp = DateTimeOffset.MinValue;
_readingIsComing = false;
_readingWait = new ManualResetEvent(false);
_reading = null;
_readingTimestamp = DateTimeOffset.MinValue;
_readingTimeoutMS = 120000;
_minimumTimeHint = 60000;
_minimumDistanceHint = 100;
}
Expand Down Expand Up @@ -166,7 +168,6 @@ public void ClearListeners()
public void Initialize(Geolocator locator)
{
_locator = locator;

_locator.DesiredAccuracy = _desiredAccuracyMeters;

_locator.PositionChanged += (o, e) =>
Expand All @@ -183,57 +184,60 @@ public void Initialize(Geolocator locator)
};
}

public Position GetReading(int maxSharedReadingAgeForReuseMS, int timeout)
public Position GetReading()
{
return GetReading(0);
}

public Position GetReading(int maxReadingAgeForReuseMS)
{
// reuse a previous reading if it isn't too old
TimeSpan sharedReadingAge = DateTimeOffset.UtcNow - _sharedReadingTimestamp;
if (sharedReadingAge.TotalMilliseconds < maxSharedReadingAgeForReuseMS)
TimeSpan readingAge = DateTimeOffset.UtcNow - _readingTimestamp;
if (readingAge.TotalMilliseconds <= maxReadingAgeForReuseMS)
{
SensusServiceHelper.Get().Logger.Log("Reusing previous GPS reading, which is " + sharedReadingAge.TotalMilliseconds + " MS old (maximum=" + maxSharedReadingAgeForReuseMS + ").", LoggingLevel.Verbose);

return _sharedReading;
SensusServiceHelper.Get().Logger.Log("Reusing previous GPS reading, which is " + readingAge.TotalMilliseconds + " MS old (maximum=" + maxReadingAgeForReuseMS + ").", LoggingLevel.Verbose);
return _reading;
}

if (!_sharedReadingIsComing) // is someone else currently taking a reading? if so, wait for that instead.
{
_sharedReadingIsComing = true; // tell any subsequent, concurrent callers that we're taking a reading
_sharedReadingWait.Reset(); // make them wait
new Thread(async () =>
{
try
{
SensusServiceHelper.Get().Logger.Log("Taking shared reading.", LoggingLevel.Debug);

DateTimeOffset start = DateTimeOffset.UtcNow;
_sharedReading = await _locator.GetPositionAsync(timeout: timeout);
DateTimeOffset end = _sharedReadingTimestamp = DateTimeOffset.UtcNow;
lock (_locker)
if (_readingIsComing) // is someone else currently taking a reading? if so, wait for that instead.
SensusServiceHelper.Get().Logger.Log("A GPS reading is coming. Will wait for it.", LoggingLevel.Debug);
else
{
_readingIsComing = true; // tell any subsequent, concurrent callers that we're taking a reading
_readingWait.Reset(); // make them wait

if (_sharedReading != null)
SensusServiceHelper.Get().Logger.Log("Shared reading obtained in " + (end - start).TotalSeconds + " seconds: " + _sharedReading.Latitude + " " + _sharedReading.Longitude, LoggingLevel.Verbose);
}
catch (TaskCanceledException ex)
new Thread(async () =>
{
SensusServiceHelper.Get().Logger.Log("GPS reading task canceled: " + ex.Message, LoggingLevel.Normal);

_sharedReading = null;
}

_sharedReadingIsComing = false; // direct any future calls to this method to get their own reading
_sharedReadingWait.Set(); // tell anyone waiting on the shared reading that it is ready

}).Start();
}
else
SensusServiceHelper.Get().Logger.Log("A shared reading is coming. Will wait for it.", LoggingLevel.Debug);

_sharedReadingWait.WaitOne(timeout * 2); // wait twice the locator timeout, just to be sure.
try
{
SensusServiceHelper.Get().Logger.Log("Taking GPS reading.", LoggingLevel.Debug);

DateTimeOffset readingStart = DateTimeOffset.UtcNow;
_reading = await _locator.GetPositionAsync(timeout: _readingTimeoutMS);
DateTimeOffset readingEnd = _readingTimestamp = DateTimeOffset.UtcNow;

if (_reading != null)
SensusServiceHelper.Get().Logger.Log("GPS reading obtained in " + (readingEnd - readingStart).TotalSeconds + " seconds: " + _reading.Latitude + " " + _reading.Longitude, LoggingLevel.Verbose);
}
catch (Exception ex)
{
SensusServiceHelper.Get().Logger.Log("GPS reading failed: " + ex.Message, LoggingLevel.Normal);
_reading = null;
}

_readingWait.Set(); // tell anyone waiting on the shared reading that it is ready
_readingIsComing = false; // direct any future calls to this method to get their own reading

}).Start();
}

Position reading = _sharedReading;
_readingWait.WaitOne(_readingTimeoutMS * 2); // wait twice the locator timeout, just to be sure.

if (reading == null)
SensusServiceHelper.Get().Logger.Log("Shared reading is null.", LoggingLevel.Normal);
if (_reading == null)
SensusServiceHelper.Get().Logger.Log("GPS reading is null.", LoggingLevel.Normal);

return reading;
return _reading;
}
}
}
}
2 changes: 1 addition & 1 deletion SensusService/Probes/Location/LocationDatum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public double Longitude
[JsonIgnore]
public override string DisplayDetail
{
get { return _latitude + " (lat), " + _longitude + " (lon)"; }
get { return Math.Round(_latitude, 2) + " (lat), " + Math.Round(_longitude, 2) + " (lon)"; }
}

public LocationDatum(Probe probe, DateTimeOffset timestamp, double accuracy, double latitude, double longitude)
Expand Down
2 changes: 1 addition & 1 deletion SensusService/Probes/Location/PollingLocationProbe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected override void Initialize()

protected sealed override IEnumerable<Datum> Poll()
{
Position reading = GpsReceiver.Get().GetReading(PollingSleepDurationMS, 60000);
Position reading = GpsReceiver.Get().GetReading(PollingSleepDurationMS);

if (reading == null)
return new Datum[] { };
Expand Down
57 changes: 29 additions & 28 deletions SensusService/Probes/Movement/PollingSpeedProbe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
using SensusService.Probes.Location;
using System;
using System.Collections.Generic;
using Xamarin.Geolocation;

namespace SensusService.Probes.Movement
{
public class PollingSpeedProbe : PollingProbe
{
private PollingLocationProbe _locationProbe;
private LocationDatum _previousLocation;
private Position _previousPosition;

private readonly object _locker = new object();

Expand All @@ -30,12 +30,6 @@ protected sealed override string DefaultDisplayName
get { return "Speed (Polling)"; }
}

public sealed override int PollingSleepDurationMS
{
get { return base.PollingSleepDurationMS; }
set { base.PollingSleepDurationMS = _locationProbe.PollingSleepDurationMS = value; }
}

public sealed override Type DatumType
{
get { return typeof(SpeedDatum); }
Expand All @@ -48,18 +42,26 @@ public sealed override int DefaultPollingSleepDurationMS

public PollingSpeedProbe()
{
_locationProbe = new PollingLocationProbe();
_locationProbe.StoreData = false;
_locationProbe.PollingSleepDurationMS = DefaultPollingSleepDurationMS;
}

protected override void Initialize()
{
base.Initialize();

if (!GpsReceiver.Get().Locator.IsGeolocationEnabled)
{
string error = "Geolocation is not enabled on this device. Cannot start speed probe.";
SensusServiceHelper.Get().FlashNotificationAsync(error);
throw new Exception(error);
}
}

public override void Start()
{
lock (_locker)
{
_previousLocation = null; // do this before starting the base-class poller so it doesn't race to grab a stale previous location.
_previousPosition = null; // do this before starting the base-class poller so it doesn't race to grab a stale previous location.
base.Start();
_locationProbe.Start();
}
}

Expand All @@ -77,20 +79,20 @@ protected override IEnumerable<Datum> Poll()
{
lock (_locker)
{
LocationDatum currentLocation = _locationProbe.MostRecentDatum as LocationDatum;
Position currentPosition = GpsReceiver.Get().GetReading();

Datum[] data = null;

if (_previousLocation == null || currentLocation == null || currentLocation.Timestamp == _previousLocation.Timestamp)
if (_previousPosition == null || currentPosition == null || currentPosition.Timestamp == _previousPosition.Timestamp)
data = new Datum[] { };
else
{
// http://www.movable-type.co.uk/scripts/latlong.html

double φ1 = DegreesToRadians(_previousLocation.Latitude);
double φ2 = DegreesToRadians(currentLocation.Latitude);
double Δφ = DegreesToRadians(currentLocation.Latitude - _previousLocation.Latitude);
double Δλ = DegreesToRadians(currentLocation.Longitude - _previousLocation.Longitude);
double φ1 = DegreesToRadians(_previousPosition.Latitude);
double φ2 = DegreesToRadians(currentPosition.Latitude);
double Δφ = DegreesToRadians(currentPosition.Latitude - _previousPosition.Latitude);
double Δλ = DegreesToRadians(currentPosition.Longitude - _previousPosition.Longitude);

double a = Math.Pow(Math.Sin(Δφ / 2), 2) +
Math.Cos(φ1) *
Expand All @@ -101,22 +103,22 @@ protected override IEnumerable<Datum> Poll()

var reportedDistKM = 6371 * c;

double elapsedHours = new TimeSpan(currentLocation.Timestamp.Ticks - _previousLocation.Timestamp.Ticks).TotalHours;
double elapsedHours = new TimeSpan(currentPosition.Timestamp.Ticks - _previousPosition.Timestamp.Ticks).TotalHours;
double reportedSpeedKPH = reportedDistKM / elapsedHours;

float accuracy = 0;
if (_previousLocation.Accuracy >= 0 && currentLocation.Accuracy >= 0)
if (_previousPosition.Accuracy >= 0 && currentPosition.Accuracy >= 0)
{
double maxDistKM = reportedDistKM + _previousLocation.Accuracy / 1000f + currentLocation.Accuracy / 1000f;
double maxDistKM = reportedDistKM + _previousPosition.Accuracy / 1000f + currentPosition.Accuracy / 1000f;
double maxSpeedKPH = maxDistKM / elapsedHours;
accuracy = (float)(maxSpeedKPH - reportedSpeedKPH);
}

data = new SpeedDatum[] { new SpeedDatum(this, currentLocation.Timestamp, accuracy, (float)reportedSpeedKPH) };
data = new SpeedDatum[] { new SpeedDatum(this, currentPosition.Timestamp, accuracy, (float)reportedSpeedKPH) };
}

if (currentLocation != null)
_previousLocation = currentLocation;
if (currentPosition != null)
_previousPosition = currentPosition;

return data;
}
Expand All @@ -127,9 +129,8 @@ public override void Stop()
lock (_locker)
{
base.Stop();
_locationProbe.Stop();
_previousLocation = null; // reset previous location so it doesn't get used when this probe is restarted.
_previousPosition = null; // reset previous location so it doesn't get used when this probe is restarted.
}
}
}
}
}
10 changes: 3 additions & 7 deletions SensusService/Probes/PollingProbe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,9 @@ public override bool TestHealth(ref string error, ref string warning, ref string

if (Running)
{
DateTimeOffset mostRecentDatumTimestamp = DateTimeOffset.MinValue;
if (MostRecentDatum != null)
mostRecentDatumTimestamp = MostRecentDatum.Timestamp;

double msElapsedSinceLastDatum = (DateTimeOffset.UtcNow - mostRecentDatumTimestamp).TotalMilliseconds;
if (!_isPolling && msElapsedSinceLastDatum > _pollingSleepDurationMS)
warning += "Probe \"" + GetType().FullName + "\" has not taken a reading in " + msElapsedSinceLastDatum + "ms (polling delay = " + _pollingSleepDurationMS + "ms)." + Environment.NewLine;
double msElapsedSincePreviousStore = (DateTimeOffset.UtcNow - MostRecentStoreTimestamp).TotalMilliseconds;
if (!_isPolling && msElapsedSincePreviousStore > _pollingSleepDurationMS)
warning += "Probe \"" + GetType().FullName + "\" has not stored data in " + msElapsedSincePreviousStore + "ms (polling delay = " + _pollingSleepDurationMS + "ms)." + Environment.NewLine;
}

return restart;
Expand Down
Loading

0 comments on commit 0b58cfd

Please sign in to comment.