Skip to content

Commit

Permalink
Merge pull request #433 from WildernessLabs/clima-updates
Browse files Browse the repository at this point in the history
Driver fixes and cleanup for Clima
  • Loading branch information
jorgedevs authored Oct 1, 2022
2 parents 32e4581 + eb82c25 commit dfc180c
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 222 deletions.
3 changes: 1 addition & 2 deletions Source/Meadow.Foundation.Core/SamplingSensorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,5 @@ public virtual void StopUpdating()
IsSampling = false;
}
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace Meadow.Foundation.Sensors.Atmospheric
{
/// <summary>
/// Represents the Bosch BME68x Temperature, Pressure and Humidity Sensor.
/// Represents the Bosch BME68x Temperature, Pressure and Humidity Sensor
/// </summary>
public abstract partial class Bme68x:
SamplingSensorBase<(Units.Temperature? Temperature,
Expand Down Expand Up @@ -198,7 +198,7 @@ public bool GasConversionIsEnabled
private static readonly double[] k1Lookup = { 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.8, 0.0, 0.0, -0.2, -0.5, 0.0, -1.0, 0.0, 0.0 };
private static readonly double[] k2Lookup = { 0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8, -0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

private readonly List<HeaterProfileConfiguration> heaterConfigs = new List<HeaterProfileConfiguration>();
private readonly List<HeaterProfileConfiguration> heaterConfigs = new();

/// <summary>
/// Creates a new instance of the BME68x class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,34 @@
using Meadow.Units;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using SU = Meadow.Units.Speed.UnitType;

namespace Meadow.Foundation.Sensors.Weather
{
// TODO: there still seems to be some issue with initial reading
// every once in a while, i'll get 0.0kmh, then i'll give it a spin and
// it'll give me something insane like:
// new speed: 65.9kmh, old: 0.0kmh
// new speed(from observer) : 65.9kmh, old: 0.0kmh
// then the next reading will be 3kmh.

/// <summary>
/// Driver for a "switching" anememoter (wind speed gauge) that has an
/// internal switch that is triggered during every revolution.
/// </summary>
public partial class SwitchingAnemometer :
ObservableBase<Speed>, IAnemometer
public partial class SwitchingAnemometer : SamplingSensorBase<Speed>, IAnemometer
{
//==== events
/// <summary>
/// Raised when the speed of the wind changes.
/// Raised when the speed of the wind changes
/// </summary>
public event EventHandler<IChangeResult<Speed>> WindSpeedUpdated = delegate { };

//==== internals
IDigitalInputPort inputPort;
bool running = false;

System.Timers.Timer? noWindTimer;
List<DigitalPortResult>? samples;

// Turn on for debug output
bool debug = true;

//==== properties
/// <summary>
/// The last recored wind speed.
/// The current wind speed
/// </summary>
public Speed? WindSpeed { get; protected set; }

/// <summary>
/// A `TimeSpan` that specifies how long to
/// wait between readings. This value influences how often `*Updated`
/// events are raised and `IObservable` consumers are notified.
/// </summary>
public TimeSpan UpdateInterval { get; set; } = TimeSpan.FromSeconds(5);

/// <summary>
/// Time to wait if no events come in to register a zero speed wind.
/// Time to wait if no events come in to register a zero speed wind
/// </summary>
public TimeSpan NoWindTimeout { get; set; } = TimeSpan.FromSeconds(4);

/// <summary>
/// Number of samples to take for a reading.
/// Number of samples to take for a reading
/// </summary>
public int SampleCount
{
Expand All @@ -68,7 +41,7 @@ public int SampleCount
sampleCount = value;
}
}
protected int sampleCount = 3;
int sampleCount = 3;

/// <summary>
/// Calibration for how fast the wind speed is when the switch is hit
Expand All @@ -77,6 +50,11 @@ public int SampleCount
/// </summary>
public float KmhPerSwitchPerSecond { get; set; } = 2.4f;

readonly IDigitalInputPort inputPort;
bool running = false;

readonly Queue<DigitalPortResult>? samples;

/// <summary>
/// Creates a new `SwitchingAnemometer` using the specific digital input
/// on the device.
Expand All @@ -86,9 +64,8 @@ public int SampleCount
public SwitchingAnemometer(IDigitalInputController device, IPin digitalInputPin)
: this(device.CreateDigitalInputPort(
digitalInputPin, InterruptMode.EdgeFalling,
ResistorMode.InternalPullUp, TimeSpan.FromMilliseconds(2), TimeSpan.FromMilliseconds(2)))
{
}
ResistorMode.InternalPullUp, TimeSpan.FromMilliseconds(2), TimeSpan.FromMilliseconds(0)))
{ }

/// <summary>
/// Creates a new switching anemometer using the specific `IDigitalInputPort`.
Expand All @@ -97,178 +74,103 @@ public SwitchingAnemometer(IDigitalInputController device, IPin digitalInputPin)
public SwitchingAnemometer(IDigitalInputPort inputPort)
{
this.inputPort = inputPort;
}

protected void SubscribeToinputPortEvents()
{
inputPort.Changed += HandleInputPortChange;
samples = new Queue<DigitalPortResult>();
}

protected void UnsubscribeToInputPortEvents()
{
inputPort.Changed -= HandleInputPortChange;
}
protected void SubscribeToInputPortEvents() => inputPort.Changed += HandleInputPortChange;

protected void UnsubscribeToInputPortEvents() => inputPort.Changed -= HandleInputPortChange;

protected void HandleInputPortChange(object sender, DigitalPortResult result)
{
if (!running) { return; }

if (samples == null) { samples = new List<DigitalPortResult>(); }

// reset our nowind timer, since a sample has come in. note that the API is awkward
noWindTimer?.Stop();
noWindTimer?.Start();

// we need at least two readings to get a valid speed, since the
// speed is based on duration between clicks, we need at least two
// clicks to measure duration.
samples.Add(result);
if (debug) { Console.WriteLine($"result #[{samples.Count}] new: [{result.New.State}], old: [{result.Old?.State}]"); }

// if we don't have two samples, move on
if (samples.Count < 1) { return; }
samples?.Enqueue(result);

// if we've reached our sample count
if (samples.Count >= SampleCount)
{
float speedSum = 0f;
float oversampledSpeed = 0f;

// sum up the speeds
foreach (var sample in samples)
{
// skip the first (old will be null)
if (sample.Old is { } old)
{
speedSum += SwitchIntervalToKmh(sample.New.Time - old.Time);
}
}
// average the speeds
oversampledSpeed = speedSum / samples.Count - 1;

// clear our samples
this.samples.Clear();

// capture history
Speed? oldSpeed = WindSpeed;
// save state
Speed newSpeed = new Speed(oversampledSpeed, SU.KilometersPerHour);
WindSpeed = newSpeed;
RaiseUpdated(new ChangeResult<Speed>(newSpeed, oldSpeed));

// if we need to wait before taking another sample set,
if (UpdateInterval > TimeSpan.Zero)
{
this.UnsubscribeToInputPortEvents();
Thread.Sleep(UpdateInterval);
this.SubscribeToinputPortEvents();
}
if(samples?.Count > sampleCount)
{
samples.Dequeue();
}
}

protected void RaiseUpdated(IChangeResult<Speed> changeResult)
protected override void RaiseEventsAndNotify(IChangeResult<Speed> changeResult)
{
WindSpeedUpdated?.Invoke(this, changeResult);
base.NotifyObservers(changeResult);
}

/// <summary>
/// A wind speed of 2.4km/h causes the switch to close once per second.
/// </summary>
/// <param name="interval"></param>
/// <returns></returns>
protected float SwitchIntervalToKmh(TimeSpan interval)
{
// A wind speed of 2.4km/h causes the switch to close once per second.
return this.KmhPerSwitchPerSecond / ((float)interval.TotalMilliseconds / 1000f);
}

/// <summary>
/// Starts continuously sampling the sensor.
/// Starts continuously sampling the sensor
///
/// This method also starts raising `Updated` events and IObservable
/// subscribers getting notified. Use the `standbyDuration` parameter
/// to specify how often events and notifications are raised/sent.
/// </summary>
/// <param name="sampleCount">How many samples to take during a given
/// reading. These are automatically averaged to reduce noise.</param>
/// <param name="standbyDuration">The time, in milliseconds, to wait
/// between listening for events. This value determines how often
/// `Changed` events are raised and `IObservable` consumers are notified.</param>
/// <param name="noWindTimeout">The time, in milliseconds in which to
/// wait for events from the anemometer. If no events come in by the time
/// this elapses, then an event of `0` wind will be raised.</param>
public void StartUpdating()
public override void StartUpdating(TimeSpan? updateInterval = null)
{
if (running) { return; }
if (!running)
{
running = true;

SampleCount = sampleCount;
SubscribeToInputPortEvents();
base.StartUpdating(updateInterval);
}
}

running = true;
/// <summary>
/// Stops the driver from raising wind speed events
/// </summary>
public override void StopUpdating()
{
base.StopUpdating();

if (running)
{
UnsubscribeToInputPortEvents();
}

running = false;
}

// start a timer that we can use to raise a zero wind event in the case
// that we're not getting input events (because there is no wind)
noWindTimer = new System.Timers.Timer(NoWindTimeout.Seconds * 1000);
//noWindTimer = new System.Timers.Timer(5000);
noWindTimer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
protected override Task<Speed> ReadSensor()
{
if(samples?.Count > 0 && (DateTime.Now - samples?.Peek().New.Time > NoWindTimeout))
{ //we've exceeded the no wind interval time
samples?.Clear(); //will force a zero reading
}
// if we've reached our sample count
else if (samples?.Count >= SampleCount)
{
if (debug) { Console.WriteLine("No wind timer elapsed."); }
// if not running, clear the timer and bail out.
if (!running)
{
noWindTimer.Stop();
return;
}
// if there aren't enough samples to make a reading
if (samples == null || samples.Count <= SampleCount)
{
// capture the old value
Speed? oldSpeed = WindSpeed;
// save state
Speed newSpeed = new Speed(0, SU.KilometersPerHour);
WindSpeed = newSpeed;
float speedSum = 0f;

// raise the wind updated event with `0` wind speed
RaiseUpdated(new ChangeResult<Speed>(newSpeed, oldSpeed));
// sleep for the standby duration
if (UpdateInterval > TimeSpan.Zero)
// sum up the speeds
foreach (var sample in samples)
{ // skip the first (old will be null)
if (sample.Old is { } old)
{
if (debug) { Console.WriteLine("Sleeping for a bit."); }
Thread.Sleep(UpdateInterval);
if (debug) { Console.WriteLine("Woke up."); }
speedSum += SwitchIntervalToKmh(sample.New.Time - old.Time);
}
}

if (debug) { Console.WriteLine($"timer enabled? {noWindTimer.Enabled}"); }
// average the speeds
float oversampledSpeed = speedSum / (samples.Count -1);

// if still running, start the timer again
if (running)
{
if (debug) { Console.WriteLine("restarting timer."); }
noWindTimer.Start();
}
}
};
noWindTimer.Start();
return Task.FromResult(new Speed(oversampledSpeed, SU.KilometersPerHour));
}

SubscribeToinputPortEvents();
//otherwise return 0 speed
return Task.FromResult(new Speed(0));
}

/// <summary>
/// Stops the driver from raising wind speed events.
/// A wind speed of 2.4km/h causes the switch to close once per second
/// </summary>
public void StopUpdating()
/// <param name="interval">The interval between signals</param>
/// <returns></returns>
protected float SwitchIntervalToKmh(TimeSpan interval)
{
if (running)
{
UnsubscribeToInputPortEvents();
}
if (noWindTimer != null && noWindTimer.Enabled)
{
noWindTimer.Stop();
}

// state machine
running = false;
return KmhPerSwitchPerSecond / (float)interval.TotalSeconds;
}
}
}
Loading

0 comments on commit dfc180c

Please sign in to comment.