Skip to content

Commit

Permalink
Merge pull request #868 from WildernessLabs/feature/simulation
Browse files Browse the repository at this point in the history
Feature/simulation
  • Loading branch information
adrianstevens authored Dec 30, 2023
2 parents 41c9331 + a9daa55 commit 95dfe06
Show file tree
Hide file tree
Showing 10 changed files with 689 additions and 238 deletions.
98 changes: 98 additions & 0 deletions Source/Meadow.Foundation.Core/Simulation/SimulatedRelay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using Meadow.Peripherals.Relays;
using Meadow.Peripherals.Sensors;
using System;
using System.Threading;

namespace Meadow.Foundation.Relays;

/// <summary>
/// Represents a simulated relay that implements both IRelay and ISimulatedSensor interfaces.
/// </summary>
public class SimulatedRelay : IRelay, ISimulatedSensor
{
private RelayState _state;
private Timer? _simulationTimer;

/// <inheritdoc/>
public event EventHandler<RelayState> OnChanged = default!;

/// <inheritdoc/>
public RelayType Type => RelayType.NormallyOpen;

/// <summary>
/// Gets the name of the Relay
/// </summary>
public string Name { get; }

/// <summary>
/// Initializes a new instance of the SimulatedRelay class with a specified name.
/// </summary>
/// <param name="name">The name of the simulated relay.</param>
public SimulatedRelay(string name)
{
Name = name;
}

/// <inheritdoc/>
public RelayState State
{
get => _state;
set
{
if (value == _state) return;
_state = value;
OnChanged?.Invoke(this, State);
}
}

/// <inheritdoc/>
public SimulationBehavior[] SupportedBehaviors => new SimulationBehavior[] { SimulationBehavior.Sawtooth };
/// <inheritdoc/>
public Type ValueType => typeof(bool);

/// <inheritdoc/>
public void Toggle()
{
State = State switch
{
RelayState.Open => RelayState.Closed,
_ => RelayState.Open,
};
}

/// <inheritdoc/>
public void SetSensorValue(object value)
{
if (value is bool b)
{
State = b switch
{
true => RelayState.Closed,
_ => RelayState.Open
};
}
else if (value is RelayState s)
{
State = s;
}
else
{
throw new ArgumentException($"Expected a parameter of type '{ValueType.Name}' but received a '{value.GetType().Name}'");
}
}

/// <inheritdoc/>
public void StartSimulation(SimulationBehavior behavior)
{
if (_simulationTimer == null)
{
_simulationTimer = new Timer((o) =>
{
Toggle();
},
null,
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(5));
}
}
}
158 changes: 158 additions & 0 deletions Source/Meadow.Foundation.Core/Simulation/SimulatedTemperatureSensor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using Meadow.Hardware;
using Meadow.Peripherals.Sensors;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Meadow.Foundation.Sensors;

/// <summary>
/// Represents a simulated temperature sensor that implements both ITemperatureSensor and ISimulatedSensor interfaces.
/// </summary>
public class SimulatedTemperatureSensor : ITemperatureSensor, ISimulatedSensor
{
private readonly Random _random = new();
private Units.Temperature? _temperature;
private Units.Temperature? _minTemperature;
private Units.Temperature? _maxTemperature;
private SimulationBehavior _behavior;
private int _sawtoothDirection = 1;
private Timer? _reportTimer;
private Timer? _simulationTimer;

/// <inheritdoc/>
public event EventHandler<IChangeResult<Units.Temperature>> Updated = default!;
/// <inheritdoc/>
public SimulationBehavior[] SupportedBehaviors => new SimulationBehavior[] { SimulationBehavior.RandomWalk, SimulationBehavior.Sawtooth };
/// <inheritdoc/>
public Type ValueType => typeof(Units.Temperature);
/// <inheritdoc/>
public TimeSpan UpdateInterval { get; private set; }
/// <inheritdoc/>
public bool IsSampling { get; private set; }

/// <summary>
/// Initializes a new instance of the TemperatureSensorSimulated class.
/// </summary>
/// <param name="initialTemperature">The initial temperature value of the sensor.</param>
/// <param name="incrementPort">The digital interrupt port used for incrementing the temperature.</param>
/// <param name="decrementPort">The digital interrupt port used for decrementing the temperature.</param>
public SimulatedTemperatureSensor(
Units.Temperature initialTemperature,
IDigitalInterruptPort incrementPort,
IDigitalInterruptPort decrementPort)
{
_temperature = initialTemperature;

incrementPort.Changed += (s, e) =>
{
Temperature = new Units.Temperature(Temperature!.Value.Fahrenheit + 0.5, Meadow.Units.Temperature.UnitType.Fahrenheit);
};
decrementPort.Changed += (s, e) =>
{
Temperature = new Units.Temperature(Temperature!.Value.Fahrenheit - 0.5, Meadow.Units.Temperature.UnitType.Fahrenheit);
};
}

/// <summary>
/// Initializes a new instance of the TemperatureSensorSimulated class with specified parameters.
/// </summary>
/// <param name="initialTemperature">The initial temperature value of the sensor.</param>
/// <param name="minimumTemperature">The minimum temperature value for the simulation.</param>
/// <param name="maximumTemperature">The maximum temperature value for the simulation.</param>
/// <param name="behavior">The simulation behavior for the sensor (default is SimulationBehavior.RandomWalk).</param>
public SimulatedTemperatureSensor(
Units.Temperature initialTemperature,
Units.Temperature minimumTemperature,
Units.Temperature maximumTemperature,
SimulationBehavior behavior = SimulationBehavior.RandomWalk)
{
_temperature = initialTemperature;
_minTemperature = minimumTemperature;
_maxTemperature = maximumTemperature;

StartSimulation(behavior);
}

private void SimulationProc(object? o)
{
var delta = _behavior switch
{
SimulationBehavior.RandomWalk => _random.Next(-10, 10) / 10d,
_ => 0.1 * _sawtoothDirection
};

if (_temperature == null) return;

var newTemp = _temperature.Value.Celsius + delta;
if ((newTemp < _minTemperature!.Value.Celsius) ||
(newTemp > _maxTemperature!.Value.Celsius))
{
newTemp = _temperature.Value.Celsius - delta;
_sawtoothDirection *= -1;
}

Temperature = new Units.Temperature(newTemp, Meadow.Units.Temperature.UnitType.Celsius);
}

private void ReportTimerProc(object? o)
{
Updated?.Invoke(this, new ChangeResult<Units.Temperature>(this.Temperature!.Value, this.Temperature!.Value));
}

/// <inheritdoc/>
public Units.Temperature? Temperature
{
get => _temperature;
private set
{
if (value == Temperature) return;

if (value != null)
{
var previous = _temperature;
_temperature = value;
Updated?.Invoke(this, new ChangeResult<Units.Temperature>(Temperature!.Value, previous));
}
}
}

/// <inheritdoc/>
public Task<Units.Temperature> Read()
{
return Task.FromResult(Temperature ?? Units.Temperature.AbsoluteZero);
}

public void StartUpdating(TimeSpan? updateInterval = null)

Check warning on line 126 in Source/Meadow.Foundation.Core/Simulation/SimulatedTemperatureSensor.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SimulatedTemperatureSensor.StartUpdating(TimeSpan?)'
{
UpdateInterval = updateInterval ?? TimeSpan.FromSeconds(1);
IsSampling = true;
_reportTimer = new Timer(ReportTimerProc, null, updateInterval!.Value, updateInterval.Value);
}

public void StopUpdating()

Check warning on line 133 in Source/Meadow.Foundation.Core/Simulation/SimulatedTemperatureSensor.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SimulatedTemperatureSensor.StopUpdating()'
{
IsSampling = false;
_reportTimer?.Dispose();
}

/// <inheritdoc/>
public void SetSensorValue(object value)
{
if (value is Units.Temperature temperature)
{
Temperature = temperature;
}
else
{
throw new ArgumentException($"Expected a parameter of type '{ValueType.Name}' but received a '{value.GetType().Name}'");
}
}

/// <inheritdoc/>
public void StartSimulation(SimulationBehavior behavior)
{
_behavior = behavior;
_simulationTimer = new Timer(SimulationProc, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,139 @@ namespace Meadow.Foundation.Sensors.Hid;

public partial class Keyboard
{
internal class Interop
internal class InteropMac
{
public enum CGEventSourceStateID : int
{
hidSystemState = 1
}

// CGEventFlags CGEventSourceFlagsState(CGEventSourceStateID stateID);
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
public static extern long CGEventSourceFlagsState(CGEventSourceStateID stateID);

// bool CGEventSourceKeyState(CGEventSourceStateID stateID, CGKeyCode key);
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
public static extern int CGEventSourceKeyState(CGEventSourceStateID stateID, MacKeyCodes keyCode);

public enum MacKeyCodes : ushort
{
kVK_ANSI_A = 0x00,
kVK_ANSI_S = 0x01,
kVK_ANSI_D = 0x02,
kVK_ANSI_F = 0x03,
kVK_ANSI_H = 0x04,
kVK_ANSI_G = 0x05,
kVK_ANSI_Z = 0x06,
kVK_ANSI_X = 0x07,
kVK_ANSI_C = 0x08,
kVK_ANSI_V = 0x09,
kVK_ANSI_B = 0x0B,
kVK_ANSI_Q = 0x0C,
kVK_ANSI_W = 0x0D,
kVK_ANSI_E = 0x0E,
kVK_ANSI_R = 0x0F,
kVK_ANSI_Y = 0x10,
kVK_ANSI_T = 0x11,
kVK_ANSI_1 = 0x12,
kVK_ANSI_2 = 0x13,
kVK_ANSI_3 = 0x14,
kVK_ANSI_4 = 0x15,
kVK_ANSI_6 = 0x16,
kVK_ANSI_5 = 0x17,
kVK_ANSI_Equal = 0x18,
kVK_ANSI_9 = 0x19,
kVK_ANSI_7 = 0x1A,
kVK_ANSI_Minus = 0x1B,
kVK_ANSI_8 = 0x1C,
kVK_ANSI_0 = 0x1D,
kVK_ANSI_RightBracket = 0x1E,
kVK_ANSI_O = 0x1F,
kVK_ANSI_U = 0x20,
kVK_ANSI_LeftBracket = 0x21,
kVK_ANSI_I = 0x22,
kVK_ANSI_P = 0x23,
kVK_ANSI_L = 0x25,
kVK_ANSI_J = 0x26,
kVK_ANSI_Quote = 0x27,
kVK_ANSI_K = 0x28,
kVK_ANSI_Semicolon = 0x29,
kVK_ANSI_Backslash = 0x2A,
kVK_ANSI_Comma = 0x2B,
kVK_ANSI_Slash = 0x2C,
kVK_ANSI_N = 0x2D,
kVK_ANSI_M = 0x2E,
kVK_ANSI_Period = 0x2F,
kVK_ANSI_Grave = 0x32,
kVK_ANSI_KeypadDecimal = 0x41,
kVK_ANSI_KeypadMultiply = 0x43,
kVK_ANSI_KeypadPlus = 0x45,
kVK_ANSI_KeypadClear = 0x47,
kVK_ANSI_KeypadDivide = 0x4B,
kVK_ANSI_KeypadEnter = 0x4C,
kVK_ANSI_KeypadMinus = 0x4E,
kVK_ANSI_KeypadEquals = 0x51,
kVK_ANSI_Keypad0 = 0x52,
kVK_ANSI_Keypad1 = 0x53,
kVK_ANSI_Keypad2 = 0x54,
kVK_ANSI_Keypad3 = 0x55,
kVK_ANSI_Keypad4 = 0x56,
kVK_ANSI_Keypad5 = 0x57,
kVK_ANSI_Keypad6 = 0x58,
kVK_ANSI_Keypad7 = 0x59,
kVK_ANSI_Keypad8 = 0x5B,
kVK_ANSI_Keypad9 = 0x5C,
kVK_Return = 0x24,
kVK_Tab = 0x30,
kVK_Space = 0x31,
kVK_Delete = 0x33,
kVK_Escape = 0x35,
kVK_Command = 0x37,
kVK_Shift = 0x38,
kVK_CapsLock = 0x39,
kVK_Option = 0x3A,
kVK_Control = 0x3B,
kVK_RightShift = 0x3C,
kVK_RightOption = 0x3D,
kVK_RightControl = 0x3E,
kVK_Function = 0x3F,
kVK_F17 = 0x40,
kVK_VolumeUp = 0x48,
kVK_VolumeDown = 0x49,
kVK_Mute = 0x4A,
kVK_F18 = 0x4F,
kVK_F19 = 0x50,
kVK_F20 = 0x5A,
kVK_F5 = 0x60,
kVK_F6 = 0x61,
kVK_F7 = 0x62,
kVK_F3 = 0x63,
kVK_F8 = 0x64,
kVK_F9 = 0x65,
kVK_F11 = 0x67,
kVK_F13 = 0x69,
kVK_F16 = 0x6A,
kVK_F14 = 0x6B,
kVK_F10 = 0x6D,
kVK_F12 = 0x6F,
kVK_F15 = 0x71,
kVK_Help = 0x72,
kVK_Home = 0x73,
kVK_PageUp = 0x74,
kVK_ForwardDelete = 0x75,
kVK_F4 = 0x76,
kVK_End = 0x77,
kVK_F2 = 0x78,
kVK_PageDown = 0x79,
kVK_F1 = 0x7A,
kVK_LeftArrow = 0x7B,
kVK_RightArrow = 0x7C,
kVK_DownArrow = 0x7D,
kVK_UpArrow = 0x7E
}
}

internal class InteropWindows
{
[Flags]
internal enum DosDefineFlags
Expand Down
Loading

0 comments on commit 95dfe06

Please sign in to comment.