public SwitchboardAction MakeNextActionDecision(NewVectorReceivedArgs vector)
        {
            if (!TryGetSwitchboardInputsFromVector(vector, out var current))
            {
                return(_lastAction); // not connected, we skip this heater and act again when the connection is re-established
            }
            var(hasValidTemperature, temp) = GetOvenTemperatureFromVector(vector);
            // Careful consideration must be taken if changing the order of the below statements.
            // Note that even though we received indication the board is connected above,
            // if the connection is lost after we return the action, the control program can still fail to act on the heater.
            // When it happens, the MustResend* methods will resend the expected action after 5 seconds.
            var vectorTime = vector.GetVectorTime();

            var(executeManualOff, executeManualOn) = MustExecuteManualMode(vectorTime);
            var action =
                executeManualOn ? new SwitchboardAction(true, vectorTime.AddSeconds(10)) :
                MustTurnOff(hasValidTemperature, temp, vectorTime) ? new SwitchboardAction(false, vectorTime) :
                CanTurnOn(hasValidTemperature, temp, vectorTime) ? new SwitchboardAction(true, vectorTime.AddSeconds(10)) :
                // manual off re-enables temp control, so we only turn off if CanTurnOn above didn't decide to turn on
                executeManualOff ? new SwitchboardAction(false, vectorTime) :
                // keep off: retrigger if current is detected
                MustResendOffCommand(temp, current, vectorTime) ? new SwitchboardAction(false, vectorTime) :
                // keep on: re-trigger early to avoid switching
                _lastAction.IsOn && _lastAction.GetRemainingOnSeconds(vectorTime) < 2 ? new SwitchboardAction(true, vectorTime.AddSeconds(10)) :
                _lastAction;

            return(_lastAction = action);
        }
        public void ProportionalGainUsesMaxOutput()
        {
            //In this test we are 500 degrees below the target temperature so that it will actuate at max output for a long time.
            //Because we are using time based power control, this means it should turn on for 80% of the control period
            //the gain of 2 means there should pass 2 seconds for every 1C to gain, so the proportional control alone would normally turn it on for the whole control period.
            var element = new HeaterElement(NewConfig.WithProportionalGain(2).WithMaxOutput(0.8d).Build());

            element.SetTargetTemperature(544);
            var vector = new NewVectorReceivedArgs(NewVectorSamples);

            element.MakeNextActionDecision(vector);
            var newSamples = NewVectorSamples;

            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(5).ToVectorDouble();
            var action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.AreEqual(true, action.IsOn, "should still be on after 5 seconds");
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(23).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(true, action.IsOn, "should still be on after 23 seconds");
            newSamples = NewVectorSamples;
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(24).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(false, action.IsOn, "should be off after 24 seconds");
            newSamples = NewVectorSamples;
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(29).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(false, action.IsOn, "should remain off just before next control period (29 seconds)");
            newSamples = NewVectorSamples;
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(30).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(true, action.IsOn, "should try heating again on the next control period");
        }
        public void DisconnectedTemperatureOverControlPeriodLengthTurnsOff()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(200);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);

            element.MakeNextActionDecision(vector);
            var newSamples = NewVectorSamples;

            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(29).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            var action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.IsTrue(action.IsOn);//still within the control period, should not turn off yet
            newSamples = NewVectorSamples;
            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(30).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.IsFalse(action.IsOn);//reached the end of the control period, so it should turn off
            newSamples = NewVectorSamples;
            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(31).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.IsFalse(action.IsOn);//must still be off as long as it keep being disconnected (its good to do the extra check, as the first actuation at the end of the control period tends to be off)
        }
        public void WhenHeaterIsOnCanTurnOffBeforeReachingTargetTemperatureBasedOnProportionalGain()
        {
            //in this test we are 6 degrees below the target temperature when first actuating (so it turns on)
            //and even though the temperature did not change the proportional gain must turn the heater off after the expected amount of time.
            //the gain of 2 means there should pass 2 seconds for every 1C to gain, so we expect it to take 12 seconds before it decides to turn off.
            var element = new HeaterElement(NewConfig.WithProportionalGain(2).Build());

            element.SetTargetTemperature(50);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);

            element.MakeNextActionDecision(vector);
            var newSamples = NewVectorSamples;

            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(5).ToVectorDouble();
            var action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.AreEqual(true, action.IsOn, "should still be on after 5 seconds");
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(11).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(true, action.IsOn, "should still be on after 11 seconds");
            newSamples = NewVectorSamples;
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(12).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(false, action.IsOn, "should be off after 12 seconds");
            newSamples = NewVectorSamples;
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(17).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(false, action.IsOn, "should remain off within 30 seconds after turning on");
            newSamples = NewVectorSamples;
            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(30).ToVectorDouble();
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.AreEqual(true, action.IsOn, "should try heating again 30 seconds after it turned off");
        }
        public void WhenHeaterIsOffCanTurnOn()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(45);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);
            var action = element.MakeNextActionDecision(vector);

            Assert.AreEqual(true, action.IsOn);
            Assert.AreEqual(vector.GetVectorTime().AddSeconds(10), action.TimeToTurnOff);
        }
        public void WhenOvenWasTurnedOffAndOnHeaterCanTurnOn()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(0);
            element.SetTargetTemperature(45);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);
            var action = element.MakeNextActionDecision(vector);

            Assert.AreEqual(true, action.IsOn);
        }
        public void WhenHeaterIsOverHalfDegreeAboveTempKeepsOff()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(70);
            var samples = NewVectorSamples;

            samples["temp"] = 70.5;
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(samples);
            var action = element.MakeNextActionDecision(vector);

            Assert.AreEqual(false, action.IsOn);
        }
        //Note that the switchboard controller would ignore the action regardless of what we send due to the disconnect.
        //However, having the decision logic explicitely indicate there is no action change better reflects the situation.
        public void DisconnectedSwitchBoardDoesNotChangeCurrentAction()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(45);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);
            var firstAction = element.MakeNextActionDecision(vector);
            var newSamples  = NewVectorSamples;

            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(10).ToVectorDouble();
            newSamples["box_state"]  = (int)BaseSensorBox.ConnectionState.Connecting;
            var newAction = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.AreEqual(firstAction, newAction);
        }
        public static SwitchboardAction FromVectorSamples(NewVectorReceivedArgs args, string portName)
        {
            var isOnName    = GetOnName(portName);
            var timeOffName = GetTimeOffName(portName);

            if (!args.TryGetValue(isOnName, out var isOn))
            {
                throw new ArgumentOutOfRangeException($"failed to find {isOnName}");
            }
            if (!args.TryGetValue(timeOffName, out var timeOff))
            {
                throw new ArgumentOutOfRangeException($"failed to find {timeOffName}");
            }
            return(new SwitchboardAction(isOn == 1.0, timeOff.ToVectorDate()));
        }
        public void WhenHeaterIsOnCanTurnOff()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(100);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);

            element.MakeNextActionDecision(vector);
            var newSamples = NewVectorSamples;

            newSamples["vectortime"] = vector.GetVectorTime().AddSeconds(10).ToVectorDouble();
            newSamples["temp"]       = 101;
            var action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.AreEqual(false, action.IsOn);
        }
Exemplo n.º 11
0
        public override void OnNewVectorReceived(object sender, NewVectorReceivedArgs e)
        {
            var timestamp = e.GetVectorTime();

            var(alertsToTrigger, noSensorAlerts) = GetAlertsToTrigger(e);  // we gather alerts separately from triggering, to reduce time locking the _alerts list

            foreach (var a in alertsToTrigger ?? Enumerable.Empty <IOconfAlert>())
            {
                TriggerAlert(a, timestamp, a.Message);
            }

            foreach (var a in noSensorAlerts)
            {
                _alerts.Remove(a); // avoid missing sensors alert triggerring repeatedly on every vector received
                TriggerAlert(a, timestamp, $"Failed to find sensor {a.Sensor} for alert {a.Name}");
            }
        }
        public void DisconnectedTemperatureOver2SecondsKeepsExistingAction()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(70);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);
            var firstAction = element.MakeNextActionDecision(vector);
            var newSamples  = NewVectorSamples;

            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(1).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            newSamples = NewVectorSamples;
            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(4).ToVectorDouble();// 3 seconds after detecting the disconnect for the first time
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            var newAction = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.AreEqual(firstAction, newAction);
        }
 private static async Task DoPortActions(NewVectorReceivedArgs vector, MCUBoard board, IOconfOut230Vac port, SwitchboardAction[] lastActions, CancellationToken token)
 {
     if (token.IsCancellationRequested)
     {
         return;
     }
     try
     {
         var action = SwitchboardAction.FromVectorSamples(vector, port.Name);
         if (action.Equals(lastActions[port.PortNumber - 1]))
         {
             return; // no action changes has been requested since the last action taken on the heater.
         }
         var onSeconds = action.GetRemainingOnSeconds(vector.GetVectorTime());
         if (onSeconds <= 0)
         {
             await board.SafeWriteLine($"p{port.PortNumber} off", token);
         }
         else if (onSeconds == int.MaxValue)
         {
             await board.SafeWriteLine($"p{port.PortNumber} on", token);
         }
         else
         {
             await board.SafeWriteLine($"p{port.PortNumber} on {onSeconds}", token);
         }
         lastActions[port.PortNumber - 1] = action;
     }
     catch (TimeoutException)
     {
         // we don't want logging at this level as the caller handles this in a way that reduces the amount of noise for failures that last many vectors.
         throw;
     }
     catch (Exception)
     {
         CALog.LogErrorAndConsoleLn(LogID.A, $"Failed executing port action for board {board.BoxName} port {port.Name} - {port.Name}");
         throw; // this will log extra info and avoid extra board actions on this cycle
     }
 }
        public void ReconnectedOverTargetTemperatureTurnsOff()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(200);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);
            var firstAction = element.MakeNextActionDecision(vector);
            var newSamples  = NewVectorSamples;

            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(1).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            var disconnectedAction = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            newSamples = NewVectorSamples;
            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(3).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.ReceivingValues;
            newSamples["temp"] = 201;
            var newAction = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.IsFalse(newAction.IsOn);
            Assert.AreEqual(firstAction, disconnectedAction, "initial and disconnected action unexpectedly were different");
            Assert.AreNotEqual(disconnectedAction, newAction, "initial and disconnected action unexpectedly were the same");
        }
        public void ReconnectedUnderTargetTemperatureStartsPostponedControlPeriodInmediately()
        {
            var element = new HeaterElement(NewConfig.Build());

            element.SetTargetTemperature(200);
            NewVectorReceivedArgs vector = new NewVectorReceivedArgs(NewVectorSamples);
            var newSamples = NewVectorSamples;

            newSamples["vectortime"]        = vector.GetVectorTime().ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            var action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));

            Assert.IsFalse(action.IsOn); //no actuation yet, we are not connected
            newSamples = NewVectorSamples;
            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(2).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.Connecting;
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.IsFalse(action.IsOn); //still no actuation as we are not connected
            newSamples = NewVectorSamples;
            newSamples["vectortime"]        = vector.GetVectorTime().AddSeconds(3).ToVectorDouble();
            newSamples["temperature_state"] = (int)BaseSensorBox.ConnectionState.ReceivingValues;
            action = element.MakeNextActionDecision(new NewVectorReceivedArgs(newSamples));
            Assert.IsTrue(action.IsOn); //we reconnected, so start the postponed control period right away
        }
 public static DateTime GetVectorTime(this NewVectorReceivedArgs args) => args["vectortime"].ToVectorDate();
        private static bool CheckConnectedStateInVector(MCUBoard board, string boardStateName, ref bool waitingBoardReconnect, NewVectorReceivedArgs vector)
        {
            var vectorState = (BaseSensorBox.ConnectionState)(int) vector[boardStateName];
            var connected   = vectorState >= BaseSensorBox.ConnectionState.Connected;

            if (waitingBoardReconnect && connected)
            {
                CALog.LogData(LogID.B, $"resuming switchboard actions after reconnect on {board.ToShortDescription()}");
                waitingBoardReconnect = false;
            }
            else if (!waitingBoardReconnect && !connected)
            {
                CALog.LogData(LogID.B, $"stopping switchboard actions while connection is reestablished - state: {vectorState} - {board.ToShortDescription()}");
                waitingBoardReconnect = true;
            }
            return(connected);
        }
Exemplo n.º 18
0
        private (IEnumerable <IOconfAlert> alertsToTrigger, IEnumerable <IOconfAlert> noSensorAlerts) GetAlertsToTrigger(NewVectorReceivedArgs e)
        {
            // we only create the lists later to avoid unused lists on every call
            List <IOconfAlert> alertsToTrigger = null;
            List <IOconfAlert> noSensorAlerts  = null;

            lock (_alerts)
                foreach (var a in _alerts)
                {
                    if (!e.TryGetValue(a.Sensor, out var val))
                    {
                        EnsureInitialized(ref noSensorAlerts).Add(a);
                    }
                    else if (a.CheckValue(val, e.GetVectorTime()))
                    {
                        EnsureInitialized(ref alertsToTrigger).Add(a);
                    }
                }

            return(
                alertsToTrigger ?? Enumerable.Empty <IOconfAlert>(),
                noSensorAlerts ?? Enumerable.Empty <IOconfAlert>());
        }
 public override void OnNewVectorReceived(object sender, NewVectorReceivedArgs e)
 => Console.Write(e["IterationSensor"] % 2 == 0 ? "." : string.Empty);