//******************************************************************************************************// CsvInputAdapter.cs - Gbtc//// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may// not use this file except in compliance with the License. You may obtain a copy of the License at://// http://www.opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 04/06/2010 - Stephen C. Wills// Generated original version of source code.// 07/03/2012 - J. Ritchie Carroll// Added high-resolution input timer, auto-repeat and transverse operational mode.// 12/13/2012 - Starlynn Danyelle Gilliam// Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.ComponentModel;using System.Diagnostics;using System.IO;using System.Linq;using System.Text;using System.Threading;using System.Timers;using GSF;using GSF.Diagnostics;using GSF.TimeSeries;using GSF.TimeSeries.Adapters;using Timer = System.Timers.Timer; namespace CsvAdapters{ /// <summary> /// Represents an input adapter that reads measurements from a CSV file. /// </summary> [Description("CSV: Reads measurements from a CSV file")] public class CsvInputAdapter : InputAdapterBase { #region [ Members ] // Fields private string m_fileName; private StreamReader m_inStream; private string m_header; private readonly Dictionary<string, int> m_columns; private readonly Dictionary<int, IMeasurement> m_columnMappings; private double m_inputInterval; private int m_measurementsPerInterval; private int m_skipRows; private bool m_simulateTimestamp; private bool m_transverse; private bool m_autoRepeat; private Timer m_looseTimer; private PrecisionInputTimer m_precisionTimer; private long[] m_subsecondDistribution; private long m_previousSecond; private int m_previousFrameIndex; private bool m_disposed; #endregion #region [ Constructors ] /// <summary> /// Initializes a new instance of the <see cref="CsvInputAdapter"/> class. /// </summary> public CsvInputAdapter() { m_fileName = "measurements.csv"; m_columns = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase); m_columnMappings = new Dictionary<int, IMeasurement>(); m_inputInterval = 33.333333; m_measurementsPerInterval = 5; // Set minimum timer resolution to one millisecond to improve timer accuracy PrecisionTimer.SetMinimumTimerResolution(1); } #endregion #region [ Properties ] /// <summary> /// Gets or sets the name of the CSV file from which measurements will be read. /// </summary> [ConnectionStringParameter, Description("Define the name of the CSV file from which measurements will be read."), DefaultValue("measurements.csv"), CustomConfigurationEditor("GSF.TimeSeries.UI.WPF.dll", "GSF.TimeSeries.UI.Editors.FileDialogEditor", "type=open; checkFileExists=true; defaultExt=.csv; filter=CSV files|*.csv|AllFiles|*.*")] public string FileName { get { return m_fileName; } set { m_fileName = value; } } /// <summary> /// Gets or sets the interval of time between sending frames into the concentrator. /// </summary> [ConnectionStringParameter, Description("Define the interval of time, in milliseconds, between sending frames into the concentrator."), DefaultValue(33.333333)] public double InputInterval { get { return m_inputInterval; } set { m_inputInterval = value; } } /// <summary> /// Gets or sets value that determines if the CSV input file data should be replayed repeatedly. /// </summary> [ConnectionStringParameter, Description("Define if the CSV input file data should be replayed repeatedly."), DefaultValue(false)] public bool AutoRepeat { get { return m_autoRepeat; } set { m_autoRepeat = value; } } /// <summary> /// Gets or sets number of lines to skip in the source file before the header line is encountered. /// </summary> [ConnectionStringParameter, Description("Define the number of lines to skip in the source file before the header line is encountered."), DefaultValue(0)] public int SkipRows { get { return m_skipRows; } set { m_skipRows = value; } } /// <summary> /// Gets or sets flag that determines if a high-resolution precision timer should be used for CSV file based input. /// </summary> /// <remarks> /// Useful when input frames need be accurately time-aligned to the local clock to better simulate /// an input device and calculate downstream latencies.<br/> /// This is only applicable when connection is made to a file for replay purposes. /// </remarks> [ConnectionStringParameter, Description("Determines if a high-resolution precision timer should be used for CSV file based input."), DefaultValue(false)] public bool UseHighResolutionInputTimer { get { return (object)m_precisionTimer != null; } set { // Note that a 1-ms timer and debug mode don't mix, so the high-resolution timer is disabled while debugging if (value && (object)m_precisionTimer == null && !Debugger.IsAttached) m_precisionTimer = PrecisionInputTimer.Attach((int)(1000.0D / m_inputInterval), ex => OnProcessException(MessageLevel.Warning, ex)); else if (!value && m_precisionTimer != null) PrecisionInputTimer.Detach(ref m_precisionTimer); } } /// <summary> /// Gets or sets the number of measurements that are read from the CSV file in each frame. /// </summary> [ConnectionStringParameter, Description("Define the number of measurements that are read from the CSV file in each frame."), DefaultValue(5)] public int MeasurementsPerInterval { get { return m_measurementsPerInterval; } set { m_measurementsPerInterval = value; } } /// <summary> /// Gets or sets a value that determines whether CSV file is in transverse mode for real-time concentration. /// </summary> [ConnectionStringParameter, Description("Indicate whether CSV file is in transverse mode for real-time concentration."), DefaultValue(false)] public bool TransverseMode { get { return m_transverse; } set { m_transverse = value; } } /// <summary> /// Gets or sets a value that determines whether timestamps are /// simulated for the purposes of real-time concentration. /// </summary> [ConnectionStringParameter, Description("Indicate whether timestamps are simulated for real-time concentration."), DefaultValue(false)] public bool SimulateTimestamp { get { return m_simulateTimestamp; } set { m_simulateTimestamp = value; } } /// <summary> /// Defines the column mappings must defined: e.g., 0=Timestamp; 1=PPA:12; 2=PPA13. /// </summary> [ConnectionStringParameter, Description("Defines the column mappings must defined: e.g., \"0=Timestamp; 1=PPA:12; 2=PPA13\"."), DefaultValue("")] public int ColumnMappings { get; set; } /// <summary> /// Gets a flag that determines if this <see cref="CsvInputAdapter"/> /// uses an asynchronous connection. /// </summary> protected override bool UseAsyncConnect => false; /// <summary> /// Returns the detailed status of this <see cref="CsvInputAdapter"/>. /// </summary> public override string Status { get { StringBuilder status = new StringBuilder(); status.Append(base.Status); status.AppendLine(); status.AppendFormat(" File name: {0}", m_fileName); status.AppendLine(); status.AppendFormat(" File header: {0}", m_header); status.AppendLine(); status.AppendFormat(" Input interval: {0}", m_inputInterval); status.AppendLine(); status.AppendFormat(" Measurements per interval: {0}", m_measurementsPerInterval); status.AppendLine(); status.AppendFormat(" Using traverse format: {0}", m_transverse); status.AppendLine(); status.AppendFormat(" Auto-repeat: {0}", m_autoRepeat); status.AppendLine(); status.AppendFormat(" Precision input timer: {0}", UseHighResolutionInputTimer ? "Enabled" : "Offline"); status.AppendLine(); status.AppendFormat(" Lines to skip: {0}", m_skipRows); status.AppendLine(); if ((object)m_precisionTimer != null) { status.AppendFormat(" Timer resynchronizations: {0}", m_precisionTimer.Resynchronizations); status.AppendLine(); } return status.ToString(); } } /// <summary> /// Gets the flag indicating if this adapter supports temporal processing. /// </summary> public override bool SupportsTemporalProcessing => true; #endregion #region [ Methods ] /// <summary> /// Releases the unmanaged resources used by the <see cref="CsvInputAdapter"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if (UseHighResolutionInputTimer) { PrecisionInputTimer.Detach(ref m_precisionTimer); } else if ((object)m_looseTimer != null) { m_looseTimer.Stop(); m_looseTimer.Dispose(); } m_looseTimer = null; if ((object)m_inStream != null) { m_inStream.Close(); m_inStream.Dispose(); } m_inStream = null; // Clear minimum timer resolution. PrecisionTimer.ClearMinimumTimerResolution(1); } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } /// <summary> /// Initializes this <see cref="CsvInputAdapter"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; string setting; // Load optional parameters if (settings.TryGetValue("fileName", out setting)) m_fileName = setting; if (settings.TryGetValue("inputInterval", out setting)) m_inputInterval = double.Parse(setting); if (settings.TryGetValue("measurementsPerInterval", out setting)) m_measurementsPerInterval = int.Parse(setting); if (settings.TryGetValue("simulateTimestamp", out setting)) m_simulateTimestamp = setting.ParseBoolean(); if (settings.TryGetValue("transverse", out setting) || settings.TryGetValue("transverseMode", out setting)) m_transverse = setting.ParseBoolean(); if (settings.TryGetValue("autoRepeat", out setting)) m_autoRepeat = setting.ParseBoolean(); if (settings.TryGetValue("skipRows", out setting)) int.TryParse(setting, out m_skipRows); if (m_skipRows < 0) m_skipRows = 0; settings.TryGetValue("useHighResolutionInputTimer", out setting); if (string.IsNullOrEmpty(setting)) setting = "false"; UseHighResolutionInputTimer = setting.ParseBoolean(); if (!UseHighResolutionInputTimer) m_looseTimer = new Timer(); if (m_transverse) { // Load column mappings: if (settings.TryGetValue("columnMappings", out setting)) { Dictionary<int, string> columnMappings = new Dictionary<int, string>(); int index; foreach (KeyValuePair<string, string> mapping in setting.ParseKeyValuePairs()) { if (int.TryParse(mapping.Key, out index)) columnMappings[index] = mapping.Value; } if (!m_simulateTimestamp && !columnMappings.Values.Contains("Timestamp", StringComparer.OrdinalIgnoreCase)) throw new InvalidOperationException("One of the column mappings must be defined as a \"Timestamp\": e.g., columnMappings={0=Timestamp; 1=PPA:12; 2=PPA13}."); // In transverse mode, maximum measurements per interval is set to maximum columns in input file m_measurementsPerInterval = columnMappings.Keys.Max() + 1; // Auto-assign output measurements based on column mappings OutputMeasurements = columnMappings.Where(kvp => string.Compare(kvp.Value, "Timestamp", StringComparison.OrdinalIgnoreCase) != 0).Select(kvp => { string measurementID = kvp.Value; IMeasurement measurement = new Measurement(); MeasurementKey key; Guid id; if (Guid.TryParse(measurementID, out id)) { key = MeasurementKey.LookUpBySignalID(id); } else { MeasurementKey.TryParse(measurementID, out key); } measurement.Metadata = key.Metadata; // Associate measurement with column index m_columnMappings[kvp.Key] = measurement; return measurement; }).ToArray(); int timestampColumn = columnMappings.First(kvp => string.Compare(kvp.Value, "Timestamp", StringComparison.OrdinalIgnoreCase) == 0).Key; // Reserve a column mapping for timestamp value IMeasurement timestampMeasurement = new Measurement { Metadata = new MeasurementMetadata(null, "Timestamp", 0, 1, null) }; m_columnMappings[timestampColumn] = timestampMeasurement; } else { throw new InvalidOperationException("Column mappings must be defined when using transverse format: e.g., columnMappings={0=Timestamp; 1=PPA:12; 2=PPA:13}."); } } // Override input interval based on temporal processing interval if it's not set to default if (ProcessingInterval > -1) m_inputInterval = ProcessingInterval == 0 ? 1 : ProcessingInterval; if ((object)m_looseTimer != null) { m_looseTimer.Interval = m_inputInterval; m_looseTimer.AutoReset = true; m_looseTimer.Elapsed += m_looseTimer_Elapsed; } } /// <summary> /// Attempts to connect to this <see cref="CsvInputAdapter"/>. /// </summary> protected override void AttemptConnection() { string[] headings; m_inStream = new StreamReader(File.Open(m_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); // Skip specified number of header lines that exist before column heading definitions for (int i = 0; i < m_skipRows; i++) m_inStream.ReadLine(); m_columns.Clear(); m_header = m_inStream.ReadLine(); headings = m_header.ToNonNullString().Split(','); for (int i = 0; i < headings.Length; i++) m_columns.Add(headings[i], i); if (UseHighResolutionInputTimer) {Lines 1–500 of 721. Load more
//****************************************************************************************************** // CsvInputAdapter.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 04/06/2010 - Stephen C. Wills // Generated original version of source code. // 07/03/2012 - J. Ritchie Carroll // Added high-resolution input timer, auto-repeat and transverse operational mode. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Timers; using GSF; using GSF.Diagnostics; using GSF.TimeSeries; using GSF.TimeSeries.Adapters; using Timer = System.Timers.Timer; namespace CsvAdapters { /// <summary> /// Represents an input adapter that reads measurements from a CSV file. /// </summary> [Description("CSV: Reads measurements from a CSV file")] public class CsvInputAdapter : InputAdapterBase { #region [ Members ] // Fields private string m_fileName; private StreamReader m_inStream; private string m_header; private readonly Dictionary<string, int> m_columns; private readonly Dictionary<int, IMeasurement> m_columnMappings; private double m_inputInterval; private int m_measurementsPerInterval; private int m_skipRows; private bool m_simulateTimestamp; private bool m_transverse; private bool m_autoRepeat; private Timer m_looseTimer; private PrecisionInputTimer m_precisionTimer; private long[] m_subsecondDistribution; private long m_previousSecond; private int m_previousFrameIndex; private bool m_disposed; #endregion #region [ Constructors ] /// <summary> /// Initializes a new instance of the <see cref="CsvInputAdapter"/> class. /// </summary> public CsvInputAdapter() { m_fileName = "measurements.csv"; m_columns = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase); m_columnMappings = new Dictionary<int, IMeasurement>(); m_inputInterval = 33.333333; m_measurementsPerInterval = 5; // Set minimum timer resolution to one millisecond to improve timer accuracy PrecisionTimer.SetMinimumTimerResolution(1); } #endregion #region [ Properties ] /// <summary> /// Gets or sets the name of the CSV file from which measurements will be read. /// </summary> [ConnectionStringParameter, Description("Define the name of the CSV file from which measurements will be read."), DefaultValue("measurements.csv"), CustomConfigurationEditor("GSF.TimeSeries.UI.WPF.dll", "GSF.TimeSeries.UI.Editors.FileDialogEditor", "type=open; checkFileExists=true; defaultExt=.csv; filter=CSV files|*.csv|AllFiles|*.*")] public string FileName { get { return m_fileName; } set { m_fileName = value; } } /// <summary> /// Gets or sets the interval of time between sending frames into the concentrator. /// </summary> [ConnectionStringParameter, Description("Define the interval of time, in milliseconds, between sending frames into the concentrator."), DefaultValue(33.333333)] public double InputInterval { get { return m_inputInterval; } set { m_inputInterval = value; } } /// <summary> /// Gets or sets value that determines if the CSV input file data should be replayed repeatedly. /// </summary> [ConnectionStringParameter, Description("Define if the CSV input file data should be replayed repeatedly."), DefaultValue(false)] public bool AutoRepeat { get { return m_autoRepeat; } set { m_autoRepeat = value; } } /// <summary> /// Gets or sets number of lines to skip in the source file before the header line is encountered. /// </summary> [ConnectionStringParameter, Description("Define the number of lines to skip in the source file before the header line is encountered."), DefaultValue(0)] public int SkipRows { get { return m_skipRows; } set { m_skipRows = value; } } /// <summary> /// Gets or sets flag that determines if a high-resolution precision timer should be used for CSV file based input. /// </summary> /// <remarks> /// Useful when input frames need be accurately time-aligned to the local clock to better simulate /// an input device and calculate downstream latencies.<br/> /// This is only applicable when connection is made to a file for replay purposes. /// </remarks> [ConnectionStringParameter, Description("Determines if a high-resolution precision timer should be used for CSV file based input."), DefaultValue(false)] public bool UseHighResolutionInputTimer { get { return (object)m_precisionTimer != null; } set { // Note that a 1-ms timer and debug mode don't mix, so the high-resolution timer is disabled while debugging if (value && (object)m_precisionTimer == null && !Debugger.IsAttached) m_precisionTimer = PrecisionInputTimer.Attach((int)(1000.0D / m_inputInterval), ex => OnProcessException(MessageLevel.Warning, ex)); else if (!value && m_precisionTimer != null) PrecisionInputTimer.Detach(ref m_precisionTimer); } } /// <summary> /// Gets or sets the number of measurements that are read from the CSV file in each frame. /// </summary> [ConnectionStringParameter, Description("Define the number of measurements that are read from the CSV file in each frame."), DefaultValue(5)] public int MeasurementsPerInterval { get { return m_measurementsPerInterval; } set { m_measurementsPerInterval = value; } } /// <summary> /// Gets or sets a value that determines whether CSV file is in transverse mode for real-time concentration. /// </summary> [ConnectionStringParameter, Description("Indicate whether CSV file is in transverse mode for real-time concentration."), DefaultValue(false)] public bool TransverseMode { get { return m_transverse; } set { m_transverse = value; } } /// <summary> /// Gets or sets a value that determines whether timestamps are /// simulated for the purposes of real-time concentration. /// </summary> [ConnectionStringParameter, Description("Indicate whether timestamps are simulated for real-time concentration."), DefaultValue(false)] public bool SimulateTimestamp { get { return m_simulateTimestamp; } set { m_simulateTimestamp = value; } } /// <summary> /// Defines the column mappings must defined: e.g., 0=Timestamp; 1=PPA:12; 2=PPA13. /// </summary> [ConnectionStringParameter, Description("Defines the column mappings must defined: e.g., \"0=Timestamp; 1=PPA:12; 2=PPA13\"."), DefaultValue("")] public int ColumnMappings { get; set; } /// <summary> /// Gets a flag that determines if this <see cref="CsvInputAdapter"/> /// uses an asynchronous connection. /// </summary> protected override bool UseAsyncConnect => false; /// <summary> /// Returns the detailed status of this <see cref="CsvInputAdapter"/>. /// </summary> public override string Status { get { StringBuilder status = new StringBuilder(); status.Append(base.Status); status.AppendLine(); status.AppendFormat(" File name: {0}", m_fileName); status.AppendLine(); status.AppendFormat(" File header: {0}", m_header); status.AppendLine(); status.AppendFormat(" Input interval: {0}", m_inputInterval); status.AppendLine(); status.AppendFormat(" Measurements per interval: {0}", m_measurementsPerInterval); status.AppendLine(); status.AppendFormat(" Using traverse format: {0}", m_transverse); status.AppendLine(); status.AppendFormat(" Auto-repeat: {0}", m_autoRepeat); status.AppendLine(); status.AppendFormat(" Precision input timer: {0}", UseHighResolutionInputTimer ? "Enabled" : "Offline"); status.AppendLine(); status.AppendFormat(" Lines to skip: {0}", m_skipRows); status.AppendLine(); if ((object)m_precisionTimer != null) { status.AppendFormat(" Timer resynchronizations: {0}", m_precisionTimer.Resynchronizations); status.AppendLine(); } return status.ToString(); } } /// <summary> /// Gets the flag indicating if this adapter supports temporal processing. /// </summary> public override bool SupportsTemporalProcessing => true; #endregion #region [ Methods ] /// <summary> /// Releases the unmanaged resources used by the <see cref="CsvInputAdapter"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if (UseHighResolutionInputTimer) { PrecisionInputTimer.Detach(ref m_precisionTimer); } else if ((object)m_looseTimer != null) { m_looseTimer.Stop(); m_looseTimer.Dispose(); } m_looseTimer = null; if ((object)m_inStream != null) { m_inStream.Close(); m_inStream.Dispose(); } m_inStream = null; // Clear minimum timer resolution. PrecisionTimer.ClearMinimumTimerResolution(1); } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } /// <summary> /// Initializes this <see cref="CsvInputAdapter"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; string setting; // Load optional parameters if (settings.TryGetValue("fileName", out setting)) m_fileName = setting; if (settings.TryGetValue("inputInterval", out setting)) m_inputInterval = double.Parse(setting); if (settings.TryGetValue("measurementsPerInterval", out setting)) m_measurementsPerInterval = int.Parse(setting); if (settings.TryGetValue("simulateTimestamp", out setting)) m_simulateTimestamp = setting.ParseBoolean(); if (settings.TryGetValue("transverse", out setting) || settings.TryGetValue("transverseMode", out setting)) m_transverse = setting.ParseBoolean(); if (settings.TryGetValue("autoRepeat", out setting)) m_autoRepeat = setting.ParseBoolean(); if (settings.TryGetValue("skipRows", out setting)) int.TryParse(setting, out m_skipRows); if (m_skipRows < 0) m_skipRows = 0; settings.TryGetValue("useHighResolutionInputTimer", out setting); if (string.IsNullOrEmpty(setting)) setting = "false"; UseHighResolutionInputTimer = setting.ParseBoolean(); if (!UseHighResolutionInputTimer) m_looseTimer = new Timer(); if (m_transverse) { // Load column mappings: if (settings.TryGetValue("columnMappings", out setting)) { Dictionary<int, string> columnMappings = new Dictionary<int, string>(); int index; foreach (KeyValuePair<string, string> mapping in setting.ParseKeyValuePairs()) { if (int.TryParse(mapping.Key, out index)) columnMappings[index] = mapping.Value; } if (!m_simulateTimestamp && !columnMappings.Values.Contains("Timestamp", StringComparer.OrdinalIgnoreCase)) throw new InvalidOperationException("One of the column mappings must be defined as a \"Timestamp\": e.g., columnMappings={0=Timestamp; 1=PPA:12; 2=PPA13}."); // In transverse mode, maximum measurements per interval is set to maximum columns in input file m_measurementsPerInterval = columnMappings.Keys.Max() + 1; // Auto-assign output measurements based on column mappings OutputMeasurements = columnMappings.Where(kvp => string.Compare(kvp.Value, "Timestamp", StringComparison.OrdinalIgnoreCase) != 0).Select(kvp => { string measurementID = kvp.Value; IMeasurement measurement = new Measurement(); MeasurementKey key; Guid id; if (Guid.TryParse(measurementID, out id)) { key = MeasurementKey.LookUpBySignalID(id); } else { MeasurementKey.TryParse(measurementID, out key); } measurement.Metadata = key.Metadata; // Associate measurement with column index m_columnMappings[kvp.Key] = measurement; return measurement; }).ToArray(); int timestampColumn = columnMappings.First(kvp => string.Compare(kvp.Value, "Timestamp", StringComparison.OrdinalIgnoreCase) == 0).Key; // Reserve a column mapping for timestamp value IMeasurement timestampMeasurement = new Measurement { Metadata = new MeasurementMetadata(null, "Timestamp", 0, 1, null) }; m_columnMappings[timestampColumn] = timestampMeasurement; } else { throw new InvalidOperationException("Column mappings must be defined when using transverse format: e.g., columnMappings={0=Timestamp; 1=PPA:12; 2=PPA:13}."); } } // Override input interval based on temporal processing interval if it's not set to default if (ProcessingInterval > -1) m_inputInterval = ProcessingInterval == 0 ? 1 : ProcessingInterval; if ((object)m_looseTimer != null) { m_looseTimer.Interval = m_inputInterval; m_looseTimer.AutoReset = true; m_looseTimer.Elapsed += m_looseTimer_Elapsed; } } /// <summary> /// Attempts to connect to this <see cref="CsvInputAdapter"/>. /// </summary> protected override void AttemptConnection() { string[] headings; m_inStream = new StreamReader(File.Open(m_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); // Skip specified number of header lines that exist before column heading definitions for (int i = 0; i < m_skipRows; i++) m_inStream.ReadLine(); m_columns.Clear(); m_header = m_inStream.ReadLine(); headings = m_header.ToNonNullString().Split(','); for (int i = 0; i < headings.Length; i++) m_columns.Add(headings[i], i); if (UseHighResolutionInputTimer) {