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); }
private async Task BoardLoop(MCUBoard board, List <IOconfOut230Vac> ports, CancellationToken token) { var lastActions = new SwitchboardAction[ports.Max(p => p.PortNumber)]; var boardStateName = board.BoxName + "_state"; // we use the next 2 booleans to avoid spamming logs/display with an ongoing problem, so we only notify at the beginning and when we resume normal operation. // we might still get lots of entries for problems that alternate between normal and failed states, but for now is a good data point to know if that case is happening. var waitingBoardReconnect = false; var tryingToRecoverAfterTimeoutWatch = new Stopwatch(); while (!token.IsCancellationRequested) { try { var vector = await _cmd.When(_ => true, token); if (!CheckConnectedStateInVector(board, boardStateName, ref waitingBoardReconnect, vector)) { continue; // no point trying to send commands while there is no connection to the board. } foreach (var port in ports) { await DoPortActions(vector, board, port, lastActions, token); } if (tryingToRecoverAfterTimeoutWatch.IsRunning) { tryingToRecoverAfterTimeoutWatch.Stop(); CALog.LogInfoAndConsoleLn(LogID.A, $"wrote to switch board without time outs after {tryingToRecoverAfterTimeoutWatch.Elapsed}, resuming normal action frequency - {board.ToShortDescription()}"); } } catch (TimeoutException) { if (!tryingToRecoverAfterTimeoutWatch.IsRunning) { // we only notify of the situation once while this is a different way a disconnect might look like, we have CALog.LogInfoAndConsoleLn(LogID.A, $"timed out writing to switchboard, reducing action frequency until reconnect - {board.ToShortDescription()}"); tryingToRecoverAfterTimeoutWatch.Restart(); } // forcing reduced acting frequency ) try { await Task.Delay(500, token); } catch (TaskCanceledException) { } } catch (TaskCanceledException ex) { if (!token.IsCancellationRequested) { CALog.LogErrorAndConsoleLn(LogID.A, ex.ToString()); } } catch (Exception ex) { CALog.LogErrorAndConsoleLn(LogID.A, ex.ToString()); } } AllOff(board, ports); }
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 } }