private bool ScanPortWithIDCode(ushort comPort, ushort idCode, ScanParameters scanParams, CancellationToken cancellationToken) { string scanConnectionMode = scanParams.AutoStartParsingSequenceForScan ? ActiveScanConnectionString : PassiveScanConnectionString; string connectionString = string.Format(ConnectionStringTemplate, comPort, Settings.BaudRate, Settings.Parity, Settings.StopBits, Settings.DataBits, Settings.DtrEnable, Settings.RtsEnable, scanConnectionMode); ShowUpdateMessage($"{Tab1}Scanning COM{comPort} with ID code {idCode}..."); IConfigurationFrame configFrame = RequestDeviceConfiguration(connectionString, idCode, scanParams, cancellationToken); return(configFrame is not ConfigurationErrorFrame && SaveDeviceConfiguration(configFrame, comPort, idCode, scanParams)); }
private void ExecuteScan(ScanParameters scanParams, CancellationToken cancellationToken) { // Create wait handles to use to wait for configuration frame m_configurationWaitHandle ??= new ManualResetEventSlim(false); m_bytesReceivedWaitHandle ??= new ManualResetEventSlim(false); // Create a new phasor protocol frame parser used to dynamically request device configuration frames // and return them to remote clients so that the frame can be used in system setup and configuration if (m_frameParser is null) { m_frameParser = new MultiProtocolFrameParser(); // Attach to events on new frame parser reference m_frameParser.ConnectionAttempt += m_frameParser_ConnectionAttempt; m_frameParser.ConnectionEstablished += m_frameParser_ConnectionEstablished; m_frameParser.ConnectionException += m_frameParser_ConnectionException; m_frameParser.ConnectionTerminated += m_frameParser_ConnectionTerminated; m_frameParser.ExceededParsingExceptionThreshold += m_frameParser_ExceededParsingExceptionThreshold; m_frameParser.ParsingException += m_frameParser_ParsingException; m_frameParser.ReceivedConfigurationFrame += m_frameParser_ReceivedConfigurationFrame; m_frameParser.BufferParsed += m_frameParser_BufferParsed; // We only want to try to connect to device and retrieve configuration as quickly as possible m_frameParser.MaximumConnectionAttempts = 1; m_frameParser.SourceName = Name; m_frameParser.AutoRepeatCapturedPlayback = false; m_frameParser.AutoStartDataParsingSequence = false; m_frameParser.SkipDisableRealTimeData = true; } Task.Run(() => { ClearFeedback(); UpdateFeedback(); Ticks scanStartTime = DateTime.UtcNow.Ticks; try { SetControlEnabledState(buttonScan, false); TableOperations <Device> deviceTable = scanParams.DeviceTable; ushort[] comPorts = scanParams.ComPorts; ushort[] idCodes = scanParams.IDCodes; bool rescan = scanParams.Rescan; int scannedIDCodes = 0; HashSet <ushort> configuredPorts = new(); HashSet <ushort> configuredIDCodes = new(); HashSet <ushort> idCodeSettings = new(idCodes); int discoveredDevices = 0; long totalComScanTime = 0L; long totalComScans = 0L; ShowUpdateMessage("Reading existing configuration..."); Device[] devices = deviceTable.QueryRecordsWhere("IsConcentrator = 0").ToArray(); // If re-scanning all ports, we will not skip pre-configured ports and ID codes if (rescan) { ShowUpdateMessage($"{Tab1}Discovered {devices.Length:N0} existing devices{Environment.NewLine}"); } else { foreach (Device device in devices) { if (device is null) { continue; } Dictionary <string, string> settings = device.ConnectionString.ParseKeyValuePairs(); if (settings.TryGetValue("port", out string portVal) && ushort.TryParse(portVal.Substring(3), out ushort port)) { configuredPorts.Add(port); } configuredIDCodes.Add((ushort)device.AccessID); } ShowUpdateMessage($"{Tab1}Discovered {devices.Length:N0} existing devices, {configuredPorts.Count:N0} configured COM ports and {configuredIDCodes.Count:N0} unique ID codes{Environment.NewLine}"); } // Hold onto to device list, useful when saving configurations later (no need to re-query) scanParams.Devices = devices; // Only control progress bar for manual (non-import) scans if (buttonImport.Enabled) { SetProgressBarMinMax(0, idCodes.Length); UpdateProgressBar(0); } foreach (ushort idCode in idCodes) { cancellationToken.ThrowIfCancellationRequested(); if (configuredIDCodes.Contains(idCode)) { ShowUpdateMessage($"Skipping scan for already configured ID code {idCode}..."); } else { Ticks idScanStartTime = DateTime.UtcNow.Ticks; try { ShowUpdateMessage($"Starting scan for ID code {idCode}..."); int processedComPorts = 0; bool found = false; HashSet <ushort> unprocessedComPorts = new(comPorts); unprocessedComPorts.ExceptWith(configuredPorts); comPorts = unprocessedComPorts.ToArray(); foreach (ushort comPort in comPorts) { cancellationToken.ThrowIfCancellationRequested(); Ticks comScanStartTime = DateTime.UtcNow.Ticks; bool scanned = true; try { if (configuredPorts.Contains(comPort)) { ShowUpdateMessage($"Skipping scan for already configured COM{comPort}..."); scanned = false; continue; } if (!ScanPortWithIDCode(comPort, idCode, scanParams, cancellationToken)) { continue; } // Shorten COM port scan list as new devices are detected configuredPorts.Add(comPort); UpdateFeedback(null, ++discoveredDevices); found = true; break; } finally { Ticks comScanTime = DateTime.UtcNow.Ticks - comScanStartTime; ShowUpdateMessage($"{Environment.NewLine}>> Scan time for COM{comPort} for ID code {idCode}: {comScanTime.ToElapsedTimeString(3)}.{Environment.NewLine}"); processedComPorts++; totalComScanTime += comScanTime.Value; if (scanned) { totalComScans++; long remainingTimeEstimate = (idCodes.Length - configuredIDCodes.Count - scannedIDCodes) * comPorts.Length; if (!found) { remainingTimeEstimate += comPorts.Length - processedComPorts; } remainingTimeEstimate *= (long)(totalComScanTime / (double)totalComScans); UpdateFeedback(new Ticks(remainingTimeEstimate).ToElapsedTimeString(0)); } } } ShowUpdateMessage($"Completed scan for ID code {idCode}."); } finally { ShowUpdateMessage($"{Environment.NewLine}>>>> Scan time for ID code {idCode}: {(DateTime.UtcNow.Ticks - idScanStartTime).ToElapsedTimeString(3)}.{Environment.NewLine}"); } } scannedIDCodes++; // Only control progress bar for manual (non-import) scans if (buttonImport.Enabled) { UpdateProgressBar(scannedIDCodes); } // Serialize reduced ID code list try { // In case app needs to restart do not rescan existing ID codes if (Settings.AutoRemoveIDs && idCodeSettings.Remove(idCode)) { Settings.IDCodes = idCodeSettings.ToArray(); Settings.Save(); } } catch (Exception ex) { m_log.Publish(MessageLevel.Error, "UpdateIDCodes", "Failed while reducing ID code list", exception: ex); } } ShowUpdateMessage($"Completed scan for {scannedIDCodes:N0} ID codes over {comPorts.Length:N0} COM ports.{Environment.NewLine}"); UpdateFeedback("None -- Operation Complete"); } catch (OperationCanceledException) { ShowUpdateMessage($"{Environment.NewLine}Serial port scan cancelled.{Environment.NewLine}"); UpdateFeedback("None -- Operation Cancelled"); } catch (Exception ex) { ShowUpdateMessage($"{Environment.NewLine}ERROR: Failed during serial port scan: {ex.Message}"); UpdateFeedback("None -- Operation Failed"); } finally { ShowUpdateMessage($">>>>>> Total scan time: {(DateTime.UtcNow.Ticks - scanStartTime).ToElapsedTimeString(3)}.{Environment.NewLine}"); SetControlEnabledState(buttonScan, true); m_scanExecutionComplete.Set(); } }, cancellationToken); }
public IConfigurationFrame RequestDeviceConfiguration(string connectionString, int idCode, ScanParameters scanParams, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(connectionString)) { ShowUpdateMessage($"{Tab2}ERROR: No connection string was specified, request for configuration canceled."); return(new ConfigurationErrorFrame()); } try { int responseTimeout = scanParams.ResponseTimeout; int configFrameTimeout = scanParams.ConfigFrameTimeout; // Most of the parameters in the connection string will be for the data source in the frame parser // so we provide all of them, other parameters will simply be ignored m_frameParser.ConnectionString = connectionString; // Provide access ID to frame parser as this may be necessary to make a phasor connection m_frameParser.DeviceID = (ushort)idCode; // Clear any existing configuration frame m_configurationFrame = null; // Set auto-start parsing sequence for scan state m_autoStartParsingSequenceForScan = scanParams.AutoStartParsingSequenceForScan; // Inform user of temporary loss of command access ShowUpdateMessage($"{Tab2}Requesting device configuration..."); // Make sure the wait handles are not set m_configurationWaitHandle.Reset(); m_bytesReceivedWaitHandle.Reset(); // Start the frame parser - this will attempt connection m_frameParser.Start(); // Wait for any bytes received within configured response timeout if (!m_bytesReceivedWaitHandle.Wait(responseTimeout, cancellationToken)) { ShowUpdateMessage($"{Tab2}Timed-out waiting for device response."); } else { // Wait to receive the configuration frame if (!m_configurationWaitHandle.Wait(configFrameTimeout, cancellationToken)) { ShowUpdateMessage($"{Tab2}Timed-out waiting to receive remote device configuration."); } } // Terminate connection to device m_frameParser.Stop(); if (m_configurationFrame is null) { m_configurationFrame = new ConfigurationErrorFrame(); ShowUpdateMessage($"{Tab2}Failed to retrieve remote device configuration."); } return(m_configurationFrame); } catch (Exception ex) { ShowUpdateMessage($"{Tab2}ERROR: Failed to request configuration due to exception: {ex.Message}"); } return(new ConfigurationErrorFrame()); }
private void SavePhasorMeasurement(SignalType signalType, Device device, IPhasorDefinition phasorDefinition, char phase, int index, int baseKV, TableOperations <Measurement> measurementTable, ScanParameters scanParams) { string signalReference = $"{device.Acronym}-{signalType.Suffix}{index}"; // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist Measurement measurement = measurementTable.QueryMeasurement(signalReference); string pointTag = scanParams.CreatePhasorPointTag(device.Acronym, signalType.Acronym, phasorDefinition.Label, phase.ToString(), index, baseKV); measurement.DeviceID = device.ID; measurement.PointTag = pointTag; measurement.Description = $"{device.Acronym} {phasorDefinition.Label} {signalType.Name}"; measurement.PhasorSourceIndex = index; measurement.SignalReference = signalReference; measurement.SignalTypeID = signalType.ID; measurement.Internal = true; measurement.Enabled = true; measurementTable.AddNewOrUpdateMeasurement(measurement); }
private void SaveDevicePhasors(IConfigurationCell cell, Device device, TableOperations <Measurement> measurementTable, ScanParameters scanParams) { bool phaseMatchExact(string phaseLabel, string[] phaseMatches) => phaseMatches.Any(match => phaseLabel.Equals(match, StringComparison.Ordinal)); bool phaseEndsWith(string phaseLabel, string[] phaseMatches, bool ignoreCase) => phaseMatches.Any(match => phaseLabel.EndsWith(match, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); bool phaseStartsWith(string phaseLabel, string[] phaseMatches, bool ignoreCase) => phaseMatches.Any(match => phaseLabel.StartsWith(match, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); bool phaseContains(string phaseLabel, string[] phaseMatches, bool ignoreCase) => phaseMatches.Any(match => phaseLabel.IndexOf(match, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) > -1); bool phaseMatchHighConfidence(string phaseLabel, string[] containsMatches, string[] endsWithMatches) { if (phaseEndsWith(phaseLabel, containsMatches, true)) { return(true); } if (phaseStartsWith(phaseLabel, containsMatches, true)) { return(true); } foreach (string match in containsMatches.Concat(endsWithMatches)) { string[] variations = { $" {match}", $"_{match}", $"-{match}", $".{match}" }; if (phaseEndsWith(phaseLabel, variations, false)) { return(true); } } foreach (string match in containsMatches) { string[] variations = { $" {match} ", $"_{match}_", $"-{match}-", $"-{match}_", $"_{match}-", $".{match}." }; if (phaseContains(phaseLabel, variations, false)) { return(true); } } return(false); } char guessPhase(char phase, string phasorLabel) { if (phaseMatchExact(phasorLabel, new[] { "V1PM", "I1PM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "V1", "VP", "I1", "IP", "VSEQ1", "ISEQ1" }, new[] { "POS", "V1PM", "I1PM", "PS", "PSV", "PSI" }) || phaseEndsWith(phasorLabel, new[] { "+SV", "+SI", "+V", "+I" }, true)) { return('+'); } if (phaseMatchExact(phasorLabel, new[] { "V0PM", "I0PM", "VZPM", "IZPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "V0", "I0", "VSEQ0", "ISEQ0" }, new[] { "ZERO", "ZPV", "ZPI", "VSPM", "V0PM", "I0PM", "VZPM", "IZPM", "ZS", "ZSV", "ZSI" }) || phaseEndsWith(phasorLabel, new[] { "0SV", "0SI" }, true)) { return('0'); } if (phaseMatchExact(phasorLabel, new[] { "VAPM", "IAPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VA", "IA" }, new[] { "APV", "API", "VAPM", "IAPM", "AV", "AI" })) { return('A'); } if (phaseMatchExact(phasorLabel, new[] { "VBPM", "IBPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VB", "IB" }, new[] { "BPV", "BPI", "VBPM", "IBPM", "BV", "BI" })) { return('B'); } if (phaseMatchExact(phasorLabel, new[] { "VCPM", "ICPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VC", "IC" }, new[] { "CPV", "CPI", "VCPM", "ICPM", "CV", "CI" })) { return('C'); } if (phaseMatchExact(phasorLabel, new[] { "VNPM", "INPM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "VN", "IN" }, new[] { "NEUT", "NPV", "NPI", "VNPM", "INPM", "NV", "NI" })) { return('N'); } if (phaseMatchExact(phasorLabel, new[] { "V2PM", "I2PM" }) || phaseMatchHighConfidence(phasorLabel, new[] { "V2", "I2", "VSEQ2", "ISEQ2" }, new[] { "NEG", "-SV", "-SI", "V2PM", "I2PM", "NS", "NSV", "NSI" })) { return('-'); } return(phase); } int guessBaseKV(int baseKV, string phasorLabel, string deviceLabel) { // Check phasor label before device foreach (string voltageLevel in s_commonVoltageLevels) { if (phasorLabel.IndexOf(voltageLevel, StringComparison.Ordinal) > -1) { return(int.Parse(voltageLevel)); } } foreach (string voltageLevel in s_commonVoltageLevels) { if (deviceLabel.IndexOf(voltageLevel, StringComparison.Ordinal) > -1) { return(int.Parse(voltageLevel)); } } return(baseKV); } AdoDataConnection connection = scanParams.Connection; TableOperations <Phasor> phasorTable = new(connection); // Get phasor signal types SignalType iphmSignalType = m_phasorSignalTypes["IPHM"]; SignalType iphaSignalType = m_phasorSignalTypes["IPHA"]; SignalType vphmSignalType = m_phasorSignalTypes["VPHM"]; SignalType vphaSignalType = m_phasorSignalTypes["VPHA"]; Phasor[] phasors = phasorTable.QueryPhasorsForDevice(device.ID).ToArray(); bool dropAndAdd = phasors.Length != cell.PhasorDefinitions.Count; if (!dropAndAdd) { // Also do add operation if phasor source index records are not sequential if (phasors.Where((phasor, index) => phasor.SourceIndex != index + 1).Any()) { dropAndAdd = true; } } if (dropAndAdd) { if (cell.PhasorDefinitions.Count > 0) { connection.DeletePhasorsForDevice(device.ID); } foreach (IPhasorDefinition phasorDefinition in cell.PhasorDefinitions) { bool isVoltage = phasorDefinition.PhasorType == PhasorType.Voltage; Phasor phasor = phasorTable.NewPhasor(); phasor.DeviceID = device.ID; phasor.Label = phasorDefinition.Label; phasor.Type = isVoltage ? 'V' : 'I'; phasor.Phase = guessPhase('+', phasor.Label); phasor.BaseKV = guessBaseKV(500, phasor.Label, string.IsNullOrWhiteSpace(device.Name) ? device.Acronym ?? "" : device.Name); phasor.DestinationPhasorID = null; phasor.SourceIndex = phasorDefinition.Index + 1; phasorTable.AddNewPhasor(phasor); SavePhasorMeasurement(isVoltage ? vphmSignalType : iphmSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams); SavePhasorMeasurement(isVoltage ? vphaSignalType : iphaSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams); } } else { foreach (IPhasorDefinition phasorDefinition in cell.PhasorDefinitions) { bool isVoltage = phasorDefinition.PhasorType == PhasorType.Voltage; Phasor phasor = phasorTable.QueryPhasorForDevice(device.ID, phasorDefinition.Index + 1); phasor.DeviceID = device.ID; phasor.Label = phasorDefinition.Label; phasor.Type = isVoltage ? 'V' : 'I'; phasorTable.AddNewPhasor(phasor); SavePhasorMeasurement(isVoltage ? vphmSignalType : iphmSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams); SavePhasorMeasurement(isVoltage ? vphaSignalType : iphaSignalType, device, phasorDefinition, phasor.Phase, phasor.SourceIndex, phasor.BaseKV, measurementTable, scanParams); } } }
private void SaveFixedMeasurement(SignalType signalType, Device device, TableOperations <Measurement> measurementTable, ScanParameters scanParams, string label = null) { string signalReference = $"{device.Acronym}-{signalType.Suffix}"; // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist Measurement measurement = measurementTable.QueryMeasurement(signalReference); string pointTag = scanParams.CreatePointTag(device.Acronym, signalType.Acronym); measurement.DeviceID = device.ID; measurement.PointTag = pointTag; measurement.Description = $"{device.Acronym} {signalType.Name}{(string.IsNullOrWhiteSpace(label) ? "" : " - " + label)}"; measurement.SignalReference = signalReference; measurement.SignalTypeID = signalType.ID; measurement.Internal = true; measurement.Enabled = true; measurementTable.AddNewOrUpdateMeasurement(measurement); }
private void SaveDeviceRecords(IConfigurationFrame configFrame, Device device, ScanParameters scanParams) { AdoDataConnection connection = scanParams.Connection; TableOperations <Measurement> measurementTable = new(connection); IConfigurationCell cell = configFrame.Cells[0]; // Add frequency SaveFixedMeasurement(m_deviceSignalTypes["FREQ"], device, measurementTable, scanParams, cell.FrequencyDefinition.Label); // Add dF/dt SaveFixedMeasurement(m_deviceSignalTypes["DFDT"], device, measurementTable, scanParams); // Add status flags SaveFixedMeasurement(m_deviceSignalTypes["FLAG"], device, measurementTable, scanParams); // Add analogs SignalType analogSignalType = m_deviceSignalTypes["ALOG"]; for (int i = 0; i < cell.AnalogDefinitions.Count; i++) { int index = i + 1; IAnalogDefinition analogDefinition = cell.AnalogDefinitions[i]; string signalReference = $"{device.Acronym}-{analogSignalType.Suffix}{index}"; // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist Measurement measurement = measurementTable.QueryMeasurement(signalReference); string pointTag = scanParams.CreateIndexedPointTag(device.Acronym, analogSignalType.Acronym, index); measurement.DeviceID = device.ID; measurement.PointTag = pointTag; measurement.AlternateTag = analogDefinition.Label; measurement.Description = $"{device.Acronym} Analog Value {index} {analogDefinition.AnalogType}: {analogDefinition.Label}"; measurement.SignalReference = signalReference; measurement.SignalTypeID = analogSignalType.ID; measurement.Internal = true; measurement.Enabled = true; measurementTable.AddNewOrUpdateMeasurement(measurement); } // Add digitals SignalType digitalSignalType = m_deviceSignalTypes["DIGI"]; for (int i = 0; i < cell.DigitalDefinitions.Count; i++) { int index = i + 1; IDigitalDefinition digitialDefinition = cell.DigitalDefinitions[i]; string signalReference = $"{device.Acronym}-{digitalSignalType.Suffix}{index}"; // Query existing measurement record for specified signal reference - function will create a new blank measurement record if one does not exist Measurement measurement = measurementTable.QueryMeasurement(signalReference); string pointTag = scanParams.CreateIndexedPointTag(device.Acronym, digitalSignalType.Acronym, index); measurement.DeviceID = device.ID; measurement.PointTag = pointTag; measurement.AlternateTag = digitialDefinition.Label; measurement.Description = $"{device.Acronym} Digital Value {index}: {digitialDefinition.Label}"; measurement.SignalReference = signalReference; measurement.SignalTypeID = digitalSignalType.ID; measurement.Internal = true; measurement.Enabled = true; measurementTable.AddNewOrUpdateMeasurement(measurement); } // Add phasors SaveDevicePhasors(cell, device, measurementTable, scanParams); }
private void SaveDeviceConnection(IConfigurationFrame configFrame, string connectionString, ushort comPort, ushort idCode, ScanParameters scanParams) { TableOperations <Device> deviceTable = scanParams.DeviceTable; Guid nodeID = scanParams.NodeID; ShowUpdateMessage($"{Tab2}Saving device connection..."); // Query existing device record, creating new one if not found Device device = scanParams.Devices.FindDeviceByComPort(comPort) ?? deviceTable.NewDevice(); bool skipDisableRealTimeData; // Handle connection string parameters that are fields in the device table (skipDisableRealTimeData, connectionString) = ExtractFieldAssignedSkipDisableRealTimeData(connectionString); IConfigurationCell deviceConfig = configFrame.Cells[0]; string deviceAcronym = deviceConfig.IDLabel; string deviceName = null; if (string.IsNullOrWhiteSpace(deviceAcronym) && !string.IsNullOrWhiteSpace(deviceConfig.StationName)) { deviceAcronym = GetCleanAcronym(deviceConfig.StationName.ToUpperInvariant().Replace(" ", "_")); } else { throw new InvalidOperationException("Unable to get station name or ID label from device configuration frame"); } if (!string.IsNullOrWhiteSpace(deviceConfig.StationName)) { deviceName = deviceConfig.StationName; } device.NodeID = nodeID; device.Acronym = deviceAcronym; device.Name = deviceName ?? deviceAcronym; device.ProtocolID = scanParams.IeeeC37_118ProtocolID; device.FramesPerSecond = configFrame.FrameRate; device.AccessID = idCode; device.IsConcentrator = false; device.ConnectionString = connectionString; device.AutoStartDataParsingSequence = true; device.SkipDisableRealTimeData = skipDisableRealTimeData; device.Enabled = true; // Check if this is a new device or an edit to an existing one if (device.ID == 0) { // Add new device record deviceTable.AddNewDevice(device); // Get newly added device with auto-incremented ID Device newDevice = deviceTable.QueryDevice(device.Acronym); // Save associated device records SaveDeviceRecords(configFrame, newDevice, scanParams); } else { // Update existing device record deviceTable.UpdateDevice(device); // Save associated device records SaveDeviceRecords(configFrame, device, scanParams); } }
private bool SaveDeviceConfiguration(IConfigurationFrame configFrame, ushort comPort, ushort idCode, ScanParameters scanParams) { try { AdoDataConnection connection = scanParams.Connection; TableOperations <SignalType> signalTypeTable = new(connection); string configConnectionMode = scanParams.ControllingConnection ? ControllingConnectionString : ListeningConnectionString; string connectionString = string.Format(ConnectionStringTemplate, comPort, Settings.BaudRate, Settings.Parity, Settings.StopBits, Settings.DataBits, Settings.DtrEnable, Settings.RtsEnable, configConnectionMode); ShowUpdateMessage($"{Tab2}Saving \"{configFrame.Cells[0].StationName}\" configuration received on COM{comPort} with ID code {idCode}..."); m_deviceSignalTypes ??= signalTypeTable.LoadSignalTypes("PMU").ToDictionary(key => key.Acronym, StringComparer.OrdinalIgnoreCase); m_phasorSignalTypes ??= signalTypeTable.LoadSignalTypes("Phasor").ToDictionary(key => key.Acronym, StringComparer.OrdinalIgnoreCase); SaveDeviceConnection(configFrame, connectionString, comPort, idCode, scanParams); return(true); } catch (Exception ex) { ShowUpdateMessage($"{Tab2}ERROR: Failed while saving \"{configFrame.Cells[0].StationName}\" configuration: {ex.Message}"); m_log.Publish(MessageLevel.Error, nameof(AutoConfigPortScanner), exception: ex); return(false); } }