private void ReadDigitalValues(byte[] buffer, int index) { int valueIndex = m_schema.AnalogChannels.Length; // Parse all digital record values for (int i = 0; i < m_schema.DigitalWords; i++) { // Read next digital word ushort digitalWord = LittleEndian.ToUInt16(buffer, index); index += 2; // Distribute each bit of digital word through next 16 digital values for (int j = 0; j < 16 && valueIndex < Values.Length; j++, valueIndex++) { Values[valueIndex] = digitalWord.CheckBits(BitExtensions.BitVal(j)) ? 1.0D : 0.0D; } } }
/// <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); StringBuilder line = new StringBuilder(); bool isFirstDigital = true; line.Append(sample); line.Append(','); line.Append(microseconds); for (int i = 0; i < values.Length; i++) { double value = values[i]; if (i < schema.AnalogChannels.Length) { value -= schema.AnalogChannels[i].Adder; value /= schema.AnalogChannels[i].Multiplier; line.Append(','); line.Append(value.ToString(CultureInfo.InvariantCulture)); } 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()); }
// Handle binary file read private bool ReadNextBinary() { FileStream currentFile = m_fileStreams[m_streamIndex]; int recordLength = m_schema.BinaryRecordLength; byte[] buffer = new byte[recordLength]; // Read next record from file int bytesRead = currentFile.Read(buffer, 0, recordLength); // See if we have reached the end of this file if (bytesRead == 0) { m_streamIndex++; // There is more to read if there is another file return(m_streamIndex < m_fileStreams.Length && ReadNext()); } if (bytesRead == recordLength) { int index = 0; // Read sample index uint sample = LittleEndian.ToUInt32(buffer, index); index += 4; // Get timestamp of this record m_timestamp = DateTime.MinValue; // If sample rates are defined, this is the preferred method for timestamp resolution if (m_inferTimeFromSampleRates && m_schema.SampleRates.Length > 0) { // Find rate for given sample SampleRate sampleRate = m_schema.SampleRates.LastOrDefault(sr => sample <= sr.EndSample); if (sampleRate.Rate > 0.0D) { m_timestamp = new DateTime(Ticks.FromSeconds(1.0D / sampleRate.Rate * sample) + m_schema.StartTime.Value); } } // Read microsecond timestamp uint microseconds = LittleEndian.ToUInt32(buffer, index); index += 4; // Fall back on specified microsecond time if (m_timestamp == DateTime.MinValue) { m_timestamp = new DateTime(Ticks.FromMicroseconds(microseconds * m_schema.TimeFactor) + m_schema.StartTime.Value); } // Parse all analog record values for (int i = 0; i < m_schema.AnalogChannels.Length; i++) { // Read next value m_values[i] = LittleEndian.ToInt16(buffer, index) * m_schema.AnalogChannels[i].Multiplier + m_schema.AnalogChannels[i].Adder; index += 2; } int valueIndex = m_schema.AnalogChannels.Length; int digitalWords = m_schema.DigitalWords; ushort digitalWord; for (int i = 0; i < digitalWords; i++) { // Read next digital word digitalWord = LittleEndian.ToUInt16(buffer, index); index += 2; // Distribute each bit of digital word through next 16 digital values for (int j = 0; j < 16 && valueIndex < m_values.Length; j++, valueIndex++) { m_values[valueIndex] = digitalWord.CheckBits(BitExtensions.BitVal(j)) ? 1.0D : 0.0D; } } } else { throw new InvalidOperationException("Failed to read enough bytes from COMTRADE file for a record as defined by schema - possible schema/data file mismatch or file corruption."); } return(true); }