/// <summary> /// Creates a new <see cref="ConfigurationCreator"/>. /// </summary> public ConfigurationCreator() { InitializeComponent(); m_configurationFrame = new ConfigurationFrame((ushort)0, (Ticks)0, (ushort)30); listBoxDevices.ItemsSource = m_configurationFrame.Cells; listBoxDevices.SelectedValuePath = "@IDCode"; }
/// <summary> /// Creates a new <see cref="ConfigurationCell"/> from specified parameters. /// </summary> /// <param name="parent">The reference to parent <see cref="ConfigurationFrame"/> of this <see cref="ConfigurationCell"/>.</param> /// <param name="idCode">The numeric ID code for this <see cref="ConfigurationCell"/>.</param> public ConfigurationCell(ConfigurationFrame parent, ushort idCode) : base(parent, idCode, int.MaxValue, int.MaxValue, int.MaxValue) { // Create a cached signal reference dictionary for generated signal references m_generatedSignalReferenceCache = new string[Enum.GetValues(typeof(SignalKind)).Length][]; m_analogDataFormat = DataFormat.FloatingPoint; m_frequencyDataFormat = DataFormat.FloatingPoint; m_phasorDataFormat = DataFormat.FloatingPoint; m_phasorCoordinateFormat = CoordinateFormat.Polar; }
/// <summary> /// Creates a new protocol specific <see cref="IConfigurationFrame"/> based on provided protocol independent <paramref name="baseConfigurationFrame"/>. /// </summary> /// <param name="baseConfigurationFrame">Protocol independent <see cref="ConfigurationFrame"/>.</param> /// <returns>A new protocol specific <see cref="IConfigurationFrame"/>.</returns> /// <remarks> /// Derived classes should notify consumers of change in configuration if system is active when /// new configuration frame is created if outgoing protocol allows such a notfication. /// </remarks> protected abstract IConfigurationFrame CreateNewConfigurationFrame(ConfigurationFrame baseConfigurationFrame);
public void UpdateConfiguration() { const int LabelLength = 16; PhasorType type; AnalogType analogType; char phase; string label, scale; uint scalingValue; int order; // Define a protocol independent configuration frame m_baseConfigurationFrame = new ConfigurationFrame(m_idCode, DateTime.UtcNow.Ticks, (ushort)base.FramesPerSecond); // Define configuration cells (i.e., PMU's that will appear in outgoing data stream) foreach (DataRow deviceRow in DataSource.Tables["OutputStreamDevices"].Select(string.Format("ParentID={0}", ID), "LoadOrder")) { try { // Get device ID and ID code int deviceID = int.Parse(deviceRow["ID"].ToString()); ushort idCode = ushort.Parse(deviceRow["IDCode"].ToString()); // If number was never assigned or is invalid, we fall back on unique database record ID if (idCode == 0) idCode = unchecked((ushort)deviceID); // Create a new configuration cell ConfigurationCell cell = new ConfigurationCell(m_baseConfigurationFrame, idCode); // Assign user selected data and coordinate formats, derived classes can change string formatString; formatString = deviceRow["PhasorDataFormat"].ToNonNullString(m_dataFormat.ToString()); cell.PhasorDataFormat = (DataFormat)Enum.Parse(typeof(DataFormat), string.IsNullOrEmpty(formatString) ? m_dataFormat.ToString() : formatString, true); formatString = deviceRow["FrequencyDataFormat"].ToNonNullString(m_dataFormat.ToString()); cell.FrequencyDataFormat = (DataFormat)Enum.Parse(typeof(DataFormat), string.IsNullOrEmpty(formatString) ? m_dataFormat.ToString() : formatString, true); formatString = deviceRow["AnalogDataFormat"].ToNonNullString(m_dataFormat.ToString()); cell.AnalogDataFormat = (DataFormat)Enum.Parse(typeof(DataFormat), string.IsNullOrEmpty(formatString) ? m_dataFormat.ToString() : formatString, true); formatString = deviceRow["CoordinateFormat"].ToNonNullString(m_coordinateFormat.ToString()); cell.PhasorCoordinateFormat = (CoordinateFormat)Enum.Parse(typeof(CoordinateFormat), string.IsNullOrEmpty(formatString) ? m_coordinateFormat.ToString() : formatString, true); // Assign device identification labels cell.IDLabel = deviceRow["Name"].ToString().TruncateRight(cell.IDLabelLength).Trim(); label = deviceRow["Acronym"].ToString().TruncateRight(cell.MaximumStationNameLength).Trim(); // Station name is serialized to configuration frame cell.StationName = label; // Define all the phasors configured for this device foreach (DataRow phasorRow in DataSource.Tables["OutputStreamDevicePhasors"].Select(string.Format("OutputStreamDeviceID={0}", deviceID), "LoadOrder")) { order = int.Parse(phasorRow["LoadOrder"].ToNonNullString("0")); label = phasorRow["Label"].ToNonNullString("Phasor " + order).Trim().TruncateRight(LabelLength); type = phasorRow["Type"].ToNonNullString("V").Trim().ToUpper().StartsWith("V") ? PhasorType.Voltage : PhasorType.Current; phase = phasorRow["Phase"].ToNonNullString("+").Trim().ToUpper()[0]; scale = phasorRow["ScalingValue"].ToNonNullString("0"); if (m_replaceWithSpaceChar != Char.MinValue) label = label.Replace(m_replaceWithSpaceChar, ' '); // Scale can be defined as a negative value in database, so check both formatting styles if (!uint.TryParse(scale, out scalingValue)) scalingValue = unchecked((uint)int.Parse(scale)); // Choose stream defined default value if no scaling value was defined if (scalingValue == 0) scalingValue = (type == PhasorType.Voltage ? m_voltageScalingValue : m_currentScalingValue); cell.PhasorDefinitions.Add(new PhasorDefinition( cell, GeneratePhasorLabel(label, phase, type), scalingValue, type, null)); } // Add frequency definition label = string.Format("{0} Freq", cell.IDLabel.TruncateRight(LabelLength - 5)).Trim(); cell.FrequencyDefinition = new FrequencyDefinition(cell, label); // Optionally define all the analogs configured for this device if (DataSource.Tables.Contains("OutputStreamDeviceAnalogs")) { foreach (DataRow analogRow in DataSource.Tables["OutputStreamDeviceAnalogs"].Select(string.Format("OutputStreamDeviceID={0}", deviceID), "LoadOrder")) { order = int.Parse(analogRow["LoadOrder"].ToNonNullString("0")); label = analogRow["Label"].ToNonNullString("Analog " + order).Trim().TruncateRight(LabelLength); analogType = (AnalogType)int.Parse(analogRow["Type"].ToNonNullString("0")); scale = analogRow["ScalingValue"].ToNonNullString("0"); if (m_replaceWithSpaceChar != Char.MinValue) label = label.Replace(m_replaceWithSpaceChar, ' '); // Scale can be defined as a negative value in database, so check both formatting styles if (!uint.TryParse(scale, out scalingValue)) scalingValue = unchecked((uint)int.Parse(scale)); cell.AnalogDefinitions.Add(new AnalogDefinition( cell, label, scalingValue == 0 ? m_analogScalingValue : scalingValue, analogType)); } } // Optionally define all the digitals configured for this device if (DataSource.Tables.Contains("OutputStreamDeviceDigitals")) { foreach (DataRow digitalRow in DataSource.Tables["OutputStreamDeviceDigitals"].Select(string.Format("OutputStreamDeviceID={0}", deviceID), "LoadOrder")) { order = int.Parse(digitalRow["LoadOrder"].ToNonNullString("0")); scale = digitalRow["MaskValue"].ToNonNullString("0"); // IEEE C37.118 digital labels are defined with all 16-labels (one for each bit) in one large formatted string label = digitalRow["Label"].ToNonNullString("Digital " + order).Trim().TruncateRight(LabelLength * 16); if (m_replaceWithSpaceChar != Char.MinValue) label = label.Replace(m_replaceWithSpaceChar, ' '); // Mask can be defined as a negative value in database, so check both formatting styles if (!uint.TryParse(scale, out scalingValue)) scalingValue = unchecked((uint)int.Parse(scale)); cell.DigitalDefinitions.Add(new DigitalDefinition( cell, label, scalingValue == 0 ? m_digitalMaskValue : scalingValue)); } } m_baseConfigurationFrame.Cells.Add(cell); } catch (Exception ex) { OnProcessException(new InvalidOperationException(string.Format("Failed to define output stream device \"{0}\" due to exception: {1}", deviceRow["Acronym"].ToString().Trim(), ex.Message), ex)); } } OnStatusMessage("Defined {0} output stream devices...", m_baseConfigurationFrame.Cells.Count); // Clear any existing signal references m_signalReferences.Clear(); Dictionary<string, int> signalCellIndexes = new Dictionary<string, int>(); SignalReference signal; SignalReference[] signals; MeasurementKey measurementKey; bool foundQualityFlagsMeasurement = false; bool isQualityFlagsMeasurement; // Define measurement to signals cross reference dictionary foreach (DataRow measurementRow in DataSource.Tables["OutputStreamMeasurements"].Select(string.Format("AdapterID={0}", ID))) { isQualityFlagsMeasurement = false; try { // Create a new signal reference signal = new SignalReference(measurementRow["SignalReference"].ToString()); // See if this is the quality flags designation for this output stream if (signal.Kind == SignalKind.Quality) { if (Name.Equals(signal.Acronym, StringComparison.OrdinalIgnoreCase)) { if (foundQualityFlagsMeasurement) throw new Exception("Only one quality flags measurement can be assigned to an output stream - additional quality flags will be ignored."); foundQualityFlagsMeasurement = true; isQualityFlagsMeasurement = true; } else { throw new Exception(string.Format("Unexpected quality flags measurement assignment to \"{0}\". A single quality flags measurement can be assigned to output stream \"{1}\".", signal.Acronym, Name)); } } else { // Lookup cell index by acronym - doing this work upfront will save a huge amount of work during primary measurement sorting if (!signalCellIndexes.TryGetValue(signal.Acronym, out signal.CellIndex)) { // We cache these indices locally to speed up initialization as we'll be // requesting them for the same devices over and over signal.CellIndex = m_baseConfigurationFrame.Cells.IndexOfStationName(signal.Acronym); signalCellIndexes.Add(signal.Acronym, signal.CellIndex); } } // No need to define this measurement for sorting unless it has a destination in the outgoing frame if (signal.CellIndex > -1 || isQualityFlagsMeasurement) { // Get historian field string historian = measurementRow["Historian"].ToNonNullString(); string pointID = measurementRow["PointID"].ToString(); // Define measurement key if (!string.IsNullOrEmpty(historian)) { measurementKey = MeasurementKey.LookUpOrCreate(historian, uint.Parse(pointID)); } else { DataTable activeMeasurements = DataSource.Tables["ActiveMeasurements"]; DataRow[] activeMeasurementRows = new DataRow[0]; object activeMeasurementSignalID = null; object activeMeasurementID = null; // OPTIMIZE: This select query will be slow on very large ActiveMeasurement implementations, consider optimization. if ((object)activeMeasurements != null) activeMeasurementRows = activeMeasurements.Select(string.Format("ID LIKE '*:{0}'", pointID)); if (activeMeasurementRows.Length == 1) { activeMeasurementSignalID = activeMeasurementRows[0]["SignalID"]; activeMeasurementID = activeMeasurementRows[0]["ID"]; } // If we still can't find the measurement key, now is the time to give up if ((object)activeMeasurementSignalID == null && (object)activeMeasurementID == null) throw new Exception(string.Format("Cannot find measurement key for measurement with pointID {0}", pointID)); measurementKey = MeasurementKey.LookUpOrCreate(Guid.Parse(activeMeasurementRows[0]["SignalID"].ToString()), activeMeasurementID.ToString()); } // It is possible, but not as common, that a single measurement will have multiple destinations // within an outgoing data stream frame, hence the following signals = m_signalReferences.GetOrAdd(measurementKey, null as SignalReference[]); if ((object)signals == null) { // Add new signal to new collection signals = new SignalReference[1]; signals[0] = signal; } else { // Add a new signal to existing collection List<SignalReference> signalList = new List<SignalReference>(signals); signalList.Add(signal); signals = signalList.ToArray(); } m_signalReferences[measurementKey] = signals; } } catch (Exception ex) { OnProcessException(new InvalidOperationException(string.Format("Failed to associate measurement key to signal reference \"{0}\" due to exception: {1}", measurementRow["SignalReference"].ToNonNullString(), ex.Message), ex)); } } // Assign action adapter input measurement keys - this assigns the expected measurements per frame needed // by the concentration engine for preemptive publication InputMeasurementKeys = m_signalReferences.Keys.ToArray(); // Allow for spaces in output stream device names if a replacement character has been defined for spaces if (m_replaceWithSpaceChar != Char.MinValue) { foreach (IConfigurationCell cell in m_baseConfigurationFrame.Cells) { cell.StationName = cell.StationName.Replace(m_replaceWithSpaceChar, ' '); } } // Create a new protocol specific configuration frame m_configurationFrame = CreateNewConfigurationFrame(m_baseConfigurationFrame); // Cache new protocol specific configuration frame CacheConfigurationFrame(m_configurationFrame, Name); }
// Static Methods /// <summary> /// Creates a new IEEE C37.118 <see cref="ConfigurationFrame2"/> based on provided protocol independent <paramref name="baseConfigurationFrame"/>. /// </summary> /// <param name="baseConfigurationFrame">Protocol independent <see cref="GSF.PhasorProtocols.Anonymous.ConfigurationFrame"/>.</param> /// <param name="timeBase">Timebase to use for fraction second resolution.</param> /// <param name="nominalFrequency">The nominal <see cref="LineFrequency"/> to use for the new <see cref="ConfigurationFrame2"/></param>. /// <returns>A new IEEE C37.118 <see cref="ConfigurationFrame2"/>.</returns> public static ConfigurationFrame2 CreateConfigurationFrame(ConfigurationFrame baseConfigurationFrame, uint timeBase, LineFrequency nominalFrequency) { ConfigurationCell newCell; uint maskValue; // Create a new IEEE C37.118 configuration frame 2 using base configuration ConfigurationFrame2 configurationFrame = new ConfigurationFrame2(timeBase, baseConfigurationFrame.IDCode, DateTime.UtcNow.Ticks, baseConfigurationFrame.FrameRate); foreach (GSF.PhasorProtocols.Anonymous.ConfigurationCell baseCell in baseConfigurationFrame.Cells) { // Create a new IEEE C37.118 configuration cell (i.e., a PMU configuration) newCell = new ConfigurationCell(configurationFrame, baseCell.IDCode, nominalFrequency); // Update other cell level attributes newCell.StationName = baseCell.StationName; newCell.IDLabel = baseCell.IDLabel; newCell.PhasorDataFormat = baseCell.PhasorDataFormat; newCell.PhasorCoordinateFormat = baseCell.PhasorCoordinateFormat; newCell.FrequencyDataFormat = baseCell.FrequencyDataFormat; newCell.AnalogDataFormat = baseCell.AnalogDataFormat; // Add phasor definitions foreach (IPhasorDefinition phasorDefinition in baseCell.PhasorDefinitions) { newCell.PhasorDefinitions.Add(new PhasorDefinition(newCell, phasorDefinition.Label, phasorDefinition.ScalingValue, phasorDefinition.Offset, phasorDefinition.PhasorType, null)); } // Add frequency definition newCell.FrequencyDefinition = new FrequencyDefinition(newCell, baseCell.FrequencyDefinition.Label); // Add analog definitions foreach (IAnalogDefinition analogDefinition in baseCell.AnalogDefinitions) { newCell.AnalogDefinitions.Add(new AnalogDefinition(newCell, analogDefinition.Label, analogDefinition.ScalingValue, analogDefinition.Offset, analogDefinition.AnalogType)); } // Add digital definitions foreach (IDigitalDefinition digitalDefinition in baseCell.DigitalDefinitions) { // Attempt to derive user defined mask value if available DigitalDefinition anonymousDigitalDefinition = digitalDefinition as DigitalDefinition; if (anonymousDigitalDefinition != null) maskValue = anonymousDigitalDefinition.MaskValue; else maskValue = 0U; newCell.DigitalDefinitions.Add(new GSF.PhasorProtocols.IEEEC37_118.DigitalDefinition(newCell, digitalDefinition.Label, maskValue.LowWord(), maskValue.HighWord())); } // Add new PMU configuration (cell) to protocol specific configuration frame configurationFrame.Cells.Add(newCell); } return configurationFrame; }
/// <summary> /// Creates a new IEEE C37.118 specific <see cref="IConfigurationFrame"/> based on provided protocol independent <paramref name="baseConfigurationFrame"/>. /// </summary> /// <param name="baseConfigurationFrame">Protocol independent <see cref="GSF.PhasorProtocols.Anonymous.ConfigurationFrame"/>.</param> /// <returns>A new IEEE C37.118 specific <see cref="IConfigurationFrame"/>.</returns> protected override IConfigurationFrame CreateNewConfigurationFrame(ConfigurationFrame baseConfigurationFrame) { // Create a new IEEE C37.118 configuration frame 2 using base configuration ConfigurationFrame2 configurationFrame = CreateConfigurationFrame(baseConfigurationFrame, m_timeBase, base.NominalFrequency); // After system has started any subsequent changes in configuration get indicated in the outgoing data stream bool configurationChanged = (object)m_configurationFrame != null; // Cache new IEEE C7.118 for later use Interlocked.Exchange(ref m_configurationFrame, configurationFrame); if (configurationChanged) { // Start adding configuration changed notification flag to data cells m_configurationChanged = true; m_notificationStartTime = DateTime.UtcNow.Ticks; } return configurationFrame; }