/// <summary> /// Runs the sample. /// </summary> /// <param name="lamp">The lamp, as an analog output.</param> public static void Run(ISingleOutput lamp) { // Check parameters: if (lamp == null) { throw new ArgumentNullException(nameof(lamp)); } // Smoothly blink the output: const int Steps = 10; const int PauseInMs = 50; while (true) { for (int step = 0; step < Steps; step++) { lamp.Value = (float)step / (float)Steps; Thread.Sleep(PauseInMs); } for (int step = Steps; step > 0; step--) { lamp.Value = (float)step / (float)Steps; Thread.Sleep(PauseInMs); } } }
/// <summary> /// Creates a <see cref="SingleMappedFromBooleanOutput"/> object mapping the boolean values false/true to two /// float values. /// </summary> /// <param name="targetOutput">The target output receiving the float values.</param> /// <param name="falseValue">The value that the <paramref name="targetOutput"/> shall be set to when the /// <see cref="IBooleanInput.Value"/> property is false.</param> /// <param name="trueValue">The value that the <paramref name="targetOutput"/> shall be set to when the /// <see cref="IBooleanInput.Value"/> property is true.</param> /// <returns></returns> public static SingleMappedFromBooleanOutput MappedFromBoolean( this ISingleOutput targetOutput, float falseValue, float trueValue) { return(new SingleMappedFromBooleanOutput(targetOutput, falseValue, trueValue)); }
/// <summary> /// Creates a <see cref="SingleSmoothedOutput"/> object which will slowly approach the /// <paramref name="targetOutput"/> value to the goal <see cref="ISingleOutput.Value"/>. /// </summary> /// <param name="targetOutput">The target output to be smoothed.</param> /// <param name="valueChangePerSecond">The amount by that the <paramref name="targetOutput"/> value shall change /// per second in order to reach the <see cref="Value"/> property which definies the goal value.</param> /// <param name="rampIntervalMs">The interval, in milliseconds, in which the <paramref name="targetOutput"/> /// value shall be computed and set. The smaller this value, the more often and more smoothly will the target /// value be adapted.</param> /// <returns>The created <see cref="SingleSmoothedOutput"/> object.</returns> public static SingleSmoothedOutput Smoothed( this ISingleOutput targetOutput, float valueChangePerSecond, int rampIntervalMs) { return(new SingleSmoothedOutput(targetOutput, valueChangePerSecond, rampIntervalMs)); }
/// <summary> /// Creates a <see cref="SingleScaledOutput"/> object which will scale values linear using a factor and an /// offset. /// </summary> /// <param name="targetOutput">The target output to received the scaled values.</param> /// <param name="quadraticCoefficient">The factor by which the square of the value will be used.</param> /// <param name="factor">The factor to use.</param> /// <param name="offset">The offset to use.</param> /// <returns>The created <see cref="SingleScaledOutput"/> object.</returns> /// <remarks>The <paramref name="targetOutput"/> will receive values scaled by the formula: /// <see cref="ISingleOutput.Value">Value</see> * <paramref name="factor"/> + <paramref name="offset"/>. /// </remarks> public static SingleScaledOutput Scaled( this ISingleOutput targetOutput, float quadraticCoefficient, float factor, float offset) { return(new SingleScaledOutput(targetOutput, quadraticCoefficient, factor, offset)); }
/// <summary> /// Creates an instance given a factor only, using 0.0 as the offset. /// </summary> /// <param name="target">The target output which shall receive the scaled values.</param> /// <param name="factor">The factor to scale the output.</param> /// <remarks>Setting the <see cref="Value"/> will set the <paramref name="target"/> value to /// <see cref="Value"/> * <paramref name="factor"/>.</remarks> public SingleScaledOutput(ISingleOutput target, float factor) { if (target == null) { throw new ArgumentNullException(nameof(target)); } _target = target; _factor = factor; }
/// <summary> /// Creates an instance. /// </summary> /// <param name="targetOutput">The target output receiving the float values.</param> /// <param name="falseValue">The value that the <paramref name="targetOutput"/> shall be set to when the /// <see cref="IBooleanInput.Value"/> property is false.</param> /// <param name="trueValue">The value that the <paramref name="targetOutput"/> shall be set to when the /// <see cref="IBooleanInput.Value"/> property is true.</param> public SingleMappedFromBooleanOutput(ISingleOutput targetOutput, float falseValue, float trueValue) { if (targetOutput == null) { throw new ArgumentNullException(nameof(targetOutput)); } _targetOutput = targetOutput; _falseValue = falseValue; _trueValue = trueValue; _targetOutput.Value = falseValue; }
/// <summary> /// Creates an instance given a quadratic and linear factor and an offset (y = ax² + bx + c). /// </summary> /// <param name="target">The target output which shall receive the scaled values.</param> /// <param name="quadraticCoefficient">The factor by which the square of the value will be used.</param> /// <param name="factor">The factor to scale the output.</param> /// <param name="offset">The offset to add to the output.</param> /// <remarks>Setting the <see cref="Value"/> will set the <paramref name="target"/> value to /// <see cref="Value"/> * <paramref name="factor"/> + <paramref name="offset"/>.</remarks> public SingleScaledOutput(ISingleOutput target, float quadraticCoefficient, float factor, float offset) { if (target == null) { throw new ArgumentNullException(nameof(target)); } _target = target; _quadraticCoefficient = quadraticCoefficient; _factor = factor; _offset = offset; }
/// <summary> /// Creates an instance. /// </summary> /// <param name="targetOutput">The target output to be smoothed.</param> /// <param name="valueChangePerSecond">The amount by that the <paramref name="targetOutput"/> value shall change /// per second in order to reach the <see cref="Value"/> property which definies the goal value.</param> /// <param name="rampIntervalMs">The interval, in milliseconds, in which the <paramref name="targetOutput"/> /// value shall be computed and set. The smaller this value, the more often and more smoothly will the target /// value be adapted.</param> public SingleSmoothedOutput(ISingleOutput targetOutput, float valueChangePerSecond, int rampIntervalMs) { _targetOutput = targetOutput ?? throw new ArgumentNullException(nameof(targetOutput)); if (valueChangePerSecond <= 0.0f) { throw new ArgumentOutOfRangeException(nameof(valueChangePerSecond)); } if (rampIntervalMs <= 0) { throw new ArgumentOutOfRangeException(nameof(rampIntervalMs)); } _valueChangePerTick = valueChangePerSecond / TimeSpan.TicksPerSecond; _rampIntervalMs = rampIntervalMs; }
public static void Run(ISingleInput input, ISingleOutput lamp) { if (input == null) { throw new ArgumentNullException(nameof(input)); } if (lamp == null) { throw new ArgumentNullException(nameof(lamp)); } while (true) { lamp.Value = input.Value; Thread.Sleep(100); // Only to give you a chance to redeploy usung firmware as of 2018-04-08. } }
/// <summary> /// Initializes a new instance of the <see cref="StepperMotor"/> class. /// </summary> /// <param name="phase1Output">The H-Bridge that controls motor phase 1.</param> /// <param name="phase2Output">The H-Bridge that controls motor phase 2.</param> /// <param name="stepsPerStepCycle">The steps per step cycle.</param> public StepperMotor( ISingleOutput phase1Output, ISingleOutput phase2Output, int stepsPerStepCycle) { if (phase1Output == null) { throw new ArgumentNullException(nameof(phase1Output)); } if (phase2Output == null) { throw new ArgumentNullException(nameof(phase2Output)); } if (phase1Output == phase2Output) { throw new ArgumentException("Use different phaseOutputs."); } _phase1Output = phase1Output; _phase2Output = phase2Output; _maxIndex = stepsPerStepCycle - 1; _phaseIndex = 0; // Configure step tables: if (stepsPerStepCycle == 4) { ComputeWholeStepTables(); } else if (stepsPerStepCycle == 8) { ComputeHalfStepTables(); } else if (stepsPerStepCycle >= 8) { ComputeMicrostepTables(stepsPerStepCycle); } else { throw new ArgumentException( "Use 4 for full steps; 8 for half steps; or >8 for stepsPerStepCycle", nameof(stepsPerStepCycle)); } }
public void SetConnection(ISingleOutput input) { Input = input; }
public void Connect(ISingleOutput dendrite) { _dendrites.Add(dendrite); }
/// <summary> /// Creates a <see cref="SingleScaledOutput"/> object which will scale values linear using a factor only and /// 0.0 as the offset. /// </summary> /// <param name="targetOutput">The target output to received the scaled values.</param> /// <param name="factor">The factor to use.</param> /// <returns>The created <see cref="SingleScaledOutput"/> object.</returns> /// <remarks>The <paramref name="targetOutput"/> will receive values scaled by the formula: /// <see cref="ISingleOutput.Value">Value</see> * <paramref name="factor"/>.</remarks> public static SingleScaledOutput Scaled(this ISingleOutput targetOutput, float factor) { return(new SingleScaledOutput(targetOutput, factor)); }
/// <summary> /// Runs a "Turmbergbahn" train, that is, 2 trains hanging on a single steel wire driven by a motor, running on /// the same rails using a "Abt'sche Weiche". /// </summary> /// <param name="trainMotor">The motor driving both trains at once. +1.0 is output for the direction so that /// train 1 drives upwards and train 2 drives downwards, -1.0 vice versa.</param> /// <param name="train1ReachedBottomStation">Signals true when train 1 reached the bottom station (and thus /// train 2 reached the top station).</param> /// <param name="train2ReachedBottomStation">Signals true when train 2 reached the bottom station (and thus /// train 1 reached the top station).</param> /// <param name="doorMotor">The motor driving all doors on both trains at once. +1.0 is output for opening, /// -1.0 for closing.</param> /// <param name="redLight">True shall light up a red traffic light when people shall not enter or leave the /// train.</param> /// <param name="greenLight">True shall light up a green traffic light when people may enter or leave the /// train.</param> /// <param name="waitForDoorsToMoveInMs">The time, in milliseconds, to wait for the /// <paramref name="doorMotor"/> to have operated all doors reliably.</param> /// <param name="waitWithOpenDoorsInMs">The time, in milliseconds, that the doors shall remain open.</param> /// <param name="waitAroundDoorOperationsInMs">The time, in milliseconds, to wait after the train stopped and /// before opening the door, and after the doors were closed again before the train starts.</param> public static void Run(ISingleOutput trainMotor, IBooleanInput trainReachedBottomStation, ISingleOutput doorMotor, int waitForDoorsToMoveInMs, int waitWithOpenDoorsInMs, int waitAroundDoorOperationsInMs) { // Check arguments: if (trainMotor == null) { throw new ArgumentNullException(nameof(trainMotor)); } if (trainReachedBottomStation == null) { throw new ArgumentNullException(nameof(trainReachedBottomStation)); } if (doorMotor == null) { throw new ArgumentNullException(nameof(doorMotor)); } if (waitForDoorsToMoveInMs < 0) { throw new ArgumentOutOfRangeException(nameof(waitForDoorsToMoveInMs)); } if (waitWithOpenDoorsInMs < 0) { throw new ArgumentOutOfRangeException(nameof(waitWithOpenDoorsInMs)); } if (waitAroundDoorOperationsInMs < 0) { throw new ArgumentOutOfRangeException(nameof(waitAroundDoorOperationsInMs)); } // Run the train: float moveDirection = 1.0f; while (true) { // Move the train in the current direction until one of the end buttons is pressed: if (!trainReachedBottomStation.Value) { trainMotor.Value = moveDirection; trainReachedBottomStation.WaitFor(value: true, edgeOnly: false); trainMotor.Value = 0.0f; } // Change direction for the next pass: moveDirection = -moveDirection; // Wait a bit before opening the doors: Thread.Sleep(waitAroundDoorOperationsInMs); // Open the door: doorMotor.Value = 1.0f; Thread.Sleep(waitForDoorsToMoveInMs); doorMotor.Value = 0.0f; // Let people step in and out, wait a bit: Thread.Sleep(waitWithOpenDoorsInMs); // Close the door: doorMotor.Value = -1.0f; Thread.Sleep(waitForDoorsToMoveInMs); doorMotor.Value = 0.0f; // Wait a bit before the train starts again: Thread.Sleep(waitAroundDoorOperationsInMs); } }
/// <summary> /// Runs a clock driven by a simple DC motor. /// </summary> /// <param name="motor">The motor to drive continuously.</param> /// <param name="minimumMotorSpeed">The minimum speed setting that causes the motor to turn. Speeds below this /// threshold may cause the motor to not turn at all.</param> /// <param name="initialSpeedGuess">A rough initial guess for a speed to try to reach the first cycle in time. /// </param> /// <param name="pulse">The input which pulses to measure the motor speed.</param> /// <param name="pulseDebounceMillisecondsAtFullSpeed">The time, in milliseconds, that shall be used as the /// debounce time for the <paramref name="pulse"/> input when the <paramref name="motor"/> runs at full speed. /// </param> /// <param name="pulseMonitor">An output to show the monitored pulse input.</param> /// <param name="idealSecondsPerCycle">The number of seconds for one pulse cycle which would give a perfectly /// accurate operation of the clock.</param> /// <remarks>The motor speed is constantly adapted to the measurement given by the pulse to realize the needed /// pulse times without cumulative errors, even if the motor changes its behaviour during the operation. /// </remarks> public static void Run(ISingleOutput motor, float minimumMotorSpeed, float initialSpeedGuess, IBooleanInput pulse, double idealSecondsPerCycle, IBooleanInput runAtFullSpeedSwitch) { // Check parameters: if (motor == null) { throw new ArgumentNullException(nameof(motor)); } if (minimumMotorSpeed <= 0f || minimumMotorSpeed >= 1f) { throw new ArgumentOutOfRangeException(nameof(minimumMotorSpeed)); } if (initialSpeedGuess < minimumMotorSpeed || initialSpeedGuess > 1f) { throw new ArgumentOutOfRangeException(nameof(initialSpeedGuess)); } if (pulse == null) { throw new ArgumentNullException(nameof(pulse)); } if (idealSecondsPerCycle <= 0f) { throw new ArgumentOutOfRangeException(nameof(idealSecondsPerCycle)); } // Run unit tests on the RunningAverageCalculator class: Console.WriteLine("Testing RunningAverageCalculator"); RunningAverageCalculator.Test(); Console.WriteLine("RunningAverageCalculator successfully tested"); // An average calculator the motor output voltage (ranging from 0.0f to 1.0f) needed to read one cycle in // idealSecondsPerCycle seconds: var voltageForIdealCycleTime = new RunningAverageCalculator(10); // Add the initial guess of that voltage: voltageForIdealCycleTime.Add(initialSpeedGuess); // Give a short full speed pulse to the motor to get it surely running: motor.Value = 1.0f; System.Threading.Thread.Sleep(10); // Let the motor run until the pulse changes from false to true to initialize the position to a pulse // boundary: Console.WriteLine("Initializing to pulse position"); motor.Value = initialSpeedGuess; pulse.WaitFor(true, true); // This is our starting point: var clockStartTime = DateTime.UtcNow; int n = 0; // The number of cycles passed DateTime t0 = clockStartTime; // Ideal start of the running cycle DateTime a0 = t0; // Actual start of the running cycle Console.WriteLine("Ideal seconds per cycle = " + idealSecondsPerCycle.ToString("N4")); Console.WriteLine("Running the clock at initial v = " + initialSpeedGuess.ToString("N4")); while (true) { if (runAtFullSpeedSwitch.Value) { Console.WriteLine("Manually adjusting clock by running at full speed"); // Let the motor run at full speed to adjust the clock's time on the user's request: float lastSpeed = motor.Value; motor.Value = 1f; runAtFullSpeedSwitch.WaitFor(false); // Reinitialize: Console.WriteLine("Initializing to pulse position"); motor.Value = lastSpeed; pulse.WaitFor(true, true); Console.WriteLine("Pulse reached"); n = 0; clockStartTime = DateTime.UtcNow; t0 = clockStartTime; a0 = t0; } // Calculate the end of the current (and the beginning of the next) cylce: n++; DateTime t1 = clockStartTime.AddSeconds(idealSecondsPerCycle * n); double t1a0 = (t1 - a0).TotalSeconds; // Wait for the next (debounced) pulse, telling us that we reached the end of the current cycle: DateTime a1; // The actual end of the current cycle. double a1a0; // a1 - a0: The number of seconds between a0 and a1. int bounces = 0; // The number of bounces the pulse contacts made do { pulse.WaitFor(true, true); a1 = DateTime.UtcNow; a1a0 = (a1 - a0).TotalSeconds; bounces++; } // Debounce by accepting the next pulse not earlier than at 70% of the wanted time interval: while (a1a0 < 0.7 * t1a0); // We may have missed one or more pulses due to mechanical errors in pulse detection. // Estimate the number of real cyles, rounding by adding 0.5 and casting to int (which truncates): int cycles = (int)((a1 - t1).TotalSeconds * motor.Value / (voltageForIdealCycleTime.Average * idealSecondsPerCycle) + 0.5) + 1; if (cycles > 1) { // We lost [cycles - 1] pulses. The worm turned multiple times until we got a contact. // Adjust the counted pulses and the ideal target time for that number of pulses since the last // contact: n = n + cycles - 1; t1 = clockStartTime.AddSeconds(idealSecondsPerCycle * n); } // Take note of the current measurement's insight: voltageForIdealCycleTime.Add(motor.Value * a1a0 / (idealSecondsPerCycle * cycles)); // Calculate the motor voltage needed to reach the next cycle pulse right in time t1 and // set the motor voltage to this value, taking the lower and upper bounds into account: DateTime t2 = clockStartTime.AddSeconds(idealSecondsPerCycle * (n + 1)); motor.Value = Math.Max(minimumMotorSpeed, Math.Min(1.0f, (float)(voltageForIdealCycleTime.Average * idealSecondsPerCycle / (t2 - a1).TotalSeconds))); // Report to debugger: double diff = (a1 - t1).TotalSeconds; // Math.Abs(double) is not implemented on Netduiono 3: double absDiff = diff < 0.0 ? -diff : diff; Console.WriteLine( "n = " + n.ToString("N0").PadLeft(8) + " | bounces = " + bounces.ToString().PadLeft(3) + " | cycles = " + cycles.ToString().PadLeft(2) + " | vi = " + voltageForIdealCycleTime.Average.ToString("N4").PadLeft(6) + " | t1 = " + t1.ToString("HH:mm:ss") + " | a1 = " + a1.ToString("HH:mm:ss") + " | " + (diff == 0.0 ? "exactly in time " : ((diff < 0.0 ? "early by " : " late by ") + absDiff.ToString("N4").PadLeft(7) + "s (" + (absDiff * 100.0 / t1a0).ToString("N2").PadLeft(5) + "%)")) + " | v = " + motor.Value.ToString("N4")); // The current cycle gets the passed one: t0 = t1; a0 = a1; } }
/// <summary> /// Runs a "Turmbergbahn" train, that is, 2 trains hanging on a single steel wire driven by a motor, running on /// the same rails using a "Abt'sche Weiche". /// </summary> /// <param name="trainMotor">The motor driving both trains at once. +1.0 is output for the direction so that /// train 1 drives upwards and train 2 drives downwards, -1.0 vice versa.</param> /// <param name="train1ReachedBottomStation">Signals true when train 1 reached the bottom station (and thus /// train 2 reached the top station).</param> /// <param name="train2ReachedBottomStation">Signals true when train 2 reached the bottom station (and thus /// train 1 reached the top station).</param> /// <param name="doorMotor">The motor driving all doors on both trains at once. +1.0 is output for opening, /// -1.0 for closing.</param> /// <param name="redLight">True shall light up a red traffic light when people shall not enter or leave the /// train.</param> /// <param name="greenLight">True shall light up a green traffic light when people may enter or leave the /// train.</param> /// <param name="waitForDoorsToMoveInMs">The time, in milliseconds, to wait for the /// <paramref name="doorMotor"/> to have operated all doors reliably.</param> /// <param name="waitWithOpenDoorsInMs">The time, in milliseconds, that the doors shall remain open.</param> /// <param name="waitAroundDoorOperationsInMs">The time, in milliseconds, to wait after the train stopped and /// before opening the door, and after the doors were closed again before the train starts.</param> public static void Run(ISingleOutput trainMotor, IBooleanInput train1ReachedBottomStation, IBooleanInput train2ReachedBottomStation, ISingleOutput doorMotor, IBooleanOutput redLight, IBooleanOutput greenLight, int waitForDoorsToMoveInMs, int waitWithOpenDoorsInMs, int waitAroundDoorOperationsInMs) { // Check arguments: if (trainMotor == null) { throw new ArgumentNullException(nameof(trainMotor)); } if (train1ReachedBottomStation == null) { throw new ArgumentNullException(nameof(train1ReachedBottomStation)); } if (train2ReachedBottomStation == null) { throw new ArgumentNullException(nameof(train2ReachedBottomStation)); } if (doorMotor == null) { throw new ArgumentNullException(nameof(doorMotor)); } if (redLight == null) { throw new ArgumentNullException(nameof(redLight)); } if (greenLight == null) { throw new ArgumentNullException(nameof(greenLight)); } if (waitForDoorsToMoveInMs < 0) { throw new ArgumentOutOfRangeException(nameof(waitForDoorsToMoveInMs)); } if (waitWithOpenDoorsInMs < 0) { throw new ArgumentOutOfRangeException(nameof(waitWithOpenDoorsInMs)); } if (waitAroundDoorOperationsInMs < 0) { throw new ArgumentOutOfRangeException(nameof(waitAroundDoorOperationsInMs)); } // Run the train: bool moveDirection = false; var trainReachedBottom = new BooleanOrInput(train1ReachedBottomStation, train2ReachedBottomStation); while (true) { // Initialize lamps: redLight.Value = true; greenLight.Value = false; // Move the train in the current direction until one of the end buttons is pressed: if (!(train1ReachedBottomStation.Value || train2ReachedBottomStation.Value)) { trainMotor.Value = moveDirection ? 1.0f : -1.0f; trainReachedBottom.WaitFor(value: true, edgeOnly: false); trainMotor.Value = 0.0f; } moveDirection = !moveDirection; // Wait a bit before opening the doors: Thread.Sleep(waitAroundDoorOperationsInMs); // Open the door: doorMotor.Value = 1.0f; Thread.Sleep(waitForDoorsToMoveInMs); doorMotor.Value = 0.0f; // Let people step in and out, wait a bit: redLight.Value = false; greenLight.Value = true; Thread.Sleep(waitWithOpenDoorsInMs); redLight.Value = true; greenLight.Value = false; // Close the door: doorMotor.Value = -1.0f; Thread.Sleep(waitForDoorsToMoveInMs); doorMotor.Value = 0.0f; // Wait a bit before the train starts again: Thread.Sleep(waitAroundDoorOperationsInMs); } }