/// <summary> /// Creates a new IEEE C37.118 specific <see cref="DataFrame"/> for the given <paramref name="timestamp"/>. /// </summary> /// <param name="timestamp">Timestamp for new <see cref="IFrame"/> in <see cref="Ticks"/>.</param> /// <param name="configurationFrame">Associated <see cref="ConfigurationFrame1"/> for the new <see cref="DataFrame"/>.</param> /// <returns>New IEEE C37.118 <see cref="DataFrame"/> at given <paramref name="timestamp"/>.</returns> public static DataFrame CreateDataFrame(Ticks timestamp, ConfigurationFrame1 configurationFrame) { // We create a new IEEE C37.118 data frame based on current configuration frame DataFrame dataFrame = new DataFrame(timestamp, configurationFrame); foreach (ConfigurationCell configurationCell in configurationFrame.Cells) { // Create a new IEEE C37.118 data cell (i.e., a PMU entry for this frame) DataCell dataCell = new DataCell(dataFrame, configurationCell, true); // Add data cell to the frame dataFrame.Cells.Add(dataCell); } return(dataFrame); }
/// <summary> /// Converts given IEEE C37.118 type 2 <paramref name="sourceFrame"/> into a type 1 configuration frame. /// </summary> /// <param name="sourceFrame">Source configuration frame.</param> /// <returns>New <see cref="ConfigurationFrame1"/> frame based on source configuration.</returns> /// <remarks> /// This function allow an explicit downcast of a typical IEEE C37.118 configuration type 2 frame to a type 1 frame. /// </remarks> public static ConfigurationFrame1 CastToConfigurationFrame1(ConfigurationFrame2 sourceFrame) { ConfigurationFrame1 derivedFrame; // Create a new IEEE C37.118 configuration frame converted from equivalent configuration information ConfigurationCell derivedCell; if (sourceFrame.DraftRevision == DraftRevision.Draft7) { derivedFrame = new ConfigurationFrame1(sourceFrame.Timebase, sourceFrame.IDCode, sourceFrame.Timestamp, sourceFrame.FrameRate); } else { derivedFrame = new ConfigurationFrame1Draft6(sourceFrame.Timebase, sourceFrame.IDCode, sourceFrame.Timestamp, sourceFrame.FrameRate); } foreach (ConfigurationCell sourceCell in sourceFrame.Cells) { // Create new derived configuration cell derivedCell = new ConfigurationCell(derivedFrame, sourceCell.IDCode, sourceCell.NominalFrequency); string stationName = sourceCell.StationName; string idLabel = sourceCell.IDLabel; if (!string.IsNullOrWhiteSpace(stationName)) { derivedCell.StationName = stationName.TruncateLeft(derivedCell.MaximumStationNameLength); } if (!string.IsNullOrWhiteSpace(idLabel)) { derivedCell.IDLabel = idLabel.TruncateLeft(derivedCell.IDLabelLength); } derivedCell.PhasorCoordinateFormat = sourceCell.PhasorCoordinateFormat; derivedCell.PhasorAngleFormat = sourceCell.PhasorAngleFormat; derivedCell.PhasorDataFormat = sourceCell.PhasorDataFormat; derivedCell.FrequencyDataFormat = sourceCell.FrequencyDataFormat; derivedCell.AnalogDataFormat = sourceCell.AnalogDataFormat; // Create equivalent derived phasor definitions foreach (PhasorDefinition sourcePhasor in sourceCell.PhasorDefinitions) { derivedCell.PhasorDefinitions.Add(new PhasorDefinition(derivedCell, sourcePhasor.Label, sourcePhasor.ScalingValue, sourcePhasor.Offset, sourcePhasor.PhasorType, null)); } // Create equivalent derived frequency definition FrequencyDefinition sourceFrequency = sourceCell.FrequencyDefinition as FrequencyDefinition; derivedCell.FrequencyDefinition = new FrequencyDefinition(derivedCell, sourceFrequency?.Label); // Create equivalent derived analog definitions (assuming analog type = SinglePointOnWave) foreach (IAnalogDefinition sourceAnalog in sourceCell.AnalogDefinitions) { derivedCell.AnalogDefinitions.Add(new AnalogDefinition(derivedCell, sourceAnalog.Label, sourceAnalog.ScalingValue, sourceAnalog.Offset, sourceAnalog.AnalogType)); } // Create equivalent derived digital definitions foreach (IDigitalDefinition sourceDigital in sourceCell.DigitalDefinitions) { derivedCell.DigitalDefinitions.Add(new GSF.PhasorProtocols.IEEEC37_118.DigitalDefinition(derivedCell, sourceDigital.Label, 0, 0)); } // Add cell to frame derivedFrame.Cells.Add(derivedCell); } return(derivedFrame); }
/// <summary> /// Handles incoming commands from devices connected over the command channel. /// </summary> /// <param name="clientID">Guid of client that sent the command.</param> /// <param name="connectionID">Remote client connection identification (i.e., IP:Port).</param> /// <param name="commandBuffer">Data buffer received from connected client device.</param> /// <param name="length">Valid length of data within the buffer.</param> protected override void DeviceCommandHandler(Guid clientID, string connectionID, byte[] commandBuffer, int length) { try { // Interpret data received from a client as a command frame CommandFrame commandFrame = new CommandFrame(commandBuffer, 0, length); IServer commandChannel = (IServer)CommandChannel ?? DataChannel; // Validate incoming ID code if requested if (!m_validateIDCode || commandFrame.IDCode == this.IDCode) { switch (commandFrame.Command) { case DeviceCommand.SendConfigurationFrame1: if (commandChannel != null) { ConfigurationFrame1 configFrame1 = CastToConfigurationFrame1(m_configurationFrame); commandChannel.SendToAsync(clientID, configFrame1.BinaryImage, 0, configFrame1.BinaryLength); OnStatusMessage(MessageLevel.Info, $"Received request for \"{commandFrame.Command}\" from \"{connectionID}\" - type 1 config frame was returned."); } break; case DeviceCommand.SendConfigurationFrame2: if (commandChannel != null) { commandChannel.SendToAsync(clientID, m_configurationFrame.BinaryImage, 0, m_configurationFrame.BinaryLength); OnStatusMessage(MessageLevel.Info, $"Received request for \"{commandFrame.Command}\" from \"{connectionID}\" - type 2 config frame was returned."); } break; case DeviceCommand.SendHeaderFrame: if (commandChannel != null) { StringBuilder status = new StringBuilder(); status.Append("IEEE C37.118 Concentrator:\r\n\r\n"); status.AppendFormat(" Auto-publish config frame: {0}\r\n", AutoPublishConfigurationFrame); status.AppendFormat(" Auto-start data channel: {0}\r\n", AutoStartDataChannel); status.AppendFormat(" Data stream ID code: {0}\r\n", IDCode); status.AppendFormat(" Derived system time: {0} UTC\r\n", ((DateTime)RealTime).ToString("yyyy-MM-dd HH:mm:ss.fff")); HeaderFrame headerFrame = new HeaderFrame(status.ToString()); commandChannel.SendToAsync(clientID, headerFrame.BinaryImage, 0, headerFrame.BinaryLength); OnStatusMessage(MessageLevel.Info, $"Received request for \"SendHeaderFrame\" from \"{connectionID}\" - frame was returned."); } break; case DeviceCommand.EnableRealTimeData: // Only responding to stream control command if auto-start data channel is false if (!AutoStartDataChannel) { StartDataChannel(); OnStatusMessage(MessageLevel.Info, $"Received request for \"EnableRealTimeData\" from \"{connectionID}\" - concentrator real-time data stream was started."); } else { OnStatusMessage(MessageLevel.Info, $"Request for \"EnableRealTimeData\" from \"{connectionID}\" was ignored - concentrator data channel is set for auto-start."); } break; case DeviceCommand.DisableRealTimeData: // Only responding to stream control command if auto-start data channel is false if (!AutoStartDataChannel) { StopDataChannel(); OnStatusMessage(MessageLevel.Info, $"Received request for \"DisableRealTimeData\" from \"{connectionID}\" - concentrator real-time data stream was stopped."); } else { OnStatusMessage(MessageLevel.Info, $"Request for \"DisableRealTimeData\" from \"{connectionID}\" was ignored - concentrator data channel is set for auto-start."); } break; default: OnStatusMessage(MessageLevel.Info, $"Request for \"{commandFrame.Command}\" from \"{connectionID}\" was ignored - device command is unsupported."); break; } } else { OnStatusMessage(MessageLevel.Warning, $"Concentrator ID code validation failed for device command \"{commandFrame.Command}\" from \"{connectionID}\" - no action was taken."); } } catch (Exception ex) { OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Remotely connected device \"{connectionID}\" sent an unrecognized data sequence to the concentrator, no action was taken. Exception details: {ex.Message}", ex)); } }
/// <summary> /// Creates a new <see cref="ConfigurationCell"/> from specified parameters. /// </summary> /// <param name="parent">The reference to parent <see cref="ConfigurationFrame1"/> of this <see cref="ConfigurationCell"/>.</param> /// <param name="idCode">The numeric ID code for this <see cref="ConfigurationCell"/>.</param> /// <param name="nominalFrequency">The nominal <see cref="LineFrequency"/> of the <see cref="FrequencyDefinition"/> of this <see cref="ConfigurationCell"/>.</param> public ConfigurationCell(ConfigurationFrame1 parent, ushort idCode, LineFrequency nominalFrequency) : this(parent) { IDCode = idCode; NominalFrequency = nominalFrequency; }
/// <summary> /// Creates a new IEEE C37.118 specific <see cref="DataFrame"/> for the given <paramref name="timestamp"/>. /// </summary> /// <param name="timestamp">Timestamp for new <see cref="IFrame"/> in <see cref="Ticks"/>.</param> /// <param name="configurationFrame">Associated <see cref="ConfigurationFrame1"/> for the new <see cref="DataFrame"/>.</param> /// <returns>New IEEE C37.118 <see cref="DataFrame"/> at given <paramref name="timestamp"/>.</returns> public static DataFrame CreateDataFrame(Ticks timestamp, ConfigurationFrame1 configurationFrame) { // We create a new IEEE C37.118 data frame based on current configuration frame DataFrame dataFrame = new DataFrame(timestamp, configurationFrame); DataCell dataCell; foreach (ConfigurationCell configurationCell in configurationFrame.Cells) { // Create a new IEEE C37.118 data cell (i.e., a PMU entry for this frame) dataCell = new DataCell(dataFrame, configurationCell, true); // Add data cell to the frame dataFrame.Cells.Add(dataCell); } return dataFrame; }