Represents the schema for a configuration file in the COMTRADE file standard, IEEE Std C37.111-1999.
Exemple #1
0
        /// <summary>
        /// Creates a new COMTRADE configuration <see cref="Schema"/>.
        /// </summary>
        /// <param name="metadata">Schema <see cref="ChannelMetadata"/> records.</param>
        /// <param name="stationName">Station name for the schema.</param>
        /// <param name="deviceID">Device ID for the schema.</param>
        /// <param name="dataStartTime">Data start time.</param>
        /// <param name="sampleCount">Total data samples (i.e., total number of rows).</param>
        /// <param name="isBinary">Determines if data file should be binary or ASCII - defaults to <c>true</c> for binary.</param>
        /// <param name="timeFactor">Time factor to use in schema - defaults to 1000.</param>
        /// <param name="samplingRate">Desired sampling rate - defaults to 33.3333Hz.</param>
        /// <param name="nominalFrequency">Nominal frequency - defaults to 60Hz.</param>
        /// <param name="includeFracSecDefinition">Determines if the FRACSEC word digital definitions should be included - defaults to <c>true</c>.</param>
        /// <returns>New COMTRADE configuration <see cref="Schema"/>.</returns>
        /// <remarks>
        /// This function is primarily intended to create a configuration based on synchrophasor data
        /// (see Annex H: Schema for Phasor Data 2150 Using the COMTRADE File Standard in IEEE C37.111-2010),
        /// it may be necessary to manually create a schema object for other COMTRADE needs. You can call
        /// the <see cref="Schema.FileImage"/> property to return a string that that can be written to a file
        /// that will be the contents of the configuration file.
        /// </remarks>
        public static Schema CreateSchema(IEnumerable<ChannelMetadata> metadata, string stationName, string deviceID, Ticks dataStartTime, int sampleCount, bool isBinary = true, double timeFactor = 1.0D, double samplingRate = 30.0D, double nominalFrequency = 60.0D, bool includeFracSecDefinition = true)
        {
            Schema schema = new Schema();

            schema.StationName = stationName;
            schema.DeviceID = deviceID;

            SampleRate samplingFrequency = new SampleRate();
            samplingFrequency.Rate = samplingRate;
            samplingFrequency.EndSample = sampleCount;

            schema.SampleRates = new[] { samplingFrequency };

            Timestamp startTime;
            startTime.Value = dataStartTime;
            schema.StartTime = startTime;
            schema.TriggerTime = startTime;

            schema.FileType = isBinary ? FileType.Binary : FileType.Ascii;
            schema.TimeFactor = timeFactor;

            List<AnalogChannel> analogChannels = new List<AnalogChannel>();
            List<DigitalChannel> digitalChannels = new List<DigitalChannel>();
            int analogIndex = 1;
            int digitalIndex = 1;

            if (includeFracSecDefinition)
            {
                // Add default time quality digitals for IEEE C37.118 FRACSEC word. Note that these flags, as
                // defined in Annex H of the IEEE C37.111-2010 standard, assume full export was all from one
                // source device. This a poor assumption since data can be exported from historical data for any
                // number of points which could have come from any number of devices all with different FRACSEC
                // values. Regardless there is only one FRACSEC definition defined and, if included, it must
                // come as the first set of digitals in the COMTRADE configuration.
                for (int i = 0; i < 4; i++)
                {
                    digitalChannels.Add(new DigitalChannel
                    {
                        Index = digitalIndex,
                        Name = "TQ_CNT" + i,
                        PhaseID = "T" + digitalIndex++
                    });
                }

                digitalChannels.Add(new DigitalChannel
                {
                    Index = digitalIndex,
                    Name = "TQ_LSPND",
                    PhaseID = "T" + digitalIndex++
                });


                digitalChannels.Add(new DigitalChannel
                {
                    Index = digitalIndex,
                    Name = "TQ_LSOCC",
                    PhaseID = "T" + digitalIndex++
                });

                digitalChannels.Add(new DigitalChannel
                {
                    Index = digitalIndex,
                    Name = "TQ_LSDIR",
                    PhaseID = "T" + digitalIndex++
                });

                digitalChannels.Add(new DigitalChannel
                {
                    Index = digitalIndex,
                    Name = "RSV",
                    PhaseID = "T" + digitalIndex++
                });

                for (int i = 1; i < 9; i++)
                {
                    digitalChannels.Add(new DigitalChannel
                    {
                        Index = digitalIndex,
                        Name = "RESV" + i,
                        PhaseID = "T" + digitalIndex++
                    });
                }
            }

            // Add meta data for selected points sorted analogs followed by status flags then digitals
            foreach (ChannelMetadata record in metadata.OrderBy(m => m, ChannelMetadataSorter.Default))
            {
                if (record.IsDigital)
                {
                    // Every synchrophasor digital is 16-bits
                    for (int i = 0; i < 16; i++)
                    {
                        digitalChannels.Add(new DigitalChannel
                        {
                            Index = digitalIndex++,
                            Name = record.Name,
                            PhaseID = "B" + i.ToString("X")
                        });
                    }
                }
                else
                {
                    switch (record.SignalType)
                    {
                        case SignalType.IPHM: // Current Magnitude
                            analogChannels.Add(new AnalogChannel
                            {
                                Index = analogIndex++,
                                Name = record.Name,
                                PhaseID = "Pm",
                                Units = "A",
                                Multiplier = 0.05D
                            });
                            break;
                        case SignalType.VPHM: // Voltage Magnitude
                            analogChannels.Add(new AnalogChannel
                            {
                                Index = analogIndex++,
                                Name = record.Name,
                                PhaseID = "Pm",
                                Units = "V",
                                Multiplier = 5.77362D
                            });
                            break;
                        case SignalType.IPHA: // Current Phase Angle
                        case SignalType.VPHA: // Voltage Phase Angle
                            analogChannels.Add(new AnalogChannel
                            {
                                Index = analogIndex++,
                                Name = record.Name,
                                PhaseID = "Pa",
                                Units = "Rads",
                                Multiplier = 1.0E-4D
                            });
                            break;
                        case SignalType.FREQ: // Frequency
                            analogChannels.Add(new AnalogChannel
                            {
                                Index = analogIndex++,
                                Name = record.Name,
                                PhaseID = "F",
                                Units = "Hz",
                                Adder = (double)nominalFrequency,
                                Multiplier = 0.001D
                            });
                            break;
                        case SignalType.DFDT: // Frequency Delta (dF/dt)
                            analogChannels.Add(new AnalogChannel
                            {
                                Index = analogIndex++,
                                Name = record.Name,
                                PhaseID = "dF",
                                Units = "Hz/s",
                                Multiplier = 0.01D
                            });
                            break;
                        case SignalType.FLAG: // Status flags
                            // Add synchrophasor status flag specific digitals
                            int statusIndex = 0;

                            for (int i = 1; i < 5; i++)
                            {
                                digitalChannels.Add(new DigitalChannel
                                {
                                    Index = digitalIndex++,
                                    Name = record.Name + ":TRG" + i,
                                    PhaseID = "S" + statusIndex++.ToString("X")
                                });
                            }

                            for (int i = 1; i < 3; i++)
                            {
                                digitalChannels.Add(new DigitalChannel
                                {
                                    Index = digitalIndex++,
                                    Name = record.Name + ":UNLK" + i,
                                    PhaseID = "S" + statusIndex++.ToString("X")
                                });
                            }

                            for (int i = 1; i < 5; i++)
                            {
                                digitalChannels.Add(new DigitalChannel
                                {
                                    Index = digitalIndex++,
                                    Name = record.Name + ":SEC" + i,
                                    PhaseID = "S" + statusIndex++.ToString("X")
                                });
                            }

                            digitalChannels.Add(new DigitalChannel
                            {
                                Index = digitalIndex++,
                                Name = record.Name + ":CFGCH",
                                PhaseID = "S" + statusIndex++.ToString("X")
                            });

                            digitalChannels.Add(new DigitalChannel
                            {
                                Index = digitalIndex++,
                                Name = record.Name + ":PMUTR",
                                PhaseID = "S" + statusIndex++.ToString("X")
                            });

                            digitalChannels.Add(new DigitalChannel
                            {
                                Index = digitalIndex++,
                                Name = record.Name + ":SORT",
                                PhaseID = "S" + statusIndex++.ToString("X")
                            });

                            digitalChannels.Add(new DigitalChannel
                            {
                                Index = digitalIndex++,
                                Name = record.Name + ":SYNC",
                                PhaseID = "S" + statusIndex++.ToString("X")
                            });

                            digitalChannels.Add(new DigitalChannel
                            {
                                Index = digitalIndex++,
                                Name = record.Name + ":PMUERR",
                                PhaseID = "S" + statusIndex++.ToString("X")
                            });

                            digitalChannels.Add(new DigitalChannel
                            {
                                Index = digitalIndex++,
                                Name = record.Name + ":DTVLD",
                                PhaseID = "S" + statusIndex.ToString("X")
                            });
                            break;
                        default:     // All other signals assumed to be analog values
                            analogChannels.Add(new AnalogChannel
                            {
                                Index = analogIndex++,
                                Name = record.Name,
                                PhaseID = ""
                            });
                            break;
                    }
                }
            }

            schema.AnalogChannels = analogChannels.ToArray();
            schema.DigitalChannels = digitalChannels.ToArray();
            schema.NominalFrequency = nominalFrequency;

            return schema;
        }
Exemple #2
0
        /// <summary>
        /// Writes next COMTRADE record in ASCII format.
        /// </summary>
        /// <param name="output">Destination stream.</param>
        /// <param name="schema">Source schema.</param>
        /// <param name="timestamp">Record timestamp (implicitly castable as <see cref="DateTime"/>).</param>
        /// <param name="values">Values to write - 16-bit digitals should exist as a word in an individual double value, method will write out bits.</param>
        /// <param name="sample">User incremented sample index.</param>
        /// <param name="injectFracSecValue">Determines if FRACSEC value should be automatically injected into stream as first digital - defaults to <c>true</c>.</param>
        /// <param name="fracSecValue">FRACSEC value to inject into output stream - defaults to 0x0000.</param>
        /// <remarks>
        /// This function is primarily intended to write COMTRADE ASCII data records based on synchrophasor data
        /// (see Annex H: Schema for Phasor Data 2150 Using the COMTRADE File Standard in IEEE C37.111-2010),
        /// it may be necessary to manually write records for other COMTRADE needs (e.g., non 16-bit digitals).
        /// </remarks>
        public static void WriteNextRecordAscii(StreamWriter output, Schema schema, Ticks timestamp, double[] values, uint sample, bool injectFracSecValue = true, ushort fracSecValue = 0x0000)
        {
            // Make timestamp relative to beginning of file
            timestamp -= schema.StartTime.Value;

            uint microseconds = (uint)(timestamp.ToMicroseconds() / schema.TimeFactor);
            double value;
            StringBuilder line = new StringBuilder();
            bool isFirstDigital = true;

            line.Append(sample);
            line.Append(',');
            line.Append(microseconds);

            for (int i = 0; i < values.Length; i++)
            {
                value = values[i];

                if (i < schema.AnalogChannels.Length)
                {
                    value -= schema.AnalogChannels[i].Adder;
                    value /= schema.AnalogChannels[i].Multiplier;

                    line.Append(',');
                    line.Append(value);
                }
                else
                {
                    if (isFirstDigital)
                    {
                        // Handle automatic injection of IEEE C37.118 FRACSEC digital value if requested
                        isFirstDigital = false;

                        if (injectFracSecValue)
                        {
                            for (int j = 0; j < 16; j++)
                            {
                                line.Append(',');
                                line.Append(fracSecValue.CheckBits(BitExtensions.BitVal(j)) ? 1 : 0);
                            }
                        }
                    }

                    ushort digitalWord = (ushort)value;

                    for (int j = 0; j < 16; j++)
                    {
                        line.Append(',');
                        line.Append(digitalWord.CheckBits(BitExtensions.BitVal(j)) ? 1 : 0);
                    }
                }
            }

            // Make sure FRACSEC values are injected
            if (isFirstDigital && injectFracSecValue)
            {
                for (int j = 0; j < 16; j++)
                {
                    line.Append(',');
                    line.Append(fracSecValue.CheckBits(BitExtensions.BitVal(j)) ? 1 : 0);
                }
            }

            output.WriteLine(line.ToString());
        }
Exemple #3
0
        /// <summary>
        /// Writes next COMTRADE record in binary format.
        /// </summary>
        /// <param name="output">Destination stream.</param>
        /// <param name="schema">Source schema.</param>
        /// <param name="timestamp">Record timestamp (implicitly castable as <see cref="DateTime"/>).</param>
        /// <param name="values">Values to write - 16-bit digitals should exist as a word in an individual double value.</param>
        /// <param name="sample">User incremented sample index.</param>
        /// <param name="injectFracSecValue">Determines if FRACSEC value should be automatically injected into stream as first digital - defaults to <c>true</c>.</param>
        /// <param name="fracSecValue">FRACSEC value to inject into output stream - defaults to 0x0000.</param>
        /// <remarks>
        /// This function is primarily intended to write COMTRADE binary data records based on synchrophasor data
        /// (see Annex H: Schema for Phasor Data 2150 Using the COMTRADE File Standard in IEEE C37.111-2010),
        /// it may be necessary to manually write records for other COMTRADE needs (e.g., non 16-bit digitals).
        /// </remarks>
        public static void WriteNextRecordBinary(Stream output, Schema schema, Ticks timestamp, double[] values, uint sample, bool injectFracSecValue = true, ushort fracSecValue = 0x0000)
        {
            // Make timestamp relative to beginning of file
            timestamp -= schema.StartTime.Value;

            uint microseconds = (uint)(timestamp.ToMicroseconds() / schema.TimeFactor);
            double value;
            bool isFirstDigital = true;

            output.Write(LittleEndian.GetBytes(sample), 0, 4);
            output.Write(LittleEndian.GetBytes(microseconds), 0, 4);

            for (int i = 0; i < values.Length; i++)
            {
                value = values[i];

                if (i < schema.AnalogChannels.Length)
                {
                    value -= schema.AnalogChannels[i].Adder;
                    value /= schema.AnalogChannels[i].Multiplier;
                }
                else if (isFirstDigital)
                {
                    // Handle automatic injection of IEEE C37.118 FRACSEC digital value if requested
                    isFirstDigital = false;

                    if (injectFracSecValue)
                        output.Write(LittleEndian.GetBytes(fracSecValue), 0, 2);
                }

                output.Write(LittleEndian.GetBytes((ushort)value), 0, 2);
            }

            // Make sure FRACSEC values are injected
            if (isFirstDigital && injectFracSecValue)
                output.Write(LittleEndian.GetBytes(fracSecValue), 0, 2);
        }
        private void WriteDataFile(COMTRADEData comtradeData, Schema schema, string dataFilePath)
        {
            // Function to get the value at a given index from a data series
            Func<DataSeries, int, double> valueAt = (series, i) =>
                (i < series.DataPoints.Count)
                ? series.DataPoints[i].Value
                : series.DataPoints.Last().Value;

            // Get the list of data series for every channel in the COMTRADE file
            IEnumerable<DataSeries> digitalSeriesList = comtradeData.DigitalChannelData
                .Select(channelData => channelData.Data)
                .Select((series, index) => series.Multiply(Math.Pow(2.0D, index % 16)))
                .Select((series, index) => Tuple.Create(index / 16, series))
                .GroupBy(tuple => tuple.Item1)
                .Select(group => group.Select(tuple => tuple.Item2))
                .Select(group => group.Aggregate((sum, series) => sum.Add(series)));

            List<DataSeries> allChannels = comtradeData.AnalogChannelData
                .Select(channelData => channelData.Data)
                .Concat(digitalSeriesList)
                .ToList();

            // Use the longest data series as the series
            // from which time values will be used
            DataSeries timeSeries = allChannels
                .OrderByDescending(series => series.DataPoints.Count)
                .First();

            // Open the data file for writing
            using (FileStream fileStream = File.Create(FilePath.GetAbsolutePath(dataFilePath)))
            {
                // Write the timestamp and values to each line of the data file
                for (int i = 0; i < comtradeData.SampleCount; i++)
                {
                    Ticks timestamp = timeSeries.DataPoints[i].Time;

                    double[] values = allChannels
                        .Select(series => valueAt(series, i))
                        .ToArray();

                    Writer.WriteNextRecordBinary(fileStream, schema, timestamp, values, (uint)i, false);
                }
            }
        }
        /// <summary>
        /// Populate known voltage and current data from PQDIF file.
        /// </summary>
        /// <param name="faultDataSet">Fault data set to be populated.</param>
        /// <param name="settings">Source parameters.</param>
        /// <param name="line">Associated XML event file definition.</param>
        public static void PopulateDataSet(FaultLocationDataSet faultDataSet, Dictionary<string, string> settings, Line line)
        {
            string fileName;

            if ((object)line == null)
                throw new ArgumentNullException("line");

            if (!settings.TryGetValue("fileName", out fileName) || !File.Exists(fileName))
                throw new ArgumentException("Parameters must define a valid \"fileName\" setting.");

            // Comtrade parsing will require a CFG file, make sure this exists...
            string directory = Path.GetDirectoryName(fileName) ?? string.Empty;
            string rootFileName = FilePath.GetFileNameWithoutExtension(fileName);
            string configurationFileName = Path.Combine(directory, rootFileName + ".cfg");

            if (!File.Exists(configurationFileName))
                throw new FileNotFoundException(string.Format("Associated CFG file \"{0}\" for COMTRADE data file does not exist - cannot parse COMTRADE file.", configurationFileName));

            // Parse configuration file
            Schema schema = new Schema(configurationFileName);

            // Find <Channels> element in XML line definition
            XElement channels = line.ChannelsElement;

            if ((object)channels == null)
                throw new NullReferenceException("No \"<channels>\" element was found in event file definition - cannot load COMTRADE data file.");

            // Extract COMTRADE channel ID's for desired voltage and current elements
            IEnumerable<Tuple<int, int>> vaIndexes = GetValueIndex(schema, channels, "VA").ToList();
            IEnumerable<Tuple<int, int>> vbIndexes = GetValueIndex(schema, channels, "VB").ToList();
            IEnumerable<Tuple<int, int>> vcIndexes = GetValueIndex(schema, channels, "VC").ToList();
            IEnumerable<Tuple<int, int>> iaIndexes = GetValueIndex(schema, channels, "IA").ToList();
            IEnumerable<Tuple<int, int>> ibIndexes = GetValueIndex(schema, channels, "IB").ToList();
            IEnumerable<Tuple<int, int>> icIndexes = GetValueIndex(schema, channels, "IC").ToList();

            List<long> times = new List<long>();
            List<double> vaValues = new List<double>();
            List<double> vbValues = new List<double>();
            List<double> vcValues = new List<double>();
            List<double> iaValues = new List<double>();
            List<double> ibValues = new List<double>();
            List<double> icValues = new List<double>();

            SampleRate sampleRate;

            ValidateIndexes("VA", vaIndexes);
            ValidateIndexes("VB", vbIndexes);
            ValidateIndexes("VC", vcIndexes);
            ValidateIndexes("IA", iaIndexes);
            ValidateIndexes("IB", ibIndexes);
            ValidateIndexes("IC", icIndexes);

            // Create a new COMTRADE file parser
            using (Parser parser = new Parser()
            {
                Schema = schema,
                FileName = fileName,
                InferTimeFromSampleRates = true
            })
            {
                // Open COMTRADE data files
                parser.OpenFiles();

                faultDataSet.Frequency = schema.NominalFrequency;
                sampleRate = schema.SampleRates.First();

                if (sampleRate.Rate != 0)
                    faultDataSet.SetSampleRates((int)(sampleRate.Rate / faultDataSet.Frequency));

                // Read all COMTRADE records
                while (parser.ReadNext())
                {
                    times.Add(parser.Timestamp.Ticks);
                    vaValues.Add(GetValue(parser, vaIndexes));
                    vbValues.Add(GetValue(parser, vbIndexes));
                    vcValues.Add(GetValue(parser, vcIndexes));
                    iaValues.Add(GetValue(parser, iaIndexes));
                    ibValues.Add(GetValue(parser, ibIndexes));
                    icValues.Add(GetValue(parser, icIndexes));
                }
            }

            // Populate voltage data set
            faultDataSet.Voltages.AN.Times = times.ToArray();
            faultDataSet.Voltages.AN.Measurements = vaValues.ToArray();
            faultDataSet.Voltages.BN.Times = times.ToArray();
            faultDataSet.Voltages.BN.Measurements = vbValues.ToArray();
            faultDataSet.Voltages.CN.Times = times.ToArray();
            faultDataSet.Voltages.CN.Measurements = vcValues.ToArray();

            // Populate current data set
            faultDataSet.Currents.AN.Times = times.ToArray();
            faultDataSet.Currents.AN.Measurements = iaValues.ToArray();
            faultDataSet.Currents.BN.Times = times.ToArray();
            faultDataSet.Currents.BN.Measurements = ibValues.ToArray();
            faultDataSet.Currents.CN.Times = times.ToArray();
            faultDataSet.Currents.CN.Measurements = icValues.ToArray();
        }
        // Gets the actual analog value indexes based on the schema based channel indexes
        private static IEnumerable<Tuple<int, int>> GetValueIndex(Schema schema, XElement channels, string channelName)
        {
            XElement element = channels.Element(channelName);
            string[] channelValues;
            int channelIndex, multiplier;
            string units;

            if ((object)element == null)
                throw new NullReferenceException(string.Format("No \"{0}\" element was found in defined \"<Channels>\" of device definition - cannot load COMTRADE data file.", channelName));

            channelValues = element.Value.Split(',');

            // Index defined in COMTRADE schema may start at any number, return actual index into value array
            foreach (string channelValue in channelValues)
            {
                if (!int.TryParse(channelValue, out channelIndex))
                    throw new InvalidOperationException(string.Format("The \"{0}\" element in the device definition was not a comma-separated list of integers - cannot load COMTRADE data file.", channelName));

                channelIndex = Math.Abs(channelIndex);
                multiplier = (channelValue.Trim()[0] == '-') ? -1 : 1;

                for (int valueIndex = 0; valueIndex < schema.AnalogChannels.Length; valueIndex++)
                {
                    if (schema.AnalogChannels[valueIndex].Index == channelIndex)
                    {
                        units = schema.AnalogChannels[valueIndex].Units.ToUpper();

                        if (units.Contains("KA") || units.Contains("KV"))
                            multiplier *= 1000;

                        yield return new Tuple<int, int>(multiplier, valueIndex);
                        break;
                    }
                }
            }
        }
        public static void Write(Meter meter, DataGroup waveFormData, FaultLocationData.FaultCurveDataTable faultCurveTable, List<FaultSegment> segments, string originalFilePath, string filePath)
        {
            List<DataSeries> waveFormSeriesList = GetWaveFormSeriesList(waveFormData);
            DataGroup faultLocationData = GetFaultLocationData(meter, faultCurveTable);

            string absoluteFilePath = FilePath.GetAbsolutePath(filePath);

            using (StreamWriter fileStream = new StreamWriter(File.OpenWrite(absoluteFilePath)))
            {
                string originalDirectory;
                string originalRootFileName;
                string originalSchemaFilePath;
                string absoluteOriginalFilePath;
                Schema originalSchema = null;

                string headerRow;

                absoluteOriginalFilePath = FilePath.GetAbsolutePath(originalFilePath);

                if (File.Exists(absoluteOriginalFilePath))
                {
                    originalDirectory = FilePath.GetDirectoryName(absoluteOriginalFilePath);
                    originalRootFileName = FilePath.GetFileNameWithoutExtension(originalFilePath);
                    originalSchemaFilePath = Path.Combine(originalDirectory, originalRootFileName + ".cfg");
                    originalSchema = new Schema(originalSchemaFilePath);
                }

                headerRow = waveFormData.DataSeries
                    .Select(series => GetOriginalChannelName(originalSchema, series))
                    .Concat(faultCurveTable.Select(row => string.Format("Fault Location ({0} Algorithm)", row.Algorithm)))
                    .Aggregate("Time", (s, s1) => s + "," + s1);

                fileStream.WriteLine(headerRow);

                for (int i = 0; i < waveFormData.Samples; i++)
                {
                    DateTime time = waveFormSeriesList[0].DataPoints[i].Time;

                    double[] values = waveFormSeriesList
                        .Select(series => series.DataPoints[i].Value)
                        .Concat(faultLocationData.DataSeries.Select(series => series.DataPoints.Count > i ? series.DataPoints[i].Value : 0.0D))
                        .ToArray();

                    fileStream.WriteLine(values.Aggregate(time.ToString("yyyy-MM-dd HH:mm:ss.ffffff"), (s, d) => s + "," + d));
                }
            }
        }
        private static string GetOriginalChannelName(Schema originalSchema, DataSeries series)
        {
            int index;

            return ((object)originalSchema != null && int.TryParse(series.SeriesInfo.SourceIndexes, out index))
                ? originalSchema.AnalogChannels[Math.Abs(index) - 1].Name
                : series.SeriesInfo.Channel.Name;
        }