//>>*************************************************************************** // UNCLASSIFIED // // COPYRIGHT 2017 RAYTHEON MISSILE SYSTEMS // ALL RIGHTS RESERVED // This data was developed pursuant to Contract Number HQ0147-12-C-0004/1088370 // with the US Government. The US Government's rights in and to this // copyrighted data are as specified in DFAR 252.227-7013 // which was made part of the above contract. // // Distribution Statement: D -Distribution authorized to the DoD and DoD // contractors only based on Critical Technology Requirements, May 2001. // Other requests shall be referred to PEO THEATER AIR DEFENSE (PMS 422). // Warning: - This document contains technical data whose export is restricted // by the Arms Export Control Act (Title 22, U.S.C.) or Export // Administration Act of 1979, as amended (Title 50, U.S.C.). Violations // of these laws are subject to severe criminal penalties. Disseminate in // accordance with provisions of DoD 5230.25. // Destruction Notice: - For unclassified, limited distribution information, // destroy by any method that will prevent disclosure of contents or // reconstruction of the document. Classified information, destroy in // accordance with DoD-5220.22-M or OPNAVINST 5510.1h. //>>*************************************************************************** using NLog; using Raytheon.Common; using System; using System.Net.Sockets; using System.Text; using System.Threading; namespace Raytheon.Instruments { /// /// A class for controlling a scope via scpi commands /// public class ScopeKeysightScpi : IOscilloScope { #region PublicMembers #endregion #region PrivateMembers /*private enum SourceType { CHANNEL, FUNCTION }*/ // if other commands are needed http://www.keysight.com/upload/cmc_upload/All/Infiniium_prog_guide.pdf pg 770 private const string m_SUBTRACTFUNC = ":SUBT "; private const string m_MEASFALLCMD = ":MEAS:FALL? "; private const string m_MEASRISCMD = ":MEAS:RISetime? "; private const string m_MEASVTIMCMD = ":MEAS:VTIM? "; private const string _READ_ERROR_CODE_CMD = "SYST:ERR?"; private const string m_MEASFREQCMD = ":MEAS:FREQ? "; private const string m_SINGLECMD = ":SINGle"; private const string m_STOPCMD = ":STOP"; private const string m_RUNCMD = ":RUN"; private const string m_RESETCMD = "*RST"; //private const string m_ISOPCCMD = "*OPC? "; private const string m_ISITCOMPLETEDCMD = ":TER? "; private const string m_SELFTESTCMD = "*TST? "; private const string m_COLON = ":"; private const string m_COMMA = ","; private const string m_QUESTMARK = "?"; private const string m_SPACE = " "; private const string m_SLASH = "\\"; private const string m_DOUBLESLASH = "\\\\"; private const string m_DOUBLEQUOTES = "\""; // Turn Headers Off when Returning Values to Numeric Variables private const string m_SYSHEADEROFFCMD = ":SYST:HEAD OFF"; private const string m_ANALAEDGCMD = ":ANALyze:AEDGes "; private const string m_ANALASIGTYPECMD = ":ANALyze:SIGNal:TYPE"; private const string m_ANALASIGTYPE_OPTION = "PAM4"; private const string m_CHANCMD = "CHAN"; private const string m_FUNCCMD = "FUNC"; private const string m_CHANDISPCMD = ":DISP "; private const string m_CHANINPUTCMD = ":INP "; private const string m_INPUTIMPEDANCECMD = "DC"; private const string m_TIMESCALECMD = ":TIM:SCAL "; private const string m_TIMEOFFSETCMD = ":TIM:POS "; private const string m_TRIGSRCCMD = ":TRIG:EDGE:SOUR "; private const string m_TRIGLEVCMD = ":TRIG:LEV "; private const string m_RISETRIGEDGESELCMD = ":TRIG:EDGE:SLOP POS"; private const string m_FALLTRIGEDGESELCMD = ":TRIG:EDGE:SLOP NEG"; private const string m_VOLTPERDIVCMD = ":SCAL "; private const string m_VOLTOFFSETCMD = ":OFFS "; // The :MEASure:CLEar command clears the measurement results from the screen // and disables all previously enabled measurements. private const string m_MEASCLEARCMD = ":MEAS:CLE"; private const string m_MEASDELTCMD = ":MEAS:DELT"; private const string m_MEASDELTDEFCMD = ":MEAS:DELT:DEF"; private const string m_MEASSOURCMD = ":MEAS:SOUR"; // These threshold settings in voltages are used for rise/fall measurements. private const string m_MEASTHRRFALABSCMD = ":MEAS:THR:RFAL:ABS"; private const string m_MEASTHRGENABSCMD = ":MEAS:THR:GEN:ABS"; private const string m_MEASTHRGENMETHCMD = ":MEAS:THR:GEN:METH"; private const string m_MEASTHRGENTOPBABSCMD = ":MEAS:THR:GEN:TOPB:ABS"; private const string m_MEASTHRGENTOPBABS_OPTION = "5.0,1.0"; private const string m_MEASTHRGENTOPBMETHCMD = ":MEAS:THR:GEN:TOPB:METH"; // The :MEASure:PWIDth command measures the width of the first positive pulse on the screen // using the mid-threshold levels of the waveform(50% levels with standard measurements selected). private const string m_MEASPWIDCMD = ":MEAS:PWID? "; private const string m_MEASTHRMETH_OPTION = "ABS"; // Trigger sweep private const string m_ISTRIGGEREDCMD = ":TRIG:SWE? "; private const string m_TRIGGERCMD = ":TRIG:SWE "; private const string m_TRIGGER_OPTION = "TRIG"; // ON or OFF private const string m_OPTION_ON = "ON"; private const string m_OPTION_OFF = "OFF"; private const string m_IMAGE_DEST = @"\Users\Public\Documents"; private const string m_DESIRED_FOLDER = @"\Scope_Measurements"; private const string m_IMAGE_FORMAT = "GIF"; private const string m_IMAGE_SOURCE = "SCR"; private const string m_SAVEIMAGECMD = ":DISK:SAVE:IMAGe "; // Maximum number of channels provided by scope. private const int m_MAXNUMOFCHANN = 4; private const int m_READ_TIMEOUT = 5000; private readonly string _address; private byte[] _readBuffer; private NetworkStream _tcpStream; private State _state; private SelfTestResult _selfTestResult; private string _name; /// /// NLog logger /// private readonly ILogger _logger; /// /// Raytheon configuration /// private readonly IConfigurationManager _configurationManager; private readonly IConfiguration _configuration; #endregion #region PrivateFunctions /// /// The Finalizer which will release resources if required /// ~ScopeKeysightScpi() { Dispose(false); } /// /// Convert scpi data to string /// /// /// private string ConvertToString(ref byte[] data) { string rsp = System.Text.Encoding.ASCII.GetString(data); return rsp; } /// /// Dispose of this object /// /// protected virtual void Dispose(bool disposing) { try { if (disposing) { if (_state == State.Ready) { Reset(); _tcpStream.Close(); _tcpStream.Dispose(); _state = State.Uninitialized; } } } catch (Exception) { try { //ErrorLogger.Instance().Write(err.Message + "\r\n" + err.StackTrace); } catch (Exception) { //Do not rethrow. Exception from error logger that has already been garbage collected } } } /// /// Get the error code. /// /// The error code. /// The error description. private string GetErrorCode(out int errorCode) { // not calling IOQuery() here so IOQuery() can call GetErrorCode() after each query string command = _READ_ERROR_CODE_CMD + "\n"; byte[] commandBuffer = Encoding.ASCII.GetBytes(command); // send the data out _tcpStream.Write(commandBuffer, 0, commandBuffer.Length); // clear our buffer Array.Clear(_readBuffer, 0, _readBuffer.Length); // read the response int numBytesRead = _tcpStream.Read(_readBuffer, 0, _readBuffer.Length); // convert to a string string rspStr = ConvertToString(ref _readBuffer); // parse the response string[] tokens = rspStr.Split(','); errorCode = Util.ConvertStringToInt32(tokens[0]); // it should always be 2 if (tokens.Length >= 2) { return tokens[1]; } else { return ""; } } /// /// /// /// /// /// private bool IsOperationCompleted(string statusString, string expectedResult = "1") { bool completed = false; // If Scope returns '+x' instead of 'x', where 'x' is any character, trim it. if (statusString.IndexOf('+') == 0) { statusString = statusString.TrimStart('+'); } if (statusString.Equals(expectedResult) == true) { completed = true; } return completed; } #endregion #region PublicFunctions /// /// ScopeKeysightScpi factory constructor /// /// /// public ScopeKeysightScpi(string deviceName, IConfigurationManager configurationManager, ILogger logger) { Name = deviceName; _logger = logger; _configurationManager = configurationManager; _configuration = _configurationManager.GetConfiguration(Name); _address = _configuration.GetConfigurationValue("ScopeKeysightScpi", "Address", ""); int port = _configuration.GetConfigurationValue("ScopeKeysightScpi", "Port", 0); const int READ_BUFFER_SIZE = 512; _readBuffer = new byte[READ_BUFFER_SIZE]; TcpClient scopeSocketConn = new TcpClient(_address, port); _tcpStream = scopeSocketConn.GetStream(); _tcpStream.ReadTimeout = m_READ_TIMEOUT; _state = State.Uninitialized; _selfTestResult = SelfTestResult.Unknown; } /// /// /// /// /// public ScopeKeysightScpi(string name, string address, int port) { const int READ_BUFFER_SIZE = 512; _name = name; _logger = LogManager.GetCurrentClassLogger(); _address = address; _readBuffer = new byte[READ_BUFFER_SIZE]; TcpClient scopeSocketConn = new TcpClient(_address, port); _tcpStream = scopeSocketConn.GetStream(); _tcpStream.ReadTimeout = m_READ_TIMEOUT; _state = State.Uninitialized; _selfTestResult = SelfTestResult.Unknown; } /// /// /// /// public bool ClearErrors() { throw new NotImplementedException(); } /// /// /// /// /// /// /// /// /// public void ConfigureChannel(int channelNumber, double timePerDivison, double timeOffset, double voltageOffset, double voltageScale, string inputImpedance) { // make sure it is stopped first string command = m_STOPCMD + "\n"; IOWrite(command); /*command = m_ISOPCCMD + "\n"; string rspStr = IOQuery(command); string[] tokens = rspStr.Split('\n'); // check to see if STOP was completed. if (IsOperationCompleted(tokens[0]) == false) { throw new Exception("STOP was not completed."); }*/ // clear the measurement command = m_MEASCLEARCMD + "\n"; IOWrite(command); //1. time per division command = m_TIMESCALECMD + timePerDivison.ToString() + "\n"; IOWrite(command); //2. time offset command = m_TIMEOFFSETCMD + timeOffset.ToString() + "\n"; IOWrite(command); //3. Volt per division command = m_COLON + m_CHANCMD + channelNumber.ToString() + m_VOLTPERDIVCMD + voltageScale.ToString() + "\n"; IOWrite(command); //4. Volt offset command = m_COLON + m_CHANCMD + channelNumber.ToString() + m_VOLTOFFSETCMD + voltageOffset.ToString() + "\n"; IOWrite(command); // It sets input impedance to 1M ohmns. // The value of inputImpedance is expected to be "DC", equivalent to 1M ohmn; otherwise, it outputs error. if (inputImpedance.ToUpper().Trim() != m_INPUTIMPEDANCECMD.ToUpper()) { throw new Exception("ScopeSetupChannel() expected inputImpedance to be DC."); } command = m_COLON + m_CHANCMD + channelNumber.ToString() + m_CHANINPUTCMD + inputImpedance + "\n"; IOWrite(command); // It turns on the specified channel. command = m_COLON + m_CHANCMD + channelNumber.ToString() + m_CHANDISPCMD + m_OPTION_ON + "\n"; IOWrite(command); // start it back up command = m_RUNCMD + "\n"; IOWrite(command); } /// /// /// public string DetailedStatus { get { return "This is a Scpi Scope"; } } /// /// /// public bool DisplayEnabled { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } /// /// Dispose of this objects resources /// public void Dispose() { try { Dispose(true); GC.SuppressFinalize(this); } catch (Exception) { try { //ErrorLogger.Instance().Write(err.Message + "\r\n" + err.StackTrace); } catch (Exception) { //Do not rethrow. Exception from error logger that has already been garbage collected } } } /// /// /// public bool FrontPanelEnabled { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } /// /// /// /// public bool HasItTriggered() { // Check the Trigger Event register to see if the event has properly triggered string command = m_ISITCOMPLETEDCMD + "\n"; //string command = m_ISTRIGGEREDCMD + "\n"; string rspStr = IOQuery(command); bool isTriggered = false; string[] tokens = rspStr.Split('\n'); // check to see if it is triggered. if (IsOperationCompleted(tokens[0]) == true) { isTriggered = true; } return isTriggered; } /// /// /// public InstrumentMetadata Info { get { throw new NotImplementedException(); } } /// /// /// public void Initialize() { // if we have not yet been initialized, go ahead and create the socket if (_state == State.Uninitialized) { Reset(); // we already connected to the instrument in the constructor _state = State.Ready; } else { throw new Exception("expected the state of System " + _name + " to be Uninitialized, state was: " + _state.ToString()); } } /// /// /// /// /// public string IOQuery(string command) { // send the command IOWrite(command, false); // clear our buffer Array.Clear(_readBuffer, 0, _readBuffer.Length); // read from the response int numBytesRead = _tcpStream.Read(_readBuffer, 0, _readBuffer.Length); // convert response to a string string rspStr = ConvertToString(ref _readBuffer); // check for errors int err = 0; string errorMessage = GetErrorCode(out err); if (err != 0) { throw new Exception("ScopeKeysightScpi:IOQuery() - Scope " + _name + " returned error code: " + err.ToString() + "," + errorMessage); } return rspStr; } /// /// /// /// public void IOWrite(string command, bool shallWeCheckForError = true) { // convert to a byte array byte[] commandBuffer = Encoding.ASCII.GetBytes(command); // send the data out _tcpStream.Write(commandBuffer, 0, commandBuffer.Length); // check for errors if (shallWeCheckForError == true) { int err = 0; string errorMessage = GetErrorCode(out err); if (err != 0) { throw new Exception("ScopeKeysightScpi:IOWrite() - Scope " + _name + " returned error code: " + err.ToString() + "," + errorMessage); } } } /// /// /// /// public double MeasureFallTime(int channelNumber) { throw new NotImplementedException(); } /// /// /// /// /// public double MeasureFrequency(int channelNumber) { // turn off system header string command = m_SYSHEADEROFFCMD + "\n"; IOWrite(command); // send the command to read the data command = m_MEASFREQCMD + m_CHANCMD + channelNumber.ToString() + "\n"; string rspStr = IOQuery(command); string[] tokens = rspStr.Split('\n'); double freq = Util.ConvertStringToDouble(tokens[0]); return freq; } /// /// /// /// public double MeasureMaxVoltage(int channelNumber) { throw new NotImplementedException(); } /// /// /// /// public double MeasureMinVoltage(int channelNumber) { throw new NotImplementedException(); } /// /// /// /// public double MeasurePulseWidth(int channelNumber) { // turn off system header string command = m_SYSHEADEROFFCMD + "\n"; IOWrite(command); // send the command to read the data command = m_MEASPWIDCMD + m_CHANCMD + channelNumber.ToString() + "\n"; string rspStr = IOQuery(command); string[] tokens = rspStr.Split('\n'); double pulseWidth = Util.ConvertStringToDouble(tokens[0]); return pulseWidth; } /// /// /// /// public double MeasureRiseTime(int channelNumber) { throw new NotImplementedException(); } /// /// /// /// public double MeasureVoltageLevel(int channelNumber) { throw new NotImplementedException(); } /// /// /// public string Name { get { return _name; } set { _name = value; } } /// /// /// public SelfTestResult SelfTestResult { get { return _selfTestResult; } } /// /// /// public State Status { get { return _state; } } /// /// /// /// public SelfTestResult PerformSelfTest() { try { // change the timeout to account for the long self test _tcpStream.ReadTimeout = 90000; // send the command and get the response string command = m_SELFTESTCMD + "\n"; string rspStr = IOQuery(command); // parse the response string[] tokens = rspStr.Split('\n'); //Check if self test is completed; returns status of "0". if (IsOperationCompleted(tokens[0], "0") == false) { _selfTestResult = SelfTestResult.Fail; string errorMsg = "System " + _name + " returned an error: " + tokens[0]; throw new Exception(errorMsg); } else { _selfTestResult = SelfTestResult.Pass; } return _selfTestResult; } catch (Exception) { throw; } finally { // restore the timeout _tcpStream.ReadTimeout = m_READ_TIMEOUT; } } /// /// /// public void Reset() { // Resets the oscilloscope string command = m_RESETCMD + "\n"; IOWrite(command); // just a swag Thread.Sleep(3000); /*command = m_ISOPCCMD + "\n"; string rspStr = IOQuery(command); // parse the response string[] tokens = rspStr.Split('\n'); if (IsOperationCompleted(tokens[0]) == false) { throw new Exception("Reset was not completed."); }*/ } /// /// /// public void SaveImage(string fileName) { try { string timeStamp_date = DateTime.Now.ToString("yyyyMMdd"); string timeStamp_sec = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"); string folderTimeStamp = DateTime.Now.ToString("yyyy-MM-dd-HH-mm"); string srcPath = $"{m_DOUBLESLASH}{_address}{m_IMAGE_DEST}{m_DESIRED_FOLDER}{m_SLASH}{timeStamp_date}{m_SLASH}{folderTimeStamp}{m_SLASH}"; bool exists = System.IO.Directory.Exists(srcPath); if (!exists) { try { System.IO.Directory.CreateDirectory(srcPath); //m_SrcDirectory = $"{m_DOUBLESLASH}{_address}{m_IMAGE_DEST}{m_DESIRED_FOLDER}{m_SLASH}{testUnit}"; // m_TimeStampeDate = timeStamp_date; } catch (Exception) { Reset(); throw; } } string m_scopeFileName = $"{timeStamp_date}{m_SLASH}{folderTimeStamp}{m_SLASH}{fileName}_{timeStamp_sec}.{m_IMAGE_FORMAT}"; string filename = $"{srcPath}{fileName}_{timeStamp_sec}"; string command = m_SAVEIMAGECMD + m_DOUBLEQUOTES + filename + m_DOUBLEQUOTES + m_COMMA + m_IMAGE_FORMAT + m_COMMA + m_IMAGE_SOURCE + m_COMMA + m_OPTION_ON + "\n"; IOWrite(command); } catch (Exception) { Reset(); throw; } } /// /// /// public void SetupTrigger(bool useRisingEdge, int channelNumber, double triggerLevel) { try { string command = m_STOPCMD + "\n"; IOWrite(command); /*command = m_ISOPCCMD + "\n"; string rspStr = IOQuery(command); string[] tokens = rspStr.Split('\n'); // check to see if STOP was completed if (IsOperationCompleted(tokens[0]) == false) { throw new Exception("STOP was not completed."); }*/ command = m_MEASCLEARCMD + "\n"; IOWrite(command); //5. Trigger level //*** Trigger edge source command = m_TRIGSRCCMD + m_CHANCMD + channelNumber.ToString() + "\n"; IOWrite(command); //*** select edge type if (useRisingEdge) { command = m_RISETRIGEDGESELCMD + "\n"; } else { command = m_FALLTRIGEDGESELCMD + "\n"; } IOWrite(command); //*** select trigger level command = m_TRIGLEVCMD + m_CHANCMD + channelNumber.ToString() + ", " + triggerLevel.ToString() + "\n"; IOWrite(command); /*command = m_ISOPCCMD + "\n"; rspStr = IOQuery(command); tokens = rspStr.Split('\n'); // check to see if trigger level command was completed if (IsOperationCompleted(tokens[0]) == false) { throw new Exception("Trigger level was not completed."); }*/ // start it back up command = m_RUNCMD + "\n"; IOWrite(command); } catch (Exception) { Reset(); throw; } } /// /// /// public void Shutdown() { string errorMsg = ""; if (_state == State.Ready) { try { //Reset System Reset(); } catch (Exception err) { errorMsg += err.Message + " "; } _state = State.Uninitialized; } // the stream was created in the constructor, dispose of it here try { if (_tcpStream != null) { _tcpStream.Dispose(); _tcpStream = null; } } catch (Exception err) { errorMsg += err.Message + " "; } if (errorMsg != "") { throw new Exception("System " + _name + " Had an error: " + errorMsg); } } #endregion } }