/// <summary> /// Creates a new <see cref="FrequencyDefinition"/> from specified parameters. /// </summary> /// <param name="parent">The <see cref="ConfigurationCell"/> parent of this <see cref="FrequencyDefinition"/>.</param> /// <param name="label">The label of this <see cref="FrequencyDefinition"/>.</param> public FrequencyDefinition(ConfigurationCell parent, string label) : base(parent, label, 1000, 100, 0.0D) { }
/// <summary> /// Creates a new <see cref="AnalogDefinition"/> from specified parameters. /// </summary> /// <param name="parent">The <see cref="ConfigurationCell"/> parent of this <see cref="AnalogDefinition"/>.</param> /// <param name="label">The label of this <see cref="AnalogDefinition"/>.</param> /// <param name="scale">The integer scaling value of this <see cref="AnalogDefinitionBase"/>.</param> /// <param name="type">The <see cref="AnalogType"/> of this <see cref="AnalogDefinition"/>.</param> public AnalogDefinition(ConfigurationCell parent, string label, uint scale, AnalogType type) : base(parent, label, scale, 0.0, type) { }
// Load device list for this mapper connection private void LoadInputDevices() { ConfigurationCell definedDevice; string deviceName; if ((object)m_definedDevices != null) { // Unregister each existing device from the statistics engine foreach (ConfigurationCell device in DefinedDevices) StatisticsEngine.Unregister(device); } m_definedDevices = new ConcurrentDictionary<ushort, DeviceStatisticsHelper<ConfigurationCell>>(); if (m_isConcentrator) { StringBuilder deviceStatus = new StringBuilder(); bool devicedAdded; int index = 0; deviceStatus.AppendLine(); deviceStatus.AppendLine(); deviceStatus.Append("Loading expected concentrator device list..."); deviceStatus.AppendLine(); deviceStatus.AppendLine(); // Making a connection to a concentrator that can support multiple devices foreach (DataRow row in DataSource.Tables["InputStreamDevices"].Select($"ParentID={SharedMappingID}")) { // Create new configuration cell parsing needed ID code and label from input stream configuration definedDevice = new ConfigurationCell(ushort.Parse(row["AccessID"].ToString())); deviceName = row["Acronym"].ToNonNullString("[undefined]").Trim(); definedDevice.StationName = row["Name"].ToNonNullString(deviceName).Trim().TruncateRight(definedDevice.MaximumStationNameLength); definedDevice.IDLabel = deviceName.TruncateRight(definedDevice.IDLabelLength); definedDevice.Tag = uint.Parse(row["ID"].ToString()); definedDevice.Source = this; devicedAdded = false; if (m_forceLabelMapping) { // When forcing label mapping we always try to use label for unique lookup if ((object)m_labelDefinedDevices == null) m_labelDefinedDevices = new ConcurrentDictionary<string, DeviceStatisticsHelper<ConfigurationCell>>(StringComparer.OrdinalIgnoreCase); // See if label already exists in this collection if (m_labelDefinedDevices.ContainsKey(definedDevice.StationName)) { // For devices that do not have unique labels when forcing label mapping, we fall back on its ID code for unique lookup if (m_definedDevices.ContainsKey(definedDevice.IDCode)) { OnProcessException(MessageLevel.Error, new InvalidOperationException($"ERROR: Device ID \"{definedDevice.IDCode}\", labeled \"{definedDevice.StationName}\", was not unique in the {Name} input stream. Data from devices that are not distinctly defined by ID code or label will not be correctly parsed until uniquely identified."), flags: MessageFlags.UsageIssue); } else { m_definedDevices.TryAdd(definedDevice.IDCode, new DeviceStatisticsHelper<ConfigurationCell>(definedDevice)); RegisterStatistics(definedDevice, definedDevice.IDLabel, "Device", "PMU"); devicedAdded = true; } } else { m_labelDefinedDevices.TryAdd(definedDevice.StationName, new DeviceStatisticsHelper<ConfigurationCell>(definedDevice)); RegisterStatistics(definedDevice, definedDevice.IDLabel, "Device", "PMU"); devicedAdded = true; } } else { // See if key already exists in this collection if (m_definedDevices.ContainsKey(definedDevice.IDCode)) { // For devices that do not have unique ID codes, we fall back on its label for unique lookup if ((object)m_labelDefinedDevices == null) m_labelDefinedDevices = new ConcurrentDictionary<string, DeviceStatisticsHelper<ConfigurationCell>>(StringComparer.OrdinalIgnoreCase); if (m_labelDefinedDevices.ContainsKey(definedDevice.StationName)) { OnProcessException(MessageLevel.Error, new InvalidOperationException($"Device ID \"{definedDevice.IDCode}\", labeled \"{definedDevice.StationName}\", was not unique in the {Name} input stream. Data from devices that are not distinctly defined by ID code or label will not be correctly parsed until uniquely identified."), flags: MessageFlags.UsageIssue); } else { m_labelDefinedDevices.TryAdd(definedDevice.StationName, new DeviceStatisticsHelper<ConfigurationCell>(definedDevice)); RegisterStatistics(definedDevice, definedDevice.IDLabel, "Device", "PMU"); devicedAdded = true; } } else { m_definedDevices.TryAdd(definedDevice.IDCode, new DeviceStatisticsHelper<ConfigurationCell>(definedDevice)); RegisterStatistics(definedDevice, definedDevice.IDLabel, "Device", "PMU"); devicedAdded = true; } } if (devicedAdded) { // Create status display string for expected device deviceStatus.Append(" Device "); deviceStatus.Append((index++).ToString("00")); deviceStatus.Append(": "); deviceStatus.Append(definedDevice.StationName); deviceStatus.Append(" ("); deviceStatus.Append(definedDevice.IDCode); deviceStatus.Append(')'); deviceStatus.AppendLine(); } } OnStatusMessage(MessageLevel.Info, deviceStatus.ToString()); if ((object)m_labelDefinedDevices != null) { if (m_forceLabelMapping) OnStatusMessage(MessageLevel.Info, $"{Name} has {m_labelDefinedDevices.Count:N0} defined input devices that are using the device label for identification since connection has been forced to use label mapping. This is not the optimal configuration.", flags: MessageFlags.UsageIssue); else OnStatusMessage(MessageLevel.Info, $"{Name} has {m_labelDefinedDevices.Count:N0} defined input devices that do not have unique ID codes (i.e., the AccessID), as a result system will use the device label for identification. This is not the optimal configuration.", flags: MessageFlags.UsageIssue); } } else { // Making a connection to a single device definedDevice = new ConfigurationCell(m_accessID); // Used shared mapping name for single device connection if defined - this causes measurement mappings to be associated // with alternate device by caching signal references associated with shared mapping acronym if (string.IsNullOrWhiteSpace(SharedMapping)) deviceName = Name.ToNonNullString("[undefined]").Trim(); else deviceName = SharedMapping; definedDevice.StationName = deviceName.TruncateRight(definedDevice.MaximumStationNameLength); definedDevice.IDLabel = deviceName.TruncateRight(definedDevice.IDLabelLength); definedDevice.Tag = ID; definedDevice.Source = this; // When forcing label mapping we always try to use label for unique lookup instead of ID code if (m_forceLabelMapping) { if ((object)m_labelDefinedDevices == null) m_labelDefinedDevices = new ConcurrentDictionary<string, DeviceStatisticsHelper<ConfigurationCell>>(StringComparer.OrdinalIgnoreCase); m_labelDefinedDevices.TryAdd(definedDevice.StationName, new DeviceStatisticsHelper<ConfigurationCell>(definedDevice)); RegisterStatistics(definedDevice, definedDevice.IDLabel, "Device", "PMU"); OnStatusMessage(MessageLevel.Info, "Input device is using the device label for identification since connection has been forced to use label mapping. This is not the optimal configuration."); } else { m_definedDevices.TryAdd(definedDevice.IDCode, new DeviceStatisticsHelper<ConfigurationCell>(definedDevice)); RegisterStatistics(definedDevice, definedDevice.IDLabel, "Device", "PMU"); } } }
/// <summary> /// Creates a new <see cref="DigitalDefinition"/> from specified parameters. /// </summary> /// <param name="parent">The <see cref="ConfigurationCell"/> parent of this <see cref="DigitalDefinition"/>.</param> /// <param name="label">The label of this <see cref="DigitalDefinition"/>.</param> /// <param name="maskValue">The value of the digital mask made available in configuration frames.</param> public DigitalDefinition(ConfigurationCell parent, string label, uint maskValue) : base(parent, label) { m_maskValue = maskValue; }
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); }
/// <summary> /// Creates a new <see cref="PhasorDefinition"/> from specified parameters. /// </summary> /// <param name="parent">The <see cref="ConfigurationCell"/> parent of this <see cref="PhasorDefinition"/>.</param> /// <param name="label">The label of this <see cref="PhasorDefinition"/>.</param> /// <param name="scale">The integer scaling value of this <see cref="PhasorDefinition"/>.</param> /// <param name="type">The <see cref="PhasorType"/> of this <see cref="PhasorDefinition"/>.</param> /// <param name="voltageReference">The associated <see cref="IPhasorDefinition"/> that represents the voltage reference (if any).</param> public PhasorDefinition(ConfigurationCell parent, string label, uint scale, PhasorType type, PhasorDefinition voltageReference) : base(parent, label, scale, 0.0D, type, voltageReference) { }
/// <summary> /// Creates a new <see cref="PhasorDefinition"/> from specified parameters. /// </summary> /// <param name="parent">The <see cref="ConfigurationCell"/> parent of this <see cref="PhasorDefinition"/>.</param> /// <param name="label">The label of this <see cref="PhasorDefinition"/>.</param> /// <param name="scale">The integer scaling value of this <see cref="PhasorDefinition"/>.</param> /// <param name="type">The <see cref="PhasorType"/> of this <see cref="PhasorDefinition"/>.</param> /// <param name="voltageReference">The associated <see cref="IPhasorDefinition"/> that represents the voltage reference (if any).</param> /// <param name="originalSourceIndex">The original source phasor index, if applicable.</param> /// <param name="phase">The phase of this <see cref="PhasorDefinition"/>.</param> public PhasorDefinition(ConfigurationCell parent, string label, uint scale, PhasorType type, PhasorDefinition voltageReference, int originalSourceIndex = -1, char phase = '+') : base(parent, label, scale, 0.0D, type, voltageReference) { OriginalSourceIndex = originalSourceIndex; Phase = phase; }
private void CopyDevice(ConfigurationCell sourceDevice) { // Create a new configuration cell to hold copied information ConfigurationCell copiedDevice = new ConfigurationCell(m_configurationFrame, 0); copiedDevice.IDCode = (ushort)m_configurationFrame.Cells.Count; copiedDevice.StationName = "Device " + (copiedDevice.IDCode + 1); // Create equivalent derived phasor definitions foreach (PhasorDefinition sourcePhasor in sourceDevice.PhasorDefinitions) { copiedDevice.PhasorDefinitions.Add(new PhasorDefinition(copiedDevice, sourcePhasor.Label, sourcePhasor.ScalingValue, sourcePhasor.PhasorType, null)); } // Create equivalent derived frequency definition IFrequencyDefinition sourceFrequency = sourceDevice.FrequencyDefinition; if (sourceFrequency != null) copiedDevice.FrequencyDefinition = new FrequencyDefinition(copiedDevice, sourceFrequency.Label); // Create equivalent derived analog definitions (assuming analog type = SinglePointOnWave) foreach (AnalogDefinition sourceAnalog in sourceDevice.AnalogDefinitions) { copiedDevice.AnalogDefinitions.Add(new AnalogDefinition(copiedDevice, sourceAnalog.Label, sourceAnalog.ScalingValue, sourceAnalog.AnalogType)); } // Create equivalent derived digital definitions foreach (DigitalDefinition sourceDigital in sourceDevice.DigitalDefinitions) { copiedDevice.DigitalDefinitions.Add(new DigitalDefinition(copiedDevice, sourceDigital.Label, sourceDigital.MaskValue)); } // Add new copied cell to the list and select it m_configurationFrame.Cells.Add(copiedDevice); listBoxDevices.SelectedIndex = (m_configurationFrame.Cells.Count - 1); }
private void buttonDeviceAdd_Click(object sender, RoutedEventArgs e) { ConfigurationCell device = new ConfigurationCell(m_configurationFrame, 0); device.IDCode = (ushort)m_configurationFrame.Cells.Count; device.StationName = "Device " + (device.IDCode + 1); m_configurationFrame.Cells.Add(device); listBoxDevices.SelectedIndex = (m_configurationFrame.Cells.Count - 1); }