/// <summary> /// Creates a <see cref="MeasurementMetadata"/> /// </summary> /// <param name="key">Gets or sets the primary key of this <see cref="IMeasurement"/>.</param> /// <param name="tagName">Gets or sets the text based tag name of this <see cref="IMeasurement"/>.</param> /// <param name="adder">Defines an offset to add to the <see cref="IMeasurement"/> value.</param> /// <param name="multiplier">Defines a multiplicative offset to apply to the <see cref="IMeasurement"/> value.</param> /// <param name="measurementValueFilter">Gets or sets function used to apply a down-sampling filter over a sequence of <see cref="IMeasurement"/> values.</param> public MeasurementMetadata(MeasurementKey key, string tagName, double adder, double multiplier, MeasurementValueFilterFunction measurementValueFilter) { Key = key; TagName = tagName; Adder = adder; Multiplier = multiplier; MeasurementValueFilter = measurementValueFilter; }
/// <summary> /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters. /// </summary> /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param> /// <param name="value">A string representation of the <see cref="MeasurementKey"/>.</param> /// <param name="key">The measurement key that was created or updated or <see cref="Undefined"/>.</param> /// <returns>True if the measurement key was successfully created or updated, false otherwise.</returns> /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty.</exception> /// <exception cref="ArgumentNullException">Measurement key Source cannot be null.</exception> public static bool TryCreateOrUpdate(Guid signalID, string value, out MeasurementKey key) { try { key = CreateOrUpdate(signalID, value); return(true); } catch { key = Undefined; return(false); } }
/// <summary> /// Initializes the <see cref="PowerCalculator"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; string setting; // Load parameters if (settings.TryGetValue("trackRecentValues", out setting)) m_trackRecentValues = setting.ParseBoolean(); else m_trackRecentValues = true; if (settings.TryGetValue("sampleSize", out setting)) // Data sample size to monitor, in seconds m_sampleSize = int.Parse(setting); else m_sampleSize = 5; // Load needed phase angle and magnitude measurement keys from defined InputMeasurementKeys m_voltageAngle = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.VPHA).FirstOrDefault(); m_voltageMagnitude = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.VPHM).FirstOrDefault(); m_currentAngle = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.IPHA).FirstOrDefault(); m_currentMagnitude = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.IPHM).FirstOrDefault(); if ((object)m_voltageAngle == null || m_voltageAngle.ID == 0) throw new InvalidOperationException("No voltage angle input was defined - one voltage angle input measurement is required for the power calculator."); if ((object)m_voltageMagnitude == null || m_voltageMagnitude.ID == 0) throw new InvalidOperationException("No voltage magnitude input was defined - one voltage magnitude input measurement is required for the power calculator."); if ((object)m_currentAngle == null || m_currentAngle.ID == 0) throw new InvalidOperationException("No current angle input was defined - one current angle input measurement is required for the power calculator."); if ((object)m_currentMagnitude == null || m_currentMagnitude.ID == 0) throw new InvalidOperationException("No current magnitude input measurement was defined - one current magnitude input measurement is required for the power calculator."); // Make sure only these four phasor measurements are used as input (any others will be ignored) InputMeasurementKeys = new[] { m_voltageAngle, m_voltageMagnitude, m_currentAngle, m_currentMagnitude }; // Validate output measurements if (OutputMeasurements.Length < Enum.GetValues(typeof(Output)).Length) throw new InvalidOperationException("Not enough output measurements were specified for the power calculator, expecting measurements for the \"Calculated Power\" and the \"Calculated Reactive Power\" - in this order."); if (m_trackRecentValues) { m_powerSample = new List<double>(); m_reactivePowerSample = new List<double>(); } // Assign a default adapter name to be used if power calculator is loaded as part of automated collection if (string.IsNullOrWhiteSpace(Name)) Name = string.Format("PC!{0}", OutputMeasurements[(int)Output.Power].Key); }
private PIPoint CreateMappedPIPoint(MeasurementKey key) { PIPoint point = null; // Map measurement to PI point try { // Two ways to find points here // 1. if we are running metadata sync from the adapter, look for the signal ID in the EXDESC field // 2. if the pi points are being manually maintained, look for either the point tag or alternate tag in the actual pi point tag Guid signalID = key.SignalID; bool foundPoint = false; if (RunMetadataSync) { // Attempt lookup by EXDESC signal ID point = GetPIPointBySignalID(m_connection.Server, signalID); foundPoint = (object)point != null; } if (!foundPoint) { // Lookup meta-data for current measurement DataRow[] rows = DataSource.Tables["ActiveMeasurements"].Select($"SignalID='{signalID}'"); if (rows.Length > 0) { DataRow measurementRow = rows[0]; string tagName = measurementRow["PointTag"].ToNonNullString().Trim(); // Use alternate tag if one is defined if (!string.IsNullOrWhiteSpace(measurementRow["AlternateTag"].ToString()) && !measurementRow["SignalType"].ToString().Equals("DIGI", StringComparison.OrdinalIgnoreCase)) tagName = measurementRow["AlternateTag"].ToString().Trim(); // Attempt lookup by tag name point = GetPIPoint(m_connection.Server, tagName); if ((object)point == null) { if (!m_refreshingMetadata) OnStatusMessage("WARNING: No PI points found for tag '{0}'. Data will not be archived for '{1}'.", tagName, key); } } } } catch (Exception ex) { OnProcessException(new InvalidOperationException($"Failed to map '{key}' to a PI tag: {ex.Message}", ex)); } // If no point could be mapped, return null connection mapping so key can be removed from tag-map if ((object)point == null) return null; return point; }
// Resets the PI tag to MeasurementKey mapping for loading data into PI by finding PI points that match either the GSFSchema point tag or alternate tag private void EstablishPIPointDictionary(MeasurementKey[] inputMeasurements) { OnStatusMessage("Establishing connection points for mapping..."); List<MeasurementKey> newTags = new List<MeasurementKey>(); if ((object)inputMeasurements != null && inputMeasurements.Length > 0) { foreach (MeasurementKey key in inputMeasurements) { // Add key to dictionary with null value if not defined, actual mapping will happen dynamically as needed if (!m_mappedPIPoints.ContainsKey(key)) m_mappedPIPoints.TryAdd(key, null); newTags.Add(key); } } if (newTags.Count > 0) { // Determine which tags no longer exist PIPoint removedPIPoint; HashSet<MeasurementKey> tagsToRemove = new HashSet<MeasurementKey>(m_mappedPIPoints.Keys); // If there are existing tags that are not part of new updates, these need to be removed tagsToRemove.ExceptWith(newTags); if (tagsToRemove.Count > 0) { foreach (MeasurementKey key in tagsToRemove) m_mappedPIPoints.TryRemove(key, out removedPIPoint); OnStatusMessage("Detected {0:N0} tags that have been removed from OSI-PI output - primary tag-map has been updated...", tagsToRemove.Count); } if (m_mappedPIPoints.Count == 0) OnStatusMessage("WARNING: No PI tags were mapped to measurements - no tag-map exists so no points will be archived."); } else { if (m_mappedPIPoints.Count > 0) OnStatusMessage("WARNING: No PI tags were mapped to measurements - existing tag-map with {0:N0} tags remains in use.", m_mappedPIPoints.Count); else OnStatusMessage("WARNING: No PI tags were mapped to measurements - no tag-map exists so no points will be archived."); } }
/// <summary> /// Initializes <see cref="FileExporter"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; const string errorMessage = "{0} is missing from Settings - Example: exportInterval=5; modelIdentifier=Goslin; referenceAngleMeasurement=DEVARCHIVE:6; inputMeasurementKeys={{FILTER ActiveMeasurements WHERE Device='SHELBY' AND SignalType='FREQ'}}"; string setting; double seconds; // Load required parameters if (!settings.TryGetValue("exportInterval", out setting) || !double.TryParse(setting, out seconds)) throw new ArgumentException(string.Format(errorMessage, "exportInterval")); if (!settings.TryGetValue("fileExportPath", out m_fileExportPath)) m_fileExportPath = FilePath.GetAbsolutePath(""); m_exportInterval = (int)(seconds * 1000.0D); if (m_exportInterval <= 0) throw new ArgumentException("exportInterval should not be 0 - Example: exportInterval=5.5"); if ((object)InputMeasurementKeys == null || InputMeasurementKeys.Length == 0) throw new InvalidOperationException("There are no input measurements defined. You must define \"inputMeasurementKeys\" to define which measurements to export."); // Reference angle measurement has to be defined if using reference angle if (!settings.TryGetValue("referenceAngleMeasurement", out setting)) throw new ArgumentException(string.Format(errorMessage, "referenceAngleMeasurement")); m_referenceAngleKey = MeasurementKey.Parse(setting); // Make sure reference angle is first angle of input measurement keys collection InputMeasurementKeys = (new[] { m_referenceAngleKey }).Concat(InputMeasurementKeys).ToArray(); // Make sure sure reference angle key is actually an angle measurement SignalType signalType = InputMeasurementKeyTypes[InputMeasurementKeys.IndexOf(key => key == m_referenceAngleKey)]; if (signalType != SignalType.IPHA && signalType != SignalType.VPHA) throw new InvalidOperationException($"Specified reference angle measurement key is a {signalType.GetFormattedName()} signal, not a phase angle."); Comments = settings.TryGetValue("comments", out setting) ? setting : "Comment section---"; if (!settings.TryGetValue("modelIdentifier", out setting)) throw new ArgumentException(string.Format(errorMessage, "modelIdentifier")); ModelIdentifier = setting; // We enable tracking of latest measurements so we can use these values if points are missing - since we are using // latest measurement tracking, we sort all incoming points even though most of them will be thrown out... TrackLatestMeasurements = true; //// Create a new dictionary of base voltages //m_baseVoltages = new Dictionary<MeasurementKey, double>(); StringBuilder header = new StringBuilder(); //MeasurementKey voltageMagnitudeKey; //double baseKV; // Write header row header.Append("TimeStamp"); DataTable measurements = DataSource.Tables["ActiveMeasurements"]; int tieLines = 0; bool referenceAdded = false; for (int i = 0; i < InputMeasurementKeys.Length; i++) { // Lookup measurement key in active measurements table DataRow row = measurements.Select($"ID='{InputMeasurementKeys[i]}'")[0]; string deviceName = row["Device"].ToNonNullString("UNDEFINED").ToUpper().Trim(); if (!referenceAdded && InputMeasurementKeys[i] == m_referenceAngleKey) { header.AppendFormat(",Ref. Angle of {0}", deviceName); referenceAdded = true; } else switch (InputMeasurementKeyTypes[i]) { case SignalType.VPHM: header.AppendFormat(",{0} |V|", deviceName); tieLines++; //voltageMagnitudeKey = InputMeasurementKeys[i]; //if (settings.TryGetValue(voltageMagnitudeKey + "BaseKV", out setting) && double.TryParse(setting, out baseKV)) //{ // m_baseVoltages.Add(voltageMagnitudeKey, baseKV * SI.Kilo); //} //else //{ // int baseKVCode; // // Second check if base KV can be inferred from device name suffixed KV index // if (int.TryParse(deviceName[deviceName.Length - 1].ToString(), out baseKVCode) && baseKVCode > 1 && baseKVCode < BaseKVs.Length) // { // m_baseVoltages.Add(voltageMagnitudeKey, BaseKVs[baseKVCode]); // } // else // { // OnStatusMessage("WARNING: Did not find a valid base KV setting for voltage magnitude {0}, assumed 500KV", voltageMagnitudeKey.ToString()); // m_baseVoltages.Add(voltageMagnitudeKey, 500.0D * SI.Kilo); // } //} break; case SignalType.VPHA: header.AppendFormat(",{0} Voltage Angle", deviceName); break; case SignalType.IPHM: header.AppendFormat(",{0} |I|", deviceName); break; case SignalType.IPHA: header.AppendFormat(",{0} Current Angle", deviceName); break; default: header.AppendFormat(",{0} ??", deviceName); break; } } string row5 = header.ToString(); header = new StringBuilder(); // Add row 1 header.AppendFormat("Comments: {0}\r\n", Comments); // Add row 2 header.AppendFormat("Model Identifier: {0}\r\n", ModelIdentifier); // Add row 3 header.Append("Datapoints,Tielines,TimeStep"); if (InputMeasurementKeys.Length - 3 > 0) header.Append(new string(',', InputMeasurementKeys.Length - 3)); header.AppendLine(); // Add row 4 header.AppendFormat("{0},{1},{2}", RowCountMarker, tieLines, 1.0D / FramesPerSecond); header.AppendLine(); // Add row 5 header.AppendLine(row5); // Cache header for each file export m_header = header.ToString(); }
/// <summary> /// Creates a new instance of <see cref="MeasurementMetadata"/> using the provided measurement <paramref name="key"/>. All other fields remain the same. /// </summary> /// <param name="key">The key to set.</param> /// <returns>New instance of <see cref="MeasurementMetadata"/> using the provided measurement <paramref name="key"/>.</returns> public MeasurementMetadata ChangeKey(MeasurementKey key) => Key == key ? this : new MeasurementMetadata(key, TagName, Adder, Multiplier, MeasurementValueFilter);
/// <summary> /// Get a collection of out-of-range <see cref="IMeasurement"/>s with the given key. /// </summary> /// <param name="key">The <see cref="MeasurementKey"/> corresponding to the desired measurements.</param> /// <returns>A collection of out-of-range <see cref="IMeasurement"/>s.</returns> public ICollection<IMeasurement> GetOutOfRangeMeasurements(MeasurementKey key) { lock (m_outOfRangeMeasurements) { PurgeOldMeasurements(key); return new LinkedList<IMeasurement>(m_outOfRangeMeasurements[key]); } }
/// <summary> /// Constructs a new <see cref="Measurement"/> using default settings. /// </summary> public Measurement() { m_key = MeasurementKey.Undefined; m_receivedTimestamp = DateTime.UtcNow.Ticks; m_multiplier = 1.0D; }
/// <summary> /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters. /// </summary> /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param> /// <param name="source">Source of the measurement that this <see cref="MeasurementKey"/> represents (e.g., name of archive).</param> /// <param name="id">Numeric ID of the measurement that this <see cref="MeasurementKey"/> represents.</param> /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty.</exception> /// <exception cref="ArgumentNullException"><paramref name="source"/> cannot be null.</exception> public static MeasurementKey CreateOrUpdate(Guid signalID, string source, uint id) { Func<Guid, MeasurementKey> addValueFactory; Func<Guid, MeasurementKey, MeasurementKey> updateValueFactory; if (signalID == Guid.Empty) throw new ArgumentException("Unable to update undefined measurement key", nameof(signalID)); if (string.IsNullOrWhiteSpace(source)) throw new ArgumentNullException(nameof(source), "MeasurementKey source cannot be null or empty"); addValueFactory = guid => { // Create a new measurement key and add it to the KeyCache ConcurrentDictionary<uint, MeasurementKey> idLookup = s_keyCache.GetOrAdd(source, s => new ConcurrentDictionary<uint, MeasurementKey>()); return idLookup[id] = new MeasurementKey(guid, id, source); }; updateValueFactory = (guid, key) => { ConcurrentDictionary<uint, MeasurementKey> idLookup; // If existing measurement key is exactly the same as the // one we are trying to create, simply return that key if (key.ID == id && key.Source == source) return key; // Update source and ID and re-insert it into the KeyCache key.m_source = source; key.m_id = id; idLookup = s_keyCache.GetOrAdd(source, s => new ConcurrentDictionary<uint, MeasurementKey>()); idLookup[id] = key; return key; }; // https://msdn.microsoft.com/en-us/library/ee378675(v=vs.110).aspx // // If you call AddOrUpdate simultaneously on different threads, // addValueFactory may be called multiple times, but its key/value // pair might not be added to the dictionary for every call. // // This lock prevents race conditions that might occur in the addValueFactory that // could cause different MeasurementKey objects to be written to the KeyCache and IDCache lock (s_syncEdits) { return s_idCache.AddOrUpdate(signalID, addValueFactory, updateValueFactory); } }
// Adds the given mapping to the key-variable map. private void AddMapping(MeasurementKey key, string alias) { if (m_variableNames.Contains(alias)) throw new ArgumentException(string.Format("Variable name is not unique: {0}", alias)); m_variableNames.Add(alias); m_keyMapping.Add(key, alias); }
/// <summary> /// Sets the associated <see cref="MeasurementKey"/> for a <see cref="IMeasurement"/>. /// </summary> /// <param name="measurement"><see cref="IMeasurement"/> to create new <see cref="MeasurementMetadata"/> for.</param> /// <param name="key">New measurement key value to assign to measurement's metadata.</param> public static void SetKey(this IMeasurement measurement, MeasurementKey key) { measurement.Metadata = measurement.Metadata.ChangeKey(key); }
// Static Constructor static MeasurementKey() { // Order of operations is critical here since MeasurementKey and MeasurementMetadata have a circular reference Undefined = CreateUndefinedMeasurementKey(); MeasurementMetadata.CreateUndefinedMeasurementMetadata(); Undefined.m_metadata = MeasurementMetadata.Undefined; }
private void HandleNewMeasurementsRequest(MeasurementKey[] Keys) { OnStatusMessage("Received request for {0} keys...", new object[] { Keys.Count() }); if (!IsConnected) AttemptConnection(); var query = from row in DataSource.Tables["ActiveMeasurements"].AsEnumerable() from key in Keys where row["ID"].ToString().Split(':')[1] == key.ID.ToString() select new { Key = key, AlternateTag = row["ALTERNATETAG"].ToString(), PointTag = row["POINTTAG"].ToString() }; StringBuilder tagFilter = new StringBuilder(); foreach (var row in query) { string tagname = row.PointTag; if (!String.IsNullOrWhiteSpace(row.AlternateTag)) tagname = row.AlternateTag; if (!m_tagKeyMap.ContainsKey(tagname)) { m_tagKeyMap.AddOrUpdate(tagname, row.Key, (k, v) => row.Key); } if (tagFilter.Length > 0) tagFilter.Append(" OR "); tagFilter.Append(string.Format("tag='{0}'", tagname)); } m_points = new PIPointList(PIPoint.FindPIPoints(m_connection.Server, tagFilter.ToString(), true)); // TODO: Re-enable event pipe functionality in AF-SDK //bool useEventPipes; //// event pipes are only applicable if enabled in connection string and this is a real time session, not playback //useEventPipes = m_useEventPipes && StartTimeConstraint == DateTime.MinValue && StopTimeConstraint == DateTime.MaxValue; //if (useEventPipes) //{ // try // { // if (m_pipe != null) // ((_DEventPipeEvents_Event)m_pipe).OnNewValue -= (_DEventPipeEvents_OnNewValueEventHandler)PipeOnOnNewValue; // m_connection.Execute(server => m_pipe = m_points.Data.EventPipe); // ((_DEventPipeEvents_Event)m_pipe).OnNewValue += (_DEventPipeEvents_OnNewValueEventHandler)PipeOnOnNewValue; // } // catch (ThreadAbortException) // { // throw; // } // catch (Exception e) // { // useEventPipes = false; // try to run with polling instead of event pipes; // OnProcessException(e); // } //} //if (!useEventPipes) //{ // warn that we are going to use a different configuration here... if (m_useEventPipes) OnStatusMessage("WARNING: PI adapter switching from event pipes to polling due to error or start/stop time constraints."); // TODO: Poll method needs some work... // set up a new thread to do some long calls to PI and set up threads, timers, etc for polling StopGettingData(); ThreadPool.QueueUserWorkItem(StartGettingData, tagFilter); //} //m_useEventPipes = useEventPipes; }
// Add a measurement to the list of out of range measurements corresponding to the given key. private void AddOutOfRangeMeasurement(MeasurementKey key, IMeasurement measurement) { lock (m_outOfRangeMeasurements) { LinkedList<IMeasurement> outOfRangeList; if (!m_outOfRangeMeasurements.TryGetValue(key, out outOfRangeList)) { outOfRangeList = new LinkedList<IMeasurement>(); m_outOfRangeMeasurements.Add(key, outOfRangeList); } outOfRangeList.AddLast(measurement); } }
// Purge old, out-of-range measurements with the given key. private void PurgeOldMeasurements(MeasurementKey key) { lock (m_outOfRangeMeasurements) { LinkedList<IMeasurement> measurements = m_outOfRangeMeasurements[key]; bool donePurging = false; // Purge old measurements to prevent redundant warnings. while (measurements.Count > 0 && !donePurging) { IMeasurement measurement = measurements.First.Value; Ticks diff = base.RealTime - measurement.Timestamp; if (diff >= base.LagTicks + m_timeToPurge) measurements.RemoveFirst(); else donePurging = true; } } }
// Lookup signal type for given measurement key private SignalType LookupSignalType(MeasurementKey key) { try { DataRow[] filteredRows = DataSource.Tables["ActiveMeasurements"].Select(string.Format("ID = '{0}'", key)); if (filteredRows.Length > 0) return (SignalType)Enum.Parse(typeof(SignalType), filteredRows[0]["SignalType"].ToString(), true); } catch (Exception ex) { OnProcessException(new InvalidOperationException(string.Format("Failed to lookup signal type for measurement {0}: {1}", key, ex.Message), ex)); } return SignalType.NONE; }
/// <summary> /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters. /// </summary> /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param> /// <param name="value">A string representation of the <see cref="MeasurementKey"/>.</param> /// <param name="key">The measurement key that was created or updated or <see cref="Undefined"/>.</param> /// <returns>True if the measurement key was successfully created or updated, false otherwise.</returns> /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty.</exception> /// <exception cref="ArgumentNullException">Measurement key Source cannot be null.</exception> public static bool TryCreateOrUpdate(Guid signalID, string value, out MeasurementKey key) { try { key = CreateOrUpdate(signalID, value); return true; } catch { key = Undefined; return false; } }
/// <summary> /// Initializes <see cref="FileExporter"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; const string errorMessage = "{0} is missing from Settings - Example: exportInterval=5; useReferenceAngle=True; referenceAngleMeasurement=DEVARCHIVE:6; companyTagPrefix=TVA; useNumericQuality=True; inputMeasurementKeys={{FILTER ActiveMeasurements WHERE Device='SHELBY' AND SignalType='FREQ'}}"; string setting; double seconds; // Load required parameters if (!settings.TryGetValue("exportInterval", out setting) || !double.TryParse(setting, out seconds)) throw new ArgumentException(string.Format(errorMessage, "exportInterval")); m_exportInterval = (int)(seconds * 1000.0D); m_lastPublicationTime = 0; if (m_exportInterval <= 0) throw new ArgumentException("exportInterval should not be 0 - Example: exportInterval=5.5"); if (InputMeasurementKeys == null || InputMeasurementKeys.Length == 0) throw new InvalidOperationException("There are no input measurements defined. You must define \"inputMeasurementKeys\" to define which measurements to export."); if (!settings.TryGetValue("useReferenceAngle", out setting)) throw new ArgumentException(string.Format(errorMessage, "useReferenceAngle")); m_useReferenceAngle = setting.ParseBoolean(); if (m_useReferenceAngle) { // Reference angle measurement has to be defined if using reference angle if (!settings.TryGetValue("referenceAngleMeasurement", out setting)) throw new ArgumentException(string.Format(errorMessage, "referenceAngleMeasurement")); m_referenceAngleKey = MeasurementKey.Parse(setting); // Make sure reference angle is part of input measurement keys collection if (!InputMeasurementKeys.Contains(m_referenceAngleKey)) InputMeasurementKeys = InputMeasurementKeys.Concat(new[] { m_referenceAngleKey }).ToArray(); // Make sure reference angle key is actually an angle measurement SignalType signalType = InputMeasurementKeyTypes[InputMeasurementKeys.IndexOf(key => key == m_referenceAngleKey)]; if (signalType != SignalType.IPHA && signalType != SignalType.VPHA) throw new InvalidOperationException($"Specified reference angle measurement key is a {signalType.GetFormattedName()} signal, not a phase angle."); } // Load optional parameters if (settings.TryGetValue("companyTagPrefix", out setting)) m_companyTagPrefix = setting.ToUpper().Trim(); else m_companyTagPrefix = null; if (settings.TryGetValue("useNumericQuality", out setting)) m_useNumericQuality = setting.ParseBoolean(); else m_useNumericQuality = false; // Suffix company tag prefix with an underscore if defined if (!string.IsNullOrWhiteSpace(m_companyTagPrefix)) m_companyTagPrefix = m_companyTagPrefix.EnsureEnd('_'); // Define a default export location - user can override and add multiple locations in config later... m_dataExporter = new MultipleDestinationExporter(ConfigurationSection, m_exportInterval); m_dataExporter.StatusMessage += m_dataExporter_StatusMessage; m_dataExporter.ProcessException += m_dataExporter_ProcessException; m_dataExporter.Initialize(new[] { new ExportDestination(FilePath.GetAbsolutePath(ConfigurationSection + ".txt"), false) }); // Create new measurement tag name dictionary m_measurementTags = new ConcurrentDictionary<MeasurementKey, string>(); string pointID = "undefined"; // Lookup point tag name for input measurement in the ActiveMeasurements table foreach (MeasurementKey key in InputMeasurementKeys) { try { // Get measurement key as a string pointID = key.ToString(); // Lookup measurement key in active measurements table DataRow row = DataSource.Tables["ActiveMeasurements"].Select($"ID='{pointID}'")[0]; // Remove invalid symbols that may be in tag name string pointTag = row["PointTag"].ToNonNullString(pointID).Replace('-', '_').Replace(':', '_').ToUpper(); // Prefix point tag with company prefix if defined if (!string.IsNullOrWhiteSpace(m_companyTagPrefix) && !pointTag.StartsWith(m_companyTagPrefix)) pointTag = m_companyTagPrefix + pointTag; m_measurementTags.TryAdd(key, pointTag); } catch (Exception ex) { OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Failed to lookup point tag for measurement [{pointID}] due to exception: {ex.Message}")); } } // We enable tracking of latest measurements so we can use these values if points are missing - since we are using // latest measurement tracking, we sort all incoming points even though most of them will be thrown out... TrackLatestMeasurements = true; }
/// <summary> /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters. /// </summary> /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param> /// <param name="source">Source of the measurement that this <see cref="MeasurementKey"/> represents (e.g., name of archive).</param> /// <param name="id">Numeric ID of the measurement that this <see cref="MeasurementKey"/> represents.</param> /// <param name="key">The measurement key that was created or updated or <see cref="Undefined"/>.</param> /// <returns>True if the measurement key was successfully created or updated, false otherwise.</returns> /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty.</exception> /// <exception cref="ArgumentNullException"><paramref name="source"/> cannot be null.</exception> public static bool TryCreateOrUpdate(Guid signalID, string source, uint id, out MeasurementKey key) { try { key = CreateOrUpdate(signalID, source, id); return true; } catch { key = Undefined; return false; } }
/// <summary> /// Initializes the <see cref="LossOfField"/> detector. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; string setting; // Load parameters if (settings.TryGetValue("pSet", out setting)) m_pSet = double.Parse(setting); else m_pSet = -600; if (settings.TryGetValue("qSet", out setting)) m_qSet = double.Parse(setting); else m_qSet = 200; if (settings.TryGetValue("qAreaSet", out setting)) m_qAreaSet = double.Parse(setting); else m_qAreaSet = 500; if (settings.TryGetValue("voltageThreshold", out setting)) m_voltageThreshold = double.Parse(setting); else m_voltageThreshold = 475000; if (settings.TryGetValue("analysisInterval", out setting)) m_analysisInterval = int.Parse(setting); else m_analysisInterval = FramesPerSecond; m_count = 0; m_count1 = 0; m_count2 = 0; // Load needed measurement keys from defined InputMeasurementKeys int index; // Get expected voltage magnitude index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.VPHM); if (index < 0) throw new InvalidOperationException("No voltage magnitude input measurement key was not found - this is a required input measurement for the loss of field detector."); m_voltageMagnitude = InputMeasurementKeys[index]; // Get expected voltage angle index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.VPHA); if (index < 0) throw new InvalidOperationException("No voltage angle input measurement key was not found - this is a required input measurement for the loss of field detector."); m_voltageAngle = InputMeasurementKeys[index]; // Get expected current magnitude index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.IPHM); if (index < 0) throw new InvalidOperationException("No current magnitude input measurement key was not found - this is a required input measurement for the loss of field detector."); m_currentMagnitude = InputMeasurementKeys[index]; // Get expected current angle index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.IPHA); if (index < 0) throw new InvalidOperationException("No current angle input measurement key was not found - this is a required input measurement for the loss of field detector."); m_currentAngle = InputMeasurementKeys[index]; // Make sure only these phasor measurements are used as input InputMeasurementKeys = new[] { m_voltageMagnitude, m_voltageAngle, m_currentMagnitude, m_currentAngle }; // Validate output measurements if (OutputMeasurements.Length < Enum.GetValues(typeof(Output)).Length) throw new InvalidOperationException("Not enough output measurements were specified for the loss of field detector, expecting measurements for \"Warning Signal Status (0 = Not Signaled, 1 = Signaled)\", \"Real Power\", \"Reactive Power\" and \"Q-Area Value\" - in this order."); }
/// <summary> /// Attempts to convert the string representation of a <see cref="MeasurementKey"/> into its value equivalent. /// </summary> /// <param name="value">A string representing the <see cref="MeasurementKey"/> to convert.</param> /// <param name="key">Output <see cref="MeasurementKey"/> in which to stored parsed value.</param> /// <returns>A <c>true</c> if <see cref="MeasurementKey"/>representation contained in <paramref name="value"/> could be parsed; otherwise <c>false</c>.</returns> public static bool TryParse(string value, out MeasurementKey key) { string source; uint id; // Split the input into source and ID if (TrySplit(value, out source, out id)) { // First attempt to look up an existing key key = LookUpBySource(source, id); if (key == Undefined) { try { // Lookup failed - attempt to create it with a newly generated signal ID key = CreateOrUpdate(Guid.NewGuid(), source, id); } catch { // source is null or empty key = Undefined; } } } else { // Incorrect format for a measurement key key = Undefined; } return key != Undefined; }
private void SubscribeToPointUpdates(MeasurementKey[] keys) { OnStatusMessage("Subscribing to updates for {0} measurements...", keys.Length); var query = from row in DataSource.Tables["ActiveMeasurements"].AsEnumerable() from key in keys where row["ID"].ToString() == key.ToString() select new { Key = key, AlternateTag = row["AlternateTag"].ToString(), PointTag = row["PointTag"].ToString() }; List<PIPoint> dataPoints = new List<PIPoint>(); foreach (var row in query) { string tagName = row.PointTag; if (!string.IsNullOrWhiteSpace(row.AlternateTag)) tagName = row.AlternateTag; OnStatusMessage("DEBUG: Looking up point tag '{0}'...", tagName); PIPoint point = GetPIPoint(m_connection.Server, tagName); if ((object)point != null) { OnStatusMessage("DEBUG: Found point tag '{0}'...", tagName); dataPoints.Add(point); m_tagKeyMap[point.ID] = row.Key; } else { OnStatusMessage("DEBUG: Failed to find point tag '{0}'...", tagName); } } // Remove sign-ups for any existing point list if ((object)m_dataPoints != null) m_dataPipe.RemoveSignups(m_dataPoints); // Sign up for updates on selected points AFListResults<PIPoint, AFDataPipeEvent> initialEvents = m_dataPipe.AddSignupsWithInitEvents(dataPoints); OnStatusMessage("DEBUG: Initial event count = {0}...", initialEvents.Results.Count); foreach (AFDataPipeEvent item in initialEvents.Results) { OnStatusMessage("DEBUG: Found initial event for action...", item.Action); if (item.Action != AFDataPipeAction.Delete) m_dataUpdateObserver_DataUpdated(this, new EventArgs<AFValue>(item.Value)); } m_dataPoints = dataPoints; m_eventTimer.Enabled = true; }
/// <summary> /// Creates the undefined measurement key. Used to initialize <see cref="Undefined"/>. /// </summary> private static MeasurementKey CreateUndefinedMeasurementKey() { MeasurementKey key = new MeasurementKey(Guid.Empty, uint.MaxValue, "__"); // Lock on s_syncEdits is not required since method is only called by the static constructor s_keyCache.GetOrAdd(key.Source, kcf => new ConcurrentDictionary<uint, MeasurementKey>())[uint.MaxValue] = key; return key; }
private void EstablishPIPointMappings(MeasurementKey[] keys) { PIPoint point; bool mappingEstablished = false; foreach (MeasurementKey key in keys) { // If adapter gets disabled while executing this thread - go ahead and exit if (!Enabled) return; try { mappingEstablished = false; if (m_mappedPIPoints.TryGetValue(key, out point)) { if ((object)point == null) { // Create new connection point point = CreateMappedPIPoint(key); if ((object)point != null) { m_mappedPIPoints[key] = point; mappingEstablished = true; } } else { // Connection point is already established mappingEstablished = true; } } } catch (Exception ex) { OnProcessException(new InvalidOperationException($"Failed to create connection point mapping for '{key}': {ex.Message}", ex)); } finally { lock (m_pendingMappings) { m_pendingMappings.Remove(key); } if (!mappingEstablished) m_mappedPIPoints.TryRemove(key, out point); // Provide some level of feed back on progress of mapping process Interlocked.Increment(ref m_processedMappings); if (Interlocked.Read(ref m_processedMappings) % 100 == 0) OnStatusMessage("Mapped {0:N0} PI tags to measurements, {1:0.00%} complete...", m_mappedPIPoints.Count - m_pendingMappings.Count, 1.0D - m_pendingMappings.Count / (double)m_mappedPIPoints.Count); } } }