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 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); }
private static void AllOff(MCUBoard board, List <IOconfOut230Vac> ports) { try { board.SafeWriteLine("off", CancellationToken.None); foreach (var port in ports) { if (port.HasOnSafeState) { board.SafeWriteLine($"p{port.PortNumber} on", CancellationToken.None); } } } catch (Exception ex) { CALog.LogErrorAndConsoleLn(LogID.A, $"Error detected while attempting to set ports to default positions for board {board.ToShortDescription()}", ex); } }
private static async Task <MCUBoard> AttemptToOpenDeviceConnection(string name, CALogLevel logLevel) { try { var mcu = await MCUBoard.OpenDeviceConnection(name); mcu.productType = mcu.productType ?? GetStringFromDmesg(mcu.PortName); string logline = logLevel == CALogLevel.Debug ? mcu.ToDebugString(Environment.NewLine) : mcu.ToString(); CALog.LogInfoAndConsoleLn(LogID.A, logline); return(mcu); } catch (UnauthorizedAccessException ex) { CALog.LogErrorAndConsoleLn(LogID.A, $"Unable to open {name}, Exception: {ex.Message}" + Environment.NewLine); } catch (Exception ex) { CALog.LogErrorAndConsoleLn(LogID.A, $"Unable to open {name}, Exception: {ex}" + Environment.NewLine); } return(default);
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 } }
private async static Task <MCUBoard> Create(string name, int baudrate, bool skipBoardAutoDetection) { MCUBoard board = null; try { var port = new SerialPort(name); board = new MCUBoard(port); port.BaudRate = 1; port.DtrEnable = true; port.RtsEnable = true; port.BaudRate = baudrate; port.PortName = name; port.ReadTimeout = 2000; port.WriteTimeout = 2000; port.Open(); board.pipeReader = PipeReader.Create(port.BaseStream); Thread.Sleep(30); // it needs to await that the board registers that the COM port has been opened before sending commands (work arounds issue when first opening the connection and sending serial). board.TryReadLine = board.TryReadAsciiLine; //this is the default which can be changed in ReadSerial based on the ThirdPartyProtocolDetection board.productType = "NA"; if (skipBoardAutoDetection) { board.InitialConnectionSucceeded = true; } else { board.InitialConnectionSucceeded = await board.ReadSerialNumber(); } if (File.Exists("IO.conf")) { // note that unlike the discovery at MCUBoard.OpenDeviceConnection that only considers the usb port, in here we can find the boards by the serial number too. foreach (var ioconfMap in IOconfFile.GetMap()) { if (ioconfMap.SetMCUboard(board)) { board.BoxName = ioconfMap.BoxName; board.ConfigSettings = ioconfMap.BoardSettings; port.ReadTimeout = ioconfMap.BoardSettings.MaxMillisecondsWithoutNewValues; await board.UpdateCalibration(board.ConfigSettings); } } } } catch (Exception ex) { CALog.LogException(LogID.A, ex); } if (board != null && !board.InitialConnectionSucceeded) { board.pipeReader?.Complete(); board.port.Close(); Thread.Sleep(100); } else if (board != null && board.IsEmpty() && board.BoxName == null) {//note we don't log this for devices without serial that were mapped by usb (last check above) CALog.LogInfoAndConsoleLn(LogID.B, $"some data without serial detected for device at port {name} - {baudrate} / still connected"); } return(board); }