/// <summary> /// Get signal reference for specified <see cref="SignalKind"/>. /// </summary> /// <param name="type"><see cref="SignalKind"/> to request signal reference for.</param> /// <returns>Signal reference of given <see cref="SignalKind"/>.</returns> public string GetSignalReference(SignalKind type) { // We cache non-indexed signal reference strings so they don't need to be generated at each mapping call. // This helps with performance since the mappings for each signal occur 30 times per second. string[] references; int typeIndex = (int)type; // Look up synonym in dictionary based on signal type, if found return single element references = m_generatedSignalReferenceCache[typeIndex]; if ((object)references != null) { return(references[0]); } // Create a new signal reference array (for single element) references = new string[1]; // Create and cache new non-indexed signal reference references[0] = SignalReference.ToString(IDLabel, type); // Cache generated signal synonym m_generatedSignalReferenceCache[typeIndex] = references; return(references[0]); }
/// <summary> /// Constructs a new <see cref="SignalReferenceMeasurement"/> from the specified parameters. /// </summary> /// <param name="measurement">Source <see cref="IMeasurement"/> value.</param> /// <param name="signalReference">Associated <see cref="SignalReference"/>.</param> public SignalReferenceMeasurement(IMeasurement measurement, SignalReference signalReference) : base(measurement.ID, measurement.Source, measurement.Value, measurement.Adder, measurement.Multiplier, measurement.Timestamp) { base.ValueQualityIsGood = measurement.ValueQualityIsGood; base.TimestampQualityIsGood = measurement.TimestampQualityIsGood; m_signalReference = signalReference; }
/// <summary> /// Get signal reference for specified <see cref="SignalKind"/> and <paramref name="index"/>. /// </summary> /// <param name="type"><see cref="SignalKind"/> to request signal reference for.</param> /// <param name="index">Index <see cref="SignalKind"/> to request signal reference for.</param> /// <param name="count">Number of signals defined for this <see cref="SignalKind"/>.</param> /// <returns>Signal reference of given <see cref="SignalKind"/> and <paramref name="index"/>.</returns> public string GetSignalReference(SignalKind type, int index, int count) { // We cache indexed signal reference strings so they don't need to be generated at each mapping call. // This helps with performance since the mappings for each signal occur 30 times per second. // For speed purposes we intentionally do not validate that signalIndex falls within signalCount, be // sure calling procedures are very careful with parameters... string[] references; int typeIndex = (int)type; // Look up synonym in dictionary based on signal type references = m_generatedSignalReferenceCache[typeIndex]; if ((object)references != null) { // Verify signal count has not changed (we may have received new configuration from device) if (count == references.Length) { // Create and cache new signal reference if it doesn't exist if ((object)references[index] == null) { references[index] = SignalReference.ToString(IDLabel, type, index + 1); } return(references[index]); } } // Create a new indexed signal reference array references = new string[count]; // Create and cache new signal reference references[index] = SignalReference.ToString(IDLabel, type, index + 1); // Cache generated signal synonym array m_generatedSignalReferenceCache[typeIndex] = references; return(references[index]); }
public void ValidateCalculatorConfigurations(int?historianID, string systemName) { const int Avg = 0, Max = 1, Min = 2; PowerCalculationConfigurationValidation.ValidateDatabaseDefinitions(); TableOperations <Measurement> measurementTable = DataContext.Table <Measurement>(); string frequencyDeviceName = string.Format(SystemFrequencyDeviceName, systemName); // Look for existing frequency average if (measurementTable.QueryRecordCountWhere($"SignalReference = '{SignalReference.ToString(frequencyDeviceName, SignalKind.Frequency)}'") > 0) { return; } TableOperations <CustomActionAdapter> customActionAdapterTable = DataContext.Table <CustomActionAdapter>(); CustomActionAdapter avgFreqAdapter = customActionAdapterTable.QueryRecordWhere("TypeName = {0}", typeof(PowerCalculations.AverageFrequency).FullName) ?? NewCustomActionAdapter(); Measurement[] measurements = GetCalculatedFrequencyMeasurements(historianID, systemName, frequencyDeviceName); double lagTime = DefaultCalculationLagTime; // Reduce lag-time since dynamic calculations can depend on average frequency lagTime -= lagTime > 1.0 ? 1.0 : 0.5; if (lagTime < 0.1) { lagTime = 0.1; } avgFreqAdapter.AdapterName = "PHASOR!AVERAGEFREQ"; avgFreqAdapter.AssemblyName = "PowerCalculations.dll"; avgFreqAdapter.TypeName = typeof(PowerCalculations.AverageFrequency).FullName; avgFreqAdapter.ConnectionString = $"InputMeasurementKeys={{FILTER ActiveMeasurements WHERE SignalType = 'FREQ' AND SignalReference NOT LIKE '{frequencyDeviceName}%'}}; OutputMeasurements={{{measurements[Avg].SignalID};{measurements[Max].SignalID};{measurements[Min].SignalID}}}; LagTime={lagTime}; LeadTime={DefaultCalculationLeadTime}; FramesPerSecond={DefaultCalculationFramesPerSecond}"; avgFreqAdapter.Enabled = true; customActionAdapterTable.AddNewOrUpdateRecord(avgFreqAdapter); }
private Measurement[] GetCalculatedFrequencyMeasurements(int?historianID) { SignalType freqSignalType = DataContext.Table <SignalType>().QueryRecordWhere("Acronym = 'FREQ'"); if (freqSignalType.ID == 0) { throw new InvalidOperationException("Failed to find 'FREQ' signal type"); } Device freqDevice = QueryDevice(SystemFrequencyDeviceName); freqDevice.Acronym = SystemFrequencyDeviceName; freqDevice.Name = "Calculated System Frequency Statistics Virtual Device"; freqDevice.IsConcentrator = false; freqDevice.HistorianID = historianID; freqDevice.ProtocolID = VirtualProtocolID; freqDevice.Enabled = true; AddNewOrUpdateDevice(freqDevice); freqDevice = QueryDevice(SystemFrequencyDeviceName); // Signal references within a device are used to map frequencies back into a frame of data - since frames are only // designated to have a single frequency measurement, the max and min frequencies are marked as analog values string avgFreqSignalRef = SignalReference.ToString(SystemFrequencyDeviceName, SignalKind.Frequency); string maxFreqSignalRef = SignalReference.ToString(SystemFrequencyDeviceName, SignalKind.Analog, 1); string minFreqSignalRef = SignalReference.ToString(SystemFrequencyDeviceName, SignalKind.Analog, 2); Measurement avgFreqMeasurement = QueryMeasurement(avgFreqSignalRef); Measurement maxFreqMeasurement = QueryMeasurement(maxFreqSignalRef); Measurement minFreqMeasurement = QueryMeasurement(minFreqSignalRef); avgFreqMeasurement.PointTag = $"{SystemFrequencyDeviceName}-AVG-FQ"; avgFreqMeasurement.SignalReference = avgFreqSignalRef; avgFreqMeasurement.SignalTypeID = freqSignalType.ID; avgFreqMeasurement.DeviceID = freqDevice.ID; avgFreqMeasurement.HistorianID = historianID; avgFreqMeasurement.Description = "Average System Frequency"; avgFreqMeasurement.Internal = true; avgFreqMeasurement.Enabled = true; maxFreqMeasurement.PointTag = $"{SystemFrequencyDeviceName}-MAX-FQ"; maxFreqMeasurement.SignalReference = maxFreqSignalRef; maxFreqMeasurement.SignalTypeID = freqSignalType.ID; maxFreqMeasurement.DeviceID = freqDevice.ID; maxFreqMeasurement.HistorianID = historianID; maxFreqMeasurement.Description = "Maximum System Frequency"; maxFreqMeasurement.Internal = true; maxFreqMeasurement.Enabled = true; minFreqMeasurement.PointTag = $"{SystemFrequencyDeviceName}-MIN-FQ"; minFreqMeasurement.SignalReference = minFreqSignalRef; minFreqMeasurement.SignalTypeID = freqSignalType.ID; minFreqMeasurement.DeviceID = freqDevice.ID; minFreqMeasurement.HistorianID = historianID; minFreqMeasurement.Description = "Minimum System Frequency"; minFreqMeasurement.Internal = true; minFreqMeasurement.Enabled = true; AddNewOrUpdateMeasurement(avgFreqMeasurement); AddNewOrUpdateMeasurement(maxFreqMeasurement); AddNewOrUpdateMeasurement(minFreqMeasurement); Measurement[] measurements = new Measurement[3]; // Requery frequency measurements in case they were newly added - this will retrieve autoinc / new Guids values measurements[0] = QueryMeasurement(avgFreqSignalRef); measurements[1] = QueryMeasurement(maxFreqSignalRef); measurements[2] = QueryMeasurement(minFreqSignalRef); return(measurements); }
private static void PhasorDataSourceValidation(AdoDataConnection database, string nodeIDQueryString, ulong trackingVersion, string arguments, Action<string> statusMessage, Action<Exception> processException) { // Make sure setting exists to allow user to by-pass phasor data source validation at startup ConfigurationFile configFile = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = configFile.Settings["systemSettings"]; settings.Add("ProcessPhasorDataSourceValidation", true, "Determines if the phasor data source validation should be processed at startup"); // See if this node should process phasor source validation if (!settings["ProcessPhasorDataSourceValidation"].ValueAsBoolean()) return; Dictionary<string, string> args = new Dictionary<string, string>(); bool skipOptimization = false, renameAllPointTags = false; string arg, acronym; if (!string.IsNullOrEmpty(arguments)) args = arguments.ParseKeyValuePairs(); if (args.TryGetValue("skipOptimization", out arg)) skipOptimization = arg.ParseBoolean(); if (args.TryGetValue("renameAllPointTags", out arg)) renameAllPointTags = arg.ParseBoolean(); CreateDefaultNode(database, nodeIDQueryString, statusMessage, processException); LoadDefaultConfigurationEntity(database, statusMessage, processException); LoadDefaultInterconnection(database, statusMessage, processException); LoadDefaultProtocol(database, statusMessage, processException); LoadDefaultSignalType(database, statusMessage, processException); LoadDefaultStatistic(database, statusMessage, processException); EstablishDefaultMeasurementKeyCache(database, statusMessage, processException); statusMessage("Validating signal types..."); // Validate that the acronym for status flags is FLAG (it was STAT in prior versions) if (database.Connection.ExecuteScalar("SELECT Acronym FROM SignalType WHERE Suffix='SF'").ToNonNullString().ToUpper() == "STAT") database.Connection.ExecuteNonQuery("UPDATE SignalType SET Acronym='FLAG' WHERE Suffix='SF'"); // Validate that the calculation and statistic signal types are defined (they did not in initial release) if (Convert.ToInt32(database.Connection.ExecuteScalar("SELECT COUNT(*) FROM SignalType WHERE Acronym='CALC'")) == 0) database.Connection.ExecuteNonQuery("INSERT INTO SignalType(Name, Acronym, Suffix, Abbreviation, LongAcronym, Source, EngineeringUnits) VALUES('Calculated Value', 'CALC', 'CV', 'CV', 'Calculated', 'PMU', '')"); if (Convert.ToInt32(database.Connection.ExecuteScalar("SELECT COUNT(*) FROM SignalType WHERE Acronym='STAT'")) == 0) database.Connection.ExecuteNonQuery("INSERT INTO SignalType(Name, Acronym, Suffix, Abbreviation, LongAcronym, Source, EngineeringUnits) VALUES('Statistic', 'STAT', 'ST', 'ST', 'Statistic', 'Any', '')"); if (Convert.ToInt32(database.Connection.ExecuteScalar("SELECT COUNT(*) FROM SignalType WHERE Acronym='QUAL'")) == 0) database.Connection.ExecuteNonQuery("INSERT INTO SignalType(Name, Acronym, Suffix, Abbreviation, LongAcronym, Source, EngineeringUnits) VALUES('Quality Flags', 'QUAL', 'QF', 'QF', 'QualityFlags', 'Frame', '')"); // Make sure values are defined for long acronyms (did not exist in prior versions) if (Convert.ToInt32(database.Connection.ExecuteScalar("SELECT COUNT(*) FROM SignalType WHERE LongAcronym='Undefined'")) > 0) { // Update abbreviations to better values for consistent custom point tag naming convention if (database.Connection.ExecuteScalar("SELECT Abbreviation FROM SignalType WHERE Acronym='ALOG'").ToNonNullString().ToUpper() == "A") database.Connection.ExecuteNonQuery("UPDATE SignalType SET Abbreviation='AV' WHERE Acronym='ALOG'"); if (database.Connection.ExecuteScalar("SELECT Abbreviation FROM SignalType WHERE Acronym='DIGI'").ToNonNullString().ToUpper() == "D") database.Connection.ExecuteNonQuery("UPDATE SignalType SET Abbreviation='DV' WHERE Acronym='DIGI'"); if (database.Connection.ExecuteScalar("SELECT Abbreviation FROM SignalType WHERE Acronym='CALC'").ToNonNullString().ToUpper() == "C") database.Connection.ExecuteNonQuery("UPDATE SignalType SET Abbreviation='CV' WHERE Acronym='CALC'"); if (database.Connection.ExecuteScalar("SELECT Abbreviation FROM SignalType WHERE Acronym='STAT'").ToNonNullString().ToUpper() == "P") database.Connection.ExecuteNonQuery("UPDATE SignalType SET Abbreviation='ST' WHERE Acronym='STAT'"); if (database.Connection.ExecuteScalar("SELECT Abbreviation FROM SignalType WHERE Acronym='QUAL'").ToNonNullString().ToUpper() == "Q") database.Connection.ExecuteNonQuery("UPDATE SignalType SET Abbreviation='QF' WHERE Acronym='QUAL'"); IEnumerable<DataRow> signalTypes = database.Connection.RetrieveData(database.AdapterType, "SELECT Name, Acronym FROM SignalType WHERE LongAcronym='Undefined'").AsEnumerable(); string longAcronym; foreach (DataRow row in signalTypes) { acronym = row.Field<string>("Acronym").ToUpperInvariant().Trim(); switch (acronym) { case "IPHM": longAcronym = "CurrentMagnitude"; break; case "IPHA": longAcronym = "CurrentAngle"; break; case "VPHM": longAcronym = "VoltageMagnitude"; break; case "VPHA": longAcronym = "VoltageAngle"; break; case "FREQ": longAcronym = "Frequency"; break; case "DFDT": longAcronym = "DfDt"; break; case "ALOG": longAcronym = "Analog"; break; case "FLAG": longAcronym = "StatusFlags"; break; case "DIGI": longAcronym = "Digital"; break; case "CALC": longAcronym = "Calculated"; break; case "STAT": longAcronym = "Statistic"; break; case "ALRM": longAcronym = "Alarm"; break; case "QUAL": longAcronym = "QualityFlags"; break; default: longAcronym = row.Field<string>("Name").Trim().RemoveWhiteSpace(); if (string.IsNullOrEmpty(longAcronym)) longAcronym = acronym.ToNonNullString("?"); break; } database.Connection.ExecuteNonQuery(string.Format("UPDATE SignalType SET LongAcronym='{0}' WHERE Acronym='{1}'", longAcronym, acronym)); } } statusMessage("Validating output stream device ID codes..."); // Validate all ID codes for output stream devices are not set their default value database.Connection.ExecuteNonQuery("UPDATE OutputStreamDevice SET IDCode = ID WHERE IDCode = 0"); statusMessage("Verifying statistics archive exists..."); // Validate that the statistics historian exists if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Historian WHERE Acronym='STAT' AND NodeID={0}", nodeIDQueryString))) == 0) database.Connection.ExecuteNonQuery(string.Format("INSERT INTO Historian(NodeID, Acronym, Name, AssemblyName, TypeName, ConnectionString, IsLocal, Description, LoadOrder, Enabled) VALUES({0}, 'STAT', 'Statistics Archive', 'HistorianAdapters.dll', 'HistorianAdapters.LocalOutputAdapter', '', 1, 'Local historian used to archive system statistics', 9999, 1)", nodeIDQueryString)); // Make sure statistics path exists to hold historian files string statisticsPath = FilePath.GetAbsolutePath(FilePath.AddPathSuffix("Statistics")); if (!Directory.Exists(statisticsPath)) Directory.CreateDirectory(statisticsPath); // Make sure needed statistic historian configuration settings are properly defined settings = configFile.Settings["statMetadataFile"]; settings.Add("FileName", string.Format("Statistics{0}stat_dbase.dat", Path.DirectorySeparatorChar), "Name of the statistics meta-data file including its path."); settings.Add("LoadOnOpen", true, "True if file records are to be loaded in memory when opened; otherwise False - this defaults to True for the statistics meta-data file."); settings.Add("ReloadOnModify", false, "True if file records loaded in memory are to be re-loaded when file is modified on disk; otherwise False - this defaults to False for the statistics meta-data file."); settings["LoadOnOpen"].Update(true); settings["ReloadOnModify"].Update(false); settings = configFile.Settings["statStateFile"]; settings.Add("FileName", string.Format("Statistics{0}stat_startup.dat", Path.DirectorySeparatorChar), "Name of the statistics state file including its path."); settings.Add("AutoSaveInterval", 10000, "Interval in milliseconds at which the file records loaded in memory are to be saved automatically to disk. Use -1 to disable automatic saving - this defaults to 10,000 for the statistics state file."); settings.Add("LoadOnOpen", true, "True if file records are to be loaded in memory when opened; otherwise False - this defaults to True for the statistics state file."); settings.Add("SaveOnClose", true, "True if file records loaded in memory are to be saved to disk when file is closed; otherwise False - this defaults to True for the statistics state file."); settings.Add("ReloadOnModify", false, "True if file records loaded in memory are to be re-loaded when file is modified on disk; otherwise False - this defaults to False for the statistics state file."); settings["AutoSaveInterval"].Update(10000); settings["LoadOnOpen"].Update(true); settings["SaveOnClose"].Update(true); settings["ReloadOnModify"].Update(false); settings = configFile.Settings["statIntercomFile"]; settings.Add("FileName", string.Format("Statistics{0}scratch.dat", Path.DirectorySeparatorChar), "Name of the statistics intercom file including its path."); settings.Add("AutoSaveInterval", 10000, "Interval in milliseconds at which the file records loaded in memory are to be saved automatically to disk. Use -1 to disable automatic saving - this defaults to 10,000 for the statistics intercom file."); settings.Add("LoadOnOpen", true, "True if file records are to be loaded in memory when opened; otherwise False - this defaults to True for the statistics intercom file."); settings.Add("SaveOnClose", true, "True if file records loaded in memory are to be saved to disk when file is closed; otherwise False - this defaults to True for the statistics intercom file."); settings.Add("ReloadOnModify", false, "True if file records loaded in memory are to be re-loaded when file is modified on disk; otherwise False - this defaults to False for the statistics intercom file."); settings["AutoSaveInterval"].Update(1000); settings["LoadOnOpen"].Update(true); settings["SaveOnClose"].Update(true); settings["ReloadOnModify"].Update(false); settings = configFile.Settings["statArchiveFile"]; settings.Add("FileName", string.Format("Statistics{0}stat_archive.d", Path.DirectorySeparatorChar), "Name of the statistics working archive file including its path."); settings.Add("CacheWrites", true, "True if writes are to be cached for performance; otherwise False - this defaults to True for the statistics working archive file."); settings.Add("ConserveMemory", false, "True if attempts are to be made to conserve memory; otherwise False - this defaults to False for the statistics working archive file."); settings["CacheWrites"].Update(true); settings["ConserveMemory"].Update(false); settings = configFile.Settings["statMetadataService"]; settings.Add("Endpoints", "http.rest://localhost:6051/historian", "Semicolon delimited list of URIs where the web service can be accessed - this defaults to http.rest://localhost:6051/historian for the statistics meta-data service."); settings = configFile.Settings["statTimeSeriesDataService"]; settings.Add("Endpoints", "http.rest://localhost:6052/historian", "Semicolon delimited list of URIs where the web service can be accessed - this defaults to http.rest://localhost:6052/historian for the statistics time-series data service."); configFile.Save(); // Get the needed statistic related IDs int statHistorianID = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT ID FROM Historian WHERE Acronym='STAT' AND NodeID={0}", nodeIDQueryString))); // Load the defined system statistics IEnumerable<DataRow> statistics = database.Connection.RetrieveData(database.AdapterType, "SELECT * FROM Statistic ORDER BY Source, SignalIndex").AsEnumerable(); // Filter statistics to device, input stream and output stream types IEnumerable<DataRow> deviceStatistics = statistics.Where(row => string.Compare(row.Field<string>("Source"), "Device", true) == 0).ToList(); IEnumerable<DataRow> inputStreamStatistics = statistics.Where(row => string.Compare(row.Field<string>("Source"), "InputStream", true) == 0).ToList(); // Define kinds of output signal that will designate a location in an output stream protocol frame - other non-mappable measurements will be removed from output stream measurements SignalKind[] validOutputSignalKinds = { SignalKind.Angle, SignalKind.Magnitude, SignalKind.Frequency, SignalKind.DfDt, SignalKind.Status, SignalKind.Analog, SignalKind.Digital, SignalKind.Quality }; HashSet<int> measurementIDsToDelete = new HashSet<int>(); SignalReference deviceSignalReference; string query, signalReference, pointTag, company, description, protocolIDs; int adapterID, deviceID, signalIndex; bool firstStatisticExisted; int? historianID; string[] trackedTables; ulong changes; try { // Determine the tables for which changes are tracked if (trackingVersion != ulong.MinValue) { trackedTables = database.Connection.RetrieveData(database.AdapterType, "SELECT Name FROM TrackedTable").Select() .Select(row => row["Name"].ToNonNullString()) .ToArray(); } else { trackedTables = new string[0]; } } catch { trackedTables = new string[0]; } statusMessage("Validating device protocols..."); // Extract IDs for phasor protocols StringBuilder protocolIDList = new StringBuilder(); DataTable protocols = database.Connection.RetrieveData(database.AdapterType, "SELECT * FROM Protocol"); if (protocols.Columns.Contains("Category")) { // Make sure new protocol types exist if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Protocol WHERE Acronym='GatewayTransport'"))) == 0) { database.Connection.ExecuteNonQuery("INSERT INTO Protocol(Acronym, Name, Type, Category, AssemblyName, TypeName, LoadOrder) VALUES('GatewayTransport', 'Gateway Transport', 'Measurement', 'Gateway', 'GSF.TimeSeries.dll', 'GSF.TimeSeries.Transport.DataSubscriber', " + (protocols.Rows.Count + 1) + ")"); if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Protocol WHERE Acronym='WAV'"))) == 0) database.Connection.ExecuteNonQuery("INSERT INTO Protocol(Acronym, Name, Type, Category, AssemblyName, TypeName, LoadOrder) VALUES('WAV', 'Wave Form Input Adapter', 'Frame', 'Audio', 'WavInputAdapter.dll', 'WavInputAdapter.WavInputAdapter', " + (protocols.Rows.Count + 2) + ")"); if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Protocol WHERE Acronym='IeeeC37_118V2'"))) == 0) database.Connection.ExecuteNonQuery("INSERT INTO Protocol(Acronym, Name, Type, Category, AssemblyName, TypeName, LoadOrder) VALUES('IeeeC37_118V2', 'IEEE C37.118.2-2011', 'Frame', 'Phasor', 'PhasorProtocolAdapters.dll', 'PhasorProtocolAdapters.PhasorMeasurementMapper', 2)"); if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Protocol WHERE Acronym='VirtualInput'"))) == 0) database.Connection.ExecuteNonQuery("INSERT INTO Protocol(Acronym, Name, Type, Category, AssemblyName, TypeName, LoadOrder) VALUES('VirtualInput', 'Virtual Device', 'Frame', 'Virtual', 'TestingAdapters.dll', 'TestingAdapters.VirtualInputAdapter', " + (protocols.Rows.Count + 4) + ")"); } if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Protocol WHERE Acronym='Iec61850_90_5'"))) == 0) database.Connection.ExecuteNonQuery("INSERT INTO Protocol(Acronym, Name, Type, Category, AssemblyName, TypeName, LoadOrder) VALUES('Iec61850_90_5', 'IEC 61850-90-5', 'Frame', 'Phasor', 'PhasorProtocolAdapters.dll', 'PhasorProtocolAdapters.PhasorMeasurementMapper', 12)"); foreach (DataRow protocol in protocols.Rows) { if (string.Compare(protocol.Field<string>("Category"), "Phasor", true) == 0) { if (protocolIDList.Length > 0) protocolIDList.Append(", "); protocolIDList.Append(protocol.ConvertField<int>("ID")); } } } else { // Older schemas do not include protocol categories and assembly info foreach (DataRow protocol in protocols.Rows) { if (protocolIDList.Length > 0) protocolIDList.Append(", "); protocolIDList.Append(protocol.ConvertField<int>("ID")); } } protocolIDs = protocolIDList.ToString(); try { // Determine how many changes were made to devices and measurements - // if no changes were made, we can skip the next few steps if (trackedTables.Contains("Device") && trackedTables.Contains("Measurement")) changes = Convert.ToUInt64(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM TrackedChange WHERE (TableName = 'Device' OR TableName = 'Measurement') AND ID > {0}", trackingVersion))); else changes = ulong.MaxValue; } catch { changes = ulong.MaxValue; } if (skipOptimization || changes != 0L) { statusMessage("Validating device measurements..."); // Get protocol ID list for those protocols that support time quality flags DataTable timeQualityProtocols = database.Connection.RetrieveData(database.AdapterType, "SELECT ID FROM Protocol WHERE Acronym = 'IeeeC37_118V1' OR Acronym = 'IeeeC37_118V2' OR Acronym = 'IeeeC37_118D6' OR Acronym = 'Iec61850_90_5'"); StringBuilder timeQualityProtocolIDList = new StringBuilder(); string timeQualityProtocolIDs; foreach (DataRow timeQualityProtocol in timeQualityProtocols.Rows) { if (timeQualityProtocolIDList.Length > 0) timeQualityProtocolIDList.Append(", "); timeQualityProtocolIDList.Append(timeQualityProtocol.ConvertField<int>("ID")); } timeQualityProtocolIDs = timeQualityProtocolIDList.ToString(); int qualityFlagsSignalTypeID = Convert.ToInt32(database.Connection.ExecuteScalar("SELECT ID FROM SignalType WHERE Acronym='QUAL'")); // Make sure one device quality flags measurement exists for each "connection" for devices that support time quality flags foreach (DataRow device in database.Connection.RetrieveData(database.AdapterType, string.Format("SELECT * FROM Device WHERE ((IsConcentrator = 0 AND ParentID IS NULL) OR IsConcentrator = 1) AND NodeID = {0} AND ProtocolID IN ({1})", nodeIDQueryString, timeQualityProtocolIDs)).Rows) { deviceID = device.ConvertField<int>("ID"); acronym = device.Field<string>("Acronym"); signalReference = SignalReference.ToString(acronym, SignalKind.Quality); // See if quality flags measurement exists for device if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Measurement WHERE SignalReference = '{0}' AND DeviceID = {1}", signalReference, deviceID))) == 0) { historianID = device.ConvertNullableField<int>("HistorianID"); company = (string)database.Connection.ExecuteScalar(string.Format("SELECT MapAcronym FROM Company WHERE ID = {0}", device.ConvertNullableField<int>("CompanyID") ?? 0)); if (string.IsNullOrEmpty(company)) company = configFile.Settings["systemSettings"]["CompanyAcronym"].Value.TruncateRight(3); pointTag = CreatePointTag(company, acronym, null, "QUAL"); description = string.Format("{0} Time Quality Flags", device.Field<string>("Name")); query = database.ParameterizedQueryString("INSERT INTO Measurement(HistorianID, DeviceID, PointTag, SignalTypeID, PhasorSourceIndex, " + "SignalReference, Description, Enabled) VALUES({0}, {1}, {2}, {3}, NULL, {4}, {5}, 1)", "historianID", "deviceID", "pointTag", "signalTypeID", "signalReference", "description"); database.Connection.ExecuteNonQuery(query, DataExtensions.DefaultTimeoutDuration, historianID.HasValue ? (object)historianID.Value : (object)DBNull.Value, deviceID, pointTag, qualityFlagsSignalTypeID, signalReference, description); } } // Make sure needed device statistic measurements exist, currently statistics are only associated with phasor devices so we filter based on protocol foreach (DataRow device in database.Connection.RetrieveData(database.AdapterType, string.Format("SELECT * FROM Device WHERE IsConcentrator = 0 AND NodeID = {0} AND ProtocolID IN ({1})", nodeIDQueryString, protocolIDs)).Rows) { foreach (DataRow statistic in deviceStatistics) { string oldAcronym; string oldSignalReference; signalIndex = statistic.ConvertField<int>("SignalIndex"); oldAcronym = device.Field<string>("Acronym"); acronym = oldAcronym + "!PMU"; oldSignalReference = SignalReference.ToString(oldAcronym, SignalKind.Statistic, signalIndex); signalReference = SignalReference.ToString(acronym, SignalKind.Statistic, signalIndex); // If the original format for device statistics is found in the database, update to new format if (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Measurement WHERE SignalReference='{0}' AND HistorianID={1}", oldSignalReference, statHistorianID))) > 0) database.Connection.ExecuteNonQuery(string.Format("UPDATE Measurement SET SignalReference='{0}' WHERE SignalReference='{1}' AND HistorianID={2}", signalReference, oldSignalReference, statHistorianID)); else if (!skipOptimization) break; } } statusMessage("Validating input stream measurements..."); // Make sure devices associated with a concentrator do not have any extraneous input stream statistic measurements - this can happen // when a device was once a direct connect device but now is part of a concentrator... foreach (DataRow inputStream in database.Connection.RetrieveData(database.AdapterType, string.Format("SELECT * FROM Device WHERE (IsConcentrator = 0 AND ParentID IS NOT NULL) AND NodeID = {0} AND ProtocolID IN ({1})", nodeIDQueryString, protocolIDs)).Rows) { firstStatisticExisted = false; foreach (DataRow statistic in inputStreamStatistics) { acronym = inputStream.Field<string>("Acronym") + "!IS"; signalIndex = statistic.ConvertField<int>("SignalIndex"); signalReference = SignalReference.ToString(acronym, SignalKind.Statistic, signalIndex); // To reduce time required to execute these steps, only first statistic is verified to exist if (!skipOptimization && !firstStatisticExisted) { firstStatisticExisted = (Convert.ToInt32(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM Measurement WHERE SignalReference='{0}'", signalReference))) > 0); // If the first extraneous input statistic doesn't exist, we assume no others do as well if (!firstStatisticExisted) break; } // Remove extraneous input statistics database.Connection.ExecuteNonQuery(string.Format("DELETE FROM Measurement WHERE SignalReference = '{0}'", signalReference)); } } } try { // Determine how many changes were made to output streams, devices, and measurements - // if no changes were made, we can skip the next few steps if (trackedTables.Contains("OutputStream") && trackedTables.Contains("OutputStreamDevice") && trackedTables.Contains("OutputStreamMeasurement") && trackedTables.Contains("Measurement")) changes = Convert.ToUInt64(database.Connection.ExecuteScalar(string.Format("SELECT COUNT(*) FROM TrackedChange WHERE (TableName = 'OutputStream' OR TableName = 'OutputStreamDevice' OR TableName = 'OutputStreamMeasurement' OR TableName = 'Measurement') AND ID > {0}", trackingVersion))); else changes = ulong.MaxValue; } catch { changes = ulong.MaxValue; } if (skipOptimization || changes != 0L) { statusMessage("Validating output stream measurements..."); // Make sure needed output stream statistic measurements exist foreach (DataRow outputStream in database.Connection.RetrieveData(database.AdapterType, string.Format("SELECT * FROM OutputStream WHERE NodeID = {0}", nodeIDQueryString)).Rows) { adapterID = outputStream.ConvertField<int>("ID"); // Load devices acronyms associated with this output stream List<string> deviceAcronyms = database.Connection.RetrieveData(database.AdapterType, string.Format("SELECT Acronym FROM OutputStreamDevice WHERE AdapterID = {0} AND NodeID = {1}", adapterID, nodeIDQueryString)) .AsEnumerable() .Select(row => row.Field<string>("Acronym")) .ToList(); // Since measurements can be added to the output stream device itself (e.g., quality flags) - we add it as a valid mapping destination as well deviceAcronyms.Add(outputStream.Field<string>("Acronym")); // Sort list so binary search can be used to speed lookups deviceAcronyms.Sort(StringComparer.OrdinalIgnoreCase); // Validate measurements associated with this output stream foreach (DataRow outputStreamMeasurement in database.Connection.RetrieveData(database.AdapterType, string.Format("SELECT * FROM OutputStreamMeasurement WHERE AdapterID = {0} AND NodeID = {1}", adapterID, nodeIDQueryString)).Rows) { // Parse output stream measurement signal reference deviceSignalReference = new SignalReference(outputStreamMeasurement.Field<string>("SignalReference")); // Validate that the signal reference is associated with one of the output stream's devices if (deviceAcronyms.BinarySearch(deviceSignalReference.Acronym, StringComparer.OrdinalIgnoreCase) < 0) { // This measurement has a signal reference for a device that is not part of the associated output stream, so we mark it for deletion measurementIDsToDelete.Add(outputStreamMeasurement.ConvertField<int>("ID")); } // Validate that the signal reference type is valid for an output stream if (!validOutputSignalKinds.Any(validSignalKind => deviceSignalReference.Kind == validSignalKind)) { // This measurement has a signal reference type that is not valid for an output stream, so we mark it for deletion measurementIDsToDelete.Add(outputStreamMeasurement.ConvertField<int>("ID")); } } } } if (measurementIDsToDelete.Count > 0) { statusMessage(string.Format("Removing {0} unused output stream device measurements...", measurementIDsToDelete.Count)); foreach (int measurementID in measurementIDsToDelete) { database.Connection.ExecuteNonQuery(string.Format("DELETE FROM OutputStreamMeasurement WHERE ID = {0} AND NodeID = {1}", measurementID, nodeIDQueryString)); } } if (renameAllPointTags) { statusMessage("Renaming all point tags..."); string device, vendor, signalAcronym; char? phase; int? vendorDeviceID; SignalReference signal; foreach (DataRow measurement in database.Connection.RetrieveData(database.AdapterType, "SELECT SignalID, CompanyAcronym, DeviceAcronym, VendorDeviceID, SignalReference, SignalAcronym, Phase FROM MeasurementDetail WHERE SignalAcronym <> 'STAT' AND Internal <> 0 AND Subscribed = 0").Rows) { company = measurement.ConvertField<string>("CompanyAcronym"); if (string.IsNullOrEmpty(company)) company = configFile.Settings["systemSettings"]["CompanyAcronym"].Value.TruncateRight(3); device = measurement.ConvertField<string>("DeviceAcronym"); if ((object)device != null) { vendorDeviceID = measurement.ConvertNullableField<int>("VendorDeviceID"); if (vendorDeviceID.HasValue) vendor = (string)database.Connection.ExecuteScalar("SELECT Acronym FROM Vendor WHERE ID = " + vendorDeviceID.Value); else vendor = null; signalAcronym = measurement.ConvertField<string>("SignalAcronym"); try { signal = new SignalReference(measurement.ConvertField<string>("SignalReference")); signalIndex = signal.Index; if (signalIndex <= 0) signalIndex = -1; } catch { signalIndex = -1; } phase = measurement.ConvertNullableField<char>("Phase"); database.Connection.ExecuteNonQuery(string.Format("UPDATE Measurement SET PointTag = '{0}' WHERE SignalID = '{1}'", CreatePointTag(company, device, vendor, signalAcronym, signalIndex, phase ?? '_'), database.Guid(measurement, "SignalID"))); } } } if (skipOptimization || renameAllPointTags) { // If skipOptimization is set to true, automatically set it back to false const string clearParametersQuery = "UPDATE DataOperation SET Arguments = '' " + "WHERE AssemblyName = 'PhasorProtocolAdapters.dll' " + "AND TypeName = 'PhasorProtocolAdapters.CommonPhasorServices' " + "AND MethodName = 'PhasorDataSourceValidation'"; database.Connection.ExecuteNonQuery(clearParametersQuery); } }
/// <summary> /// Constructs a new <see cref="SignalReferenceMeasurement"/> from the specified parameters. /// </summary> /// <param name="measurement">Source <see cref="IMeasurement"/> value.</param> /// <param name="signalReference">Associated <see cref="SignalReference"/>.</param> public SignalReferenceMeasurement(IMeasurement measurement, SignalReference signalReference) { m_measurement = measurement; SignalReference = signalReference; }
public void UpdateConfiguration() { const int labelLength = 16; Dictionary<string, int> signalCellIndexes = new Dictionary<string, int>(); ConfigurationCell cell; SignalReference signal; SignalReference[] signals; MeasurementKey measurementKey; PhasorType phasorType; AnalogType analogType; char phaseType; string label; int order; uint scale; double offset; // 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["OutputStreams"].Select(string.Format("StreamID={0}", ID))) { try { // Create a new configuration cell cell = new ConfigurationCell(m_baseConfigurationFrame, ushort.Parse(deviceRow["ID"].ToString()), deviceRow["IsVirtual"].ToNonNullString("false").ParseBoolean()); // The base class defaults to floating-point, polar values, derived classes can change cell.PhasorDataFormat = DataFormat.FloatingPoint; cell.PhasorCoordinateFormat = CoordinateFormat.Polar; cell.FrequencyDataFormat = DataFormat.FloatingPoint; cell.AnalogDataFormat = DataFormat.FloatingPoint; cell.IDLabel = deviceRow["Acronym"].ToString().Trim(); cell.StationName = deviceRow["Name"].ToString().TruncateRight(cell.MaximumStationNameLength).Trim(); // Define all the phasors configured for this device foreach (DataRow phasorRow in DataSource.Tables["OutputStreamPhasors"].Select(string.Format("DeviceID={0}", cell.IDCode), "Order")) { order = int.Parse(phasorRow["Order"].ToNonNullString("0")); label = phasorRow["Label"].ToNonNullString("Phasor " + order).Trim().RemoveDuplicateWhiteSpace().TruncateRight(labelLength - 4); phasorType = phasorRow["PhasorType"].ToNonNullString("V").Trim().ToUpper().StartsWith("V") ? PhasorType.Voltage : PhasorType.Current; phaseType = phasorRow["PhaseType"].ToNonNullString("+").Trim().ToUpper()[0]; cell.PhasorDefinitions.Add( new PhasorDefinition( cell, GeneratePhasorLabel(label, phaseType, phasorType), phasorType, 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("OutputStreamAnalogs")) { foreach (DataRow analogRow in DataSource.Tables["OutputStreamAnalogs"].Select(string.Format("DeviceID={0}", cell.IDCode), "Order")) { order = int.Parse(analogRow["Order"].ToNonNullString("0")); label = analogRow["Label"].ToNonNullString("Analog " + order).Trim().RemoveDuplicateWhiteSpace().TruncateRight(labelLength); scale = uint.Parse(analogRow["Scale"].ToNonNullString("1")); offset = double.Parse(analogRow["Offset"].ToNonNullString("0.0")); analogType = analogRow["AnalogType"].ToNonNullString("SinglePointOnWave").ConvertToType<AnalogType>(); cell.AnalogDefinitions.Add( new AnalogDefinition( cell, label, scale, offset, analogType)); } } // Optionally define all the digitals configured for this device if (DataSource.Tables.Contains("OutputStreamDigitals")) { foreach (DataRow digitalRow in DataSource.Tables["OutputStreamDigitals"].Select(string.Format("DeviceID={0}", cell.IDCode), "Order")) { order = int.Parse(digitalRow["Order"].ToNonNullString("0")); label = digitalRow["Label"].ToNonNullString("Digital " + order).Trim().RemoveDuplicateWhiteSpace().TruncateRight(labelLength); cell.DigitalDefinitions.Add( new DigitalDefinition( cell, label)); } } 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); // Create a new signal reference dictionary indexed on measurement keys m_signalReferences = new Dictionary<MeasurementKey, SignalReference[]>(); // Define measurement to signals cross reference dictionary foreach (DataRow measurementRow in DataSource.Tables["OutputStreamMeasurements"].Select(string.Format("StreamID={0}", ID))) { try { // Create a new signal reference signal = new SignalReference(measurementRow["SignalReference"].ToString()); // 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 indicies locally to speed up initialization as we'll be // requesting them for the same devices over and over signal.CellIndex = m_configurationFrame.Cells.IndexOfIDLabel(signal.Acronym); signalCellIndexes.Add(signal.Acronym, signal.CellIndex); } // Define measurement key measurementKey = new MeasurementKey(uint.Parse(measurementRow["PointID"].ToString()), measurementRow["Historian"].ToString()); // It is possible, but not as common, that a measurement will have multiple destinations // within an outgoing data stream frame, hence the following if (m_signalReferences.TryGetValue(measurementKey, out signals)) { // Add a new signal to existing collection List<SignalReference> signalList = new List<SignalReference>(signals); signalList.Add(signal); m_signalReferences[measurementKey] = signalList.ToArray(); } else { // Add new signal to new collection signals = new SignalReference[1]; signals[0] = signal; m_signalReferences.Add(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"].ToString().Trim(), ex.Message), ex)); } } // Assign action adapter input measurement keys InputMeasurementKeys = m_signalReferences.Keys.ToArray(); // Create a new protocol specific configuration frame m_configurationFrame = CreateNewConfigurationFrame(m_baseConfigurationFrame); // Cache new protocol specific configuration frame CacheConfigurationFrame(m_configurationFrame); }
/// <summary> /// Constructs a new <see cref="SignalReferenceMeasurement"/> from the specified parameters. /// </summary> /// <param name="measurement">Source <see cref="IMeasurement"/> value.</param> /// <param name="signalReference">Associated <see cref="SignalReference"/>.</param> public SignalReferenceMeasurement(IMeasurement measurement, SignalReference signalReference) { m_measurement = measurement; m_signalReference = signalReference; }
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); }
internal static global::System.Runtime.InteropServices.HandleRef getCPtr(SignalReference obj) { return((obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr); }