/// <summary> /// Converts given IEEE C37.118 type 2 <paramref name="sourceFrame"/> into a type 3 configuration frame. /// </summary> /// <param name="sourceFrame">Source configuration frame.</param> /// <returns>New <see cref="ConfigurationFrame3"/> 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 3 frame. /// </remarks> public static ConfigurationFrame3 CastToConfigurationFrame3(ConfigurationFrame2 sourceFrame) { ConfigurationFrame3 derivedFrame; // Create a new IEEE C37.118 configuration frame converted from equivalent configuration information derivedFrame = new ConfigurationFrame3(sourceFrame.Timebase, sourceFrame.IDCode, sourceFrame.Timestamp, sourceFrame.FrameRate); foreach (ConfigurationCell sourceCell in sourceFrame.Cells) { // Create new derived configuration cell ConfigurationCell 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 (!ValidateIDCode || commandFrame.IDCode == 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.SendConfigurationFrame3: if (commandChannel != null) { ConfigurationFrame3 configFrame3 = CastToConfigurationFrame3(m_configurationFrame); commandChannel.SendToAsync(clientID, configFrame3.BinaryImage, 0, configFrame3.BinaryLength); OnStatusMessage(MessageLevel.Info, $"Received request for \"{commandFrame.Command}\" from \"{connectionID}\" - type 3 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:yyyy-MM-dd HH:mm:ss.fff} UTC\r\n", RealTime); 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)); } }