text: /***********************************************************************************************************************
  *                                                                                                                      *
  * libscopehal v0.1                                                                                                     *
  *                                                                                                                      *
  * Copyright (c) 2012-2021 Andrew D. Zonenberg and contributors                                                         *
  * All rights reserved.                                                                                                 *
  *                                                                                                                      *
  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
  * following conditions are met:                                                                                        *
  *                                                                                                                      *
  *    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
  *      following disclaimer.                                                                                           *
  *                                                                                                                      *
  *    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
  *      following disclaimer in the documentation and/or other materials provided with the distribution.                *
  *                                                                                                                      *
  *    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
  *      derived from this software without specific prior written permission.                                           *
  *                                                                                                                      *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
  * THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
  * POSSIBILITY OF SUCH DAMAGE.                                                                                          *
  *                                                                                                                      *
  ***********************************************************************************************************************/
  
  /*
   * Current State
   * =============
   * - Digital channels not implemented
   * - Only basic edge trigger supported. Coupling, hysteresis, B trigger not implemented
   * 
   * RS Oscilloscope driver parts (c) 2021 Francisco Sedano, tested on RTM3004 
   */
  
  
  #include "scopehal.h"
  #include "RohdeSchwarzOscilloscope.h"
  #include "EdgeTrigger.h"
  
  using namespace std;
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Construction / destruction
  
  RohdeSchwarzOscilloscope::RohdeSchwarzOscilloscope(SCPITransport* transport)
  	: SCPIOscilloscope(transport)
  	, m_triggerArmed(false)
  	, m_triggerOneShot(false)
  {
  	//Last digit of the model number is the number of channels
  	int model_number = atoi(m_model.c_str() + 3);	//FIXME: are all series IDs 3 chars e.g. "RTM"?
  	int nchans = model_number % 10;
  
  	for(int i=0; i<nchans; i++)
  	{
  		//Hardware name of the channel
  		string chname = string("CHAN1");
  		chname[4] += i;
  
  		//Color the channels based on R&S's standard color sequence (yellow-green-orange-bluegray)
  		string color = "#ffffff";
  		switch(i)
  		{
  			case 0:
  				color = "#ffff00";
  				break;
  
  			case 1:
  				color = "#00ff00";
  				break;
  
  			case 2:
  				color = "#ff8000";
  				break;
  
  			case 3:
  				color = "#8080ff";
  				break;
  		}
  
  		//Create the channel
  		auto chan = new OscilloscopeChannel(
  			this,
  			chname,
  			OscilloscopeChannel::CHANNEL_TYPE_ANALOG,
  			color,
  			1,
  			i,
  			true);
  		m_channels.push_back(chan);
  		chan->SetDefaultDisplayName();
  
  		//Request all points when we download
  		m_transport->SendCommand(chname + ":DATA:POIN MAX");
  	}
  	m_analogChannelCount = nchans;
  
  	//Add the external trigger input
  	m_extTrigChannel = new OscilloscopeChannel(
  		this,
  		"EX",
  		OscilloscopeChannel::CHANNEL_TYPE_TRIGGER,
  		"",
  		1,
  		m_channels.size(),
  		true);
  	m_channels.push_back(m_extTrigChannel);
  	m_extTrigChannel->SetDefaultDisplayName();
  
  	//Configure transport format to raw IEEE754 float, little endian
  	//TODO: if instrument internal is big endian, skipping the bswap might improve download performance?
  	//Might be faster to do it on a beefy x86 than the embedded side of things.
  	m_transport->SendCommand("FORM:DATA REAL");
  	m_transport->SendCommand("FORM:BORD LSBFirst");
  
  	//See what options we have
  	m_transport->SendCommand("*OPT?");
  	string reply = m_transport->ReadReply();
  	vector<string> options;
  	string opt;
  	for(unsigned int i=0; i<reply.length(); i++)
  	{
  		if(reply[i] == 0)
  		{
  			options.push_back(opt);
  			break;
  		}
  
  		else if(reply[i] == ',')
  		{
  			options.push_back(opt);
  			opt = "";
  		}
  
  		else
  			opt += reply[i];
  	}
  	if(opt != "")
  		options.push_back(opt);
  
  	//Print out the option list and do processing for each
  	LogDebug("Installed options:\n");
  	if(options.empty())
  		LogDebug("* None\n");
  	for(auto sopt : options)
  	{
  		LogDebug(" * %s ",sopt.c_str());
  		if(opt == "B243")
  			LogDebug("(350 MHz bandwidth upgrade for RTM3004)\n");
  		else if(sopt == "K1")
  			LogDebug("(SPI Bus)\n");
  		else if(sopt == "K2")
  			LogDebug("(UART / RS232)\n");
  		else if(sopt == "K3")
  			LogDebug("(CAN)\n");
  		else if(sopt == "K5")
  			LogDebug("(Audio signals)\n");
  		else if(sopt == "B1") {
  			// TODO add digital channels
  			LogDebug("(Mixed signal, 16 channels)\n");
  		}
  		else if(sopt == "K31")
  			LogDebug("(Power analysis)\n");
  		else if(sopt == "K6")
  			LogDebug("(MIL-1553)\n");
  		else if(sopt == "K7")
  			LogDebug("(ARINC 429)\n");
  		else if(sopt == "K15")
  			LogDebug("(History)\n");
  		else if(sopt == "K18")
  			LogDebug("(Spectrum analysis and spectrogram)\n");
  		else if(sopt == "B6")
  			LogDebug("(Signal generation)\n");
  		else if(sopt == "B2410")
  			LogDebug("(Bandwidth 1 GHz)\n");
  		else if(sopt == "K36")
  			LogDebug("(Frequency response analysis)\n");
  		else
  			LogDebug("(unknown)\n");
  	}
  }
  
  RohdeSchwarzOscilloscope::~RohdeSchwarzOscilloscope()
  {
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Accessors
  
  string RohdeSchwarzOscilloscope::GetDriverNameInternal()
  {
  	return "rs";
  }
  
  unsigned int RohdeSchwarzOscilloscope::GetInstrumentTypes()
  {
  	return Instrument::INST_OSCILLOSCOPE;
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Device interface functions
  
  void RohdeSchwarzOscilloscope::FlushConfigCache()
  {
  	lock_guard<recursive_mutex> lock(m_cacheMutex);
  
  	m_channelOffsets.clear();
  	m_channelVoltageRanges.clear();
  	m_channelsEnabled.clear();
  	m_channelCouplings.clear();
  	m_channelAttenuations.clear();
  
  	delete m_trigger;
  	m_trigger = NULL;
  }
  
  bool RohdeSchwarzOscilloscope::IsChannelEnabled(size_t i)
  {
  	//ext trigger should never be displayed
  	if(i == m_extTrigChannel->GetIndex())
  		return false;
  
  	//TODO: handle digital channels, for now just claim they're off
  	if(i >= m_analogChannelCount)
  		return false;
  
  	lock_guard<recursive_mutex> lock(m_cacheMutex);
  
  	if(m_channelsEnabled.find(i) != m_channelsEnabled.end())
  		return m_channelsEnabled[i];
  
  	lock_guard<recursive_mutex> lock2(m_mutex);
  
  	m_transport->SendCommand(m_channels[i]->GetHwname() + ":STAT?");
  	string reply = m_transport->ReadReply();
  	if(reply == "OFF" || reply == "0")
  	{
  		m_channelsEnabled[i] = false;
  		return false;
  	}
  	else
  	{
  		m_channelsEnabled[i] = true;
  		return true;
  	}
  }
  
  void RohdeSchwarzOscilloscope::EnableChannel(size_t i)
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  	m_transport->SendCommand(m_channels[i]->GetHwname() + ":STAT ON");
  
  	lock_guard<recursive_mutex> lock2(m_cacheMutex);
  	m_channelsEnabled[i] = true;
  }
  
  void RohdeSchwarzOscilloscope::DisableChannel(size_t i)
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  	m_transport->SendCommand(m_channels[i]->GetHwname() + ":STAT OFF");
  
  	lock_guard<recursive_mutex> lock2(m_cacheMutex);
  	m_channelsEnabled[i] = false;
  }
  
  vector<OscilloscopeChannel::CouplingType> RohdeSchwarzOscilloscope::GetAvailableCouplings(size_t /*i*/)
  {
  	vector<OscilloscopeChannel::CouplingType> ret;
  	ret.push_back(OscilloscopeChannel::COUPLE_DC_1M);
  	ret.push_back(OscilloscopeChannel::COUPLE_AC_1M);
  	ret.push_back(OscilloscopeChannel::COUPLE_DC_50);
  	ret.push_back(OscilloscopeChannel::COUPLE_GND);
  	return ret;
  }
  
  OscilloscopeChannel::CouplingType RohdeSchwarzOscilloscope::GetChannelCoupling(size_t i)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_cacheMutex);
  		if(m_channelCouplings.find(i) != m_channelCouplings.end())
  			return m_channelCouplings[i];
  	}
  
  	string reply;
  	{
  		lock_guard<recursive_mutex> lock(m_mutex);
  
  		m_transport->SendCommand(m_channels[i]->GetHwname() + ":COUP?");
  		reply = m_transport->ReadReply();
  	}
  	OscilloscopeChannel::CouplingType coupling;
  
  	if(reply == "ACLimit" || reply == "ACL")
  		coupling = OscilloscopeChannel::COUPLE_AC_1M;
  	else if(reply == "DCLimit" || reply == "DCL")
  		coupling = OscilloscopeChannel::COUPLE_DC_1M;
  	else if(reply == "GND")
  		coupling = OscilloscopeChannel::COUPLE_GND;
  	else if(reply == "DC")
  		coupling = OscilloscopeChannel::COUPLE_DC_50;
  	else
  	{
  		LogWarning("invalid coupling value\n");
  		coupling = OscilloscopeChannel::COUPLE_DC_50;
  	}
  
  	lock_guard<recursive_mutex> lock(m_cacheMutex);
  	m_channelCouplings[i] = coupling;
  	return coupling;
  }
  
  void RohdeSchwarzOscilloscope::SetChannelCoupling(size_t i, OscilloscopeChannel::CouplingType type)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_mutex);
  		switch(type)
  		{
  			case OscilloscopeChannel::COUPLE_DC_50:
  				m_transport->SendCommand(m_channels[i]->GetHwname() + ":COUP DC");
  				break;
  
  			case OscilloscopeChannel::COUPLE_AC_1M:
  				m_transport->SendCommand(m_channels[i]->GetHwname() + ":COUP ACLimit");
  				break;
  
  			case OscilloscopeChannel::COUPLE_DC_1M:
  				m_transport->SendCommand(m_channels[i]->GetHwname() + ":COUP DCLimit");
  				break;
  
  			case OscilloscopeChannel::COUPLE_GND:
  				m_transport->SendCommand(m_channels[i]->GetHwname() + ":COUP GND");
  				break;
  
  			default:
  				LogError("Invalid coupling for channel\n");
  		}
  	}
  	lock_guard<recursive_mutex> lock(m_cacheMutex);
  	m_channelCouplings[i] = type;
  }
  
  double RohdeSchwarzOscilloscope::GetChannelAttenuation(size_t i)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_cacheMutex);
  		if(m_channelAttenuations.find(i) != m_channelAttenuations.end())
  			return m_channelAttenuations[i];
  	}
  	// FIXME Don't know SCPI to get this, relying on cache
  	return 1;
  }
  
  void RohdeSchwarzOscilloscope::SetChannelAttenuation(size_t i, double atten)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_cacheMutex);
  		m_channelAttenuations[i] = atten;
  	}
  
  	lock_guard<recursive_mutex> lock(m_mutex);
  
  	char cmd[128];
  	snprintf(cmd, sizeof(cmd), "PROB%zd:SET:ATT:MAN ", m_channels[i]->GetIndex()+1);
  	PushFloat(cmd, atten);
  }
  
  int RohdeSchwarzOscilloscope::GetChannelBandwidthLimit(size_t /*i*/)
  {
  	/*
  	lock_guard<recursive_mutex> lock(m_mutex);
  
  	m_transport->SendCommand(m_channels[i]->GetHwname() + ":BWL?");
  	string reply = m_transport->ReadReply();
  	if(reply == "20M")
  		return 20;
  	else
  		return 0;
  	*/
  
  	LogWarning("RohdeSchwarzOscilloscope::GetChannelBandwidthLimit unimplemented\n");
  	return 0;
  }
  
  void RohdeSchwarzOscilloscope::SetChannelBandwidthLimit(size_t /*i*/, unsigned int /*limit_mhz*/)
  {
  	LogWarning("RohdeSchwarzOscilloscope::SetChannelBandwidthLimit unimplemented\n");
  }
  
  double RohdeSchwarzOscilloscope::GetChannelVoltageRange(size_t i)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_cacheMutex);
  		if(m_channelVoltageRanges.find(i) != m_channelVoltageRanges.end())
  			return m_channelVoltageRanges[i];
  	}
  
  	lock_guard<recursive_mutex> lock2(m_mutex);
  
  	m_transport->SendCommand(m_channels[i]->GetHwname() + ":RANGE?");
  
  	string reply = m_transport->ReadReply();
  	double range;
  	sscanf(reply.c_str(), "%lf", &range);
  	lock_guard<recursive_mutex> lock(m_cacheMutex);
  	m_channelVoltageRanges[i] = range;
  	return range;
  }
  
  void RohdeSchwarzOscilloscope::SetChannelVoltageRange(size_t i, double range)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_cacheMutex);
  		m_channelVoltageRanges[i] = range;
  	}
  
  	lock_guard<recursive_mutex> lock(m_mutex);
  	char cmd[128];
  	snprintf(cmd, sizeof(cmd), "%s:RANGE %.4f", m_channels[i]->GetHwname().c_str(), range);
  	m_transport->SendCommand(cmd);
  }
  
  OscilloscopeChannel* RohdeSchwarzOscilloscope::GetExternalTrigger()
  {
  	//FIXME
  	LogWarning("RohdeSchwarzOscilloscope::GetExternalTrigger unimplemented\n");
  	return NULL;
  }
  
  double RohdeSchwarzOscilloscope::GetChannelOffset(size_t i)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_cacheMutex);
  
  		if(m_channelOffsets.find(i) != m_channelOffsets.end())
  			return m_channelOffsets[i];
  	}
  
  	lock_guard<recursive_mutex> lock2(m_mutex);
  
  	m_transport->SendCommand(m_channels[i]->GetHwname() + ":OFFS?");
  
  	string reply = m_transport->ReadReply();
  	double offset;
  	sscanf(reply.c_str(), "%lf", &offset);
  	offset = -offset;
  	lock_guard<recursive_mutex> lock(m_cacheMutex);
  	m_channelOffsets[i] = offset;
  	return offset;
  }
  
  void RohdeSchwarzOscilloscope::SetChannelOffset(size_t i, double offset)
  {
  	{
  		lock_guard<recursive_mutex> lock(m_cacheMutex);
  		m_channelOffsets[i] = offset;
  	}
  
  	lock_guard<recursive_mutex> lock(m_mutex);
  	char cmd[128];
  	snprintf(cmd, sizeof(cmd), "%s:OFFS %.4f", m_channels[i]->GetHwname().c_str(), -offset);
  	m_transport->SendCommand(cmd);
  }
  
  Oscilloscope::TriggerMode RohdeSchwarzOscilloscope::PollTrigger()
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  
  	m_transport->SendCommand("ACQ:STAT?");
  	string stat = m_transport->ReadReply();
  
  	if(stat == "RUN")
  		return TRIGGER_MODE_RUN;
  	else if( (stat == "STOP") || (stat == "BRE") )
  		return TRIGGER_MODE_STOP;
  	else /*if(stat == "COMP")*/
  	{
  		m_triggerArmed = false;
  		return TRIGGER_MODE_TRIGGERED;
  	}
  }
  
  bool RohdeSchwarzOscilloscope::AcquireData()
  {
  	//LogDebug("Acquiring data\n");
  
  	lock_guard<recursive_mutex> lock(m_mutex);
  	LogIndenter li;
  
  	double xstart;
  	double xstop;
  	size_t length;
  	int ignored;
  	map<int, vector<AnalogWaveform*> > pending_waveforms;
  	bool any_data = false;
  	for(size_t i=0; i<m_analogChannelCount; i++)
  	{
  		if(!IsChannelEnabled(i))
  			continue;
  
  		//This is basically the same function as a LeCroy WAVEDESC, but much less detailed
  		m_transport->SendCommand(m_channels[i]->GetHwname() + ":DATA:HEAD?");
  		string reply = m_transport->ReadReply();
  		int rc = sscanf(reply.c_str(), "%lf,%lf,%zu,%d", &xstart, &xstop, &length, &ignored);
  		if (rc != 4 || length == 0) {
  			/* No data - Skip query the scope and move on */
  			pending_waveforms[i].push_back(NULL);
  			continue;
  		}
  		any_data = true;
  		//Figure out the sample rate
  		double capture_len_sec = xstop - xstart;
  		double sec_per_sample = capture_len_sec / length;
  		int64_t fs_per_sample = round(sec_per_sample * FS_PER_SECOND);
  		//LogDebug("%ld fs/sample\n", fs_per_sample);
  
  		float* temp_buf = new float[length];
  
  		//Set up the capture we're going to store our data into (no high res timer on R&S scopes)
  		AnalogWaveform* cap = new AnalogWaveform;
  		cap->m_timescale = fs_per_sample;
  		cap->m_triggerPhase = 0;
  		cap->m_startTimestamp = time(NULL);
  		double t = GetTime();
  		cap->m_startFemtoseconds = (t - floor(t)) * FS_PER_SECOND;
  
  		//Ask for the data
  		m_transport->SendCommand(m_channels[i]->GetHwname() + ":DATA?");
  
  		//Read and discard the length header
  		char tmp[16] = {0};
  		m_transport->ReadRawData(2, (unsigned char*)tmp);
  		int num_digits = atoi(tmp+1);
  		m_transport->ReadRawData(num_digits, (unsigned char*)tmp);
  		//int actual_len = atoi(tmp);
  
  		//Read the actual data
  		m_transport->ReadRawData(length*sizeof(float), (unsigned char*)temp_buf);
  		//Discard trailing newline
  		m_transport->ReadRawData(1, (unsigned char*)tmp);
  
  
  		//Format the capture
  		cap->Resize(length);
  		for(size_t j=0; j<length; j++)
  		{
  			cap->m_offsets[j] = j;
  			cap->m_durations[j] = 1;
  			cap->m_samples[j] = temp_buf[j];
  		}
  
  		//Done, update the data
  		pending_waveforms[i].push_back(cap);
  
  		//Clean up
  		delete[] temp_buf;
  	}
  	if (!any_data) {
  		LogDebug("Skip update, no data from scope\n");
  		//Re-arm the trigger if not in one-shot mode
  		if(!m_triggerOneShot)
  		{
  			m_transport->SendCommand("SING");
  			m_triggerArmed = true;
  		}
  
  		return false;
  	}
  	//Now that we have all of the pending waveforms, save them in sets across all channels
  	m_pendingWaveformsMutex.lock();
  	size_t num_pending = 1;	//TODO: segmented capture support
  	for(size_t i=0; i<num_pending; i++)
  	{
  		SequenceSet s;
  		for(size_t j=0; j<m_analogChannelCount; j++)
  		{
  			if(IsChannelEnabled(j))
  				s[m_channels[j]] = pending_waveforms[j][i];
  		}
  		m_pendingWaveforms.push_back(s);
  	}
  	m_pendingWaveformsMutex.unlock();
  
  	//TODO: support digital channels
  
  	//Re-arm the trigger if not in one-shot mode
  	if(!m_triggerOneShot)
  	{
  		m_transport->SendCommand("SING");
  		m_triggerArmed = true;
  	}
  
  	//LogDebug("Acquisition done\n");
  	return true;
  }
  
  void RohdeSchwarzOscilloscope::Start()
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  	m_transport->SendCommand("SING");
  	m_triggerArmed = true;
  	m_triggerOneShot = false;
  }
  
  void RohdeSchwarzOscilloscope::StartSingleTrigger()
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  	m_transport->SendCommand("SING");
  	m_triggerArmed = true;
  	m_triggerOneShot = true;
  }
  
  void RohdeSchwarzOscilloscope::Stop()
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  	m_transport->SendCommand("STOP");
  	m_triggerArmed = false;
  	m_triggerOneShot = true;
  }
  
  bool RohdeSchwarzOscilloscope::IsTriggerArmed()
  {
  	return m_triggerArmed;
  }
  
  vector<uint64_t> RohdeSchwarzOscilloscope::GetSampleRatesNonInterleaved()
  {
  	LogWarning("RohdeSchwarzOscilloscope::GetSampleRatesNonInterleaved unimplemented\n");
  
  	//FIXME
  	vector<uint64_t> ret;
  	return ret;
  }
  
  vector<uint64_t> RohdeSchwarzOscilloscope::GetSampleRatesInterleaved()
  {
  	LogWarning("RohdeSchwarzOscilloscope::GetSampleRatesInterleaved unimplemented\n");
  
  	//FIXME
  	vector<uint64_t> ret;
  	return ret;
  }
  
  set<Oscilloscope::InterleaveConflict> RohdeSchwarzOscilloscope::GetInterleaveConflicts()
  {
  	LogWarning("RohdeSchwarzOscilloscope::GetInterleaveConflicts unimplemented\n");
  
  	//FIXME
  	set<Oscilloscope::InterleaveConflict> ret;
  	return ret;
  }
  
  vector<uint64_t> RohdeSchwarzOscilloscope::GetSampleDepthsNonInterleaved()
  {
  	LogWarning("RohdeSchwarzOscilloscope::GetSampleDepthsNonInterleaved unimplemented\n");
  
  	//FIXME
  	vector<uint64_t> ret;
  	return ret;
  }
  
  vector<uint64_t> RohdeSchwarzOscilloscope::GetSampleDepthsInterleaved()
  {
  	LogWarning("RohdeSchwarzOscilloscope::GetSampleDepthsInterleaved unimplemented\n");
  
  	//FIXME
  	vector<uint64_t> ret;
  	return ret;
  }
  
  uint64_t RohdeSchwarzOscilloscope::GetSampleRate()
  {
  	//FIXME
  	return 1;
  }
  
  uint64_t RohdeSchwarzOscilloscope::GetSampleDepth()
  {
  	//FIXME
  	return 1;
  }
  
  void RohdeSchwarzOscilloscope::SetSampleDepth(uint64_t /*depth*/)
  {
  	//FIXME
  }
  
  void RohdeSchwarzOscilloscope::SetSampleRate(uint64_t /*rate*/)
  {
  	//FIXME
  }
  
  void RohdeSchwarzOscilloscope::SetTriggerOffset(int64_t /*offset*/)
  {
  	//FIXME
  }
  
  int64_t RohdeSchwarzOscilloscope::GetTriggerOffset()
  {
  	//FIXME
  	return 0;
  }
  
  bool RohdeSchwarzOscilloscope::IsInterleaving()
  {
  	return false;
  }
  
  bool RohdeSchwarzOscilloscope::SetInterleaving(bool /*combine*/)
  {
  	return false;
  }
  
  void RohdeSchwarzOscilloscope::PullTrigger()
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  
  	//TODO: Figure out trigger type
  	if(true)
  		PullEdgeTrigger();
  
  	//Unrecognized trigger type
  	else
  	{
  		LogWarning("Unknown trigger type\n");
  		delete m_trigger;
  		m_trigger = NULL;
  		return;
  	}
  }
  
  /**
  	@brief Reads settings for an edge trigger from the instrument
   */
  void RohdeSchwarzOscilloscope::PullEdgeTrigger()
  {
  	//Clear out any triggers of the wrong type
  	if( (m_trigger != NULL) && (dynamic_cast<EdgeTrigger*>(m_trigger) != NULL) )
  	{
  		delete m_trigger;
  		m_trigger = NULL;
  	}
  
  	//Create a new trigger if necessary
  	if(m_trigger == NULL)
  		m_trigger = new EdgeTrigger(this);
  	EdgeTrigger* et = dynamic_cast<EdgeTrigger*>(m_trigger);
  
  	lock_guard<recursive_mutex> lock(m_mutex);
  
  	//Source
  	//This is a bit annoying because the hwname's used here are DIFFERENT than everywhere else!
  	m_transport->SendCommand("TRIG:A:SOUR?");
  	string reply = m_transport->ReadReply();
  	if(reply.find("CH") == 0)
  		et->SetInput(0, StreamDescriptor(m_channels[atoi(reply.c_str()+2) - 1], 0), true);
  	else if(reply == "EXT")
  		et->SetInput(0, StreamDescriptor(m_extTrigChannel, 0), true);
  	else
  		LogWarning("Unknown trigger source %s\n", reply.c_str());
  
  	//Level
  	m_transport->SendCommand("TRIG:A:LEV?");
  	reply = m_transport->ReadReply();
  	et->SetLevel(stof(reply));
  
  	//TODO: Edge slope
  }
  
  void RohdeSchwarzOscilloscope::PushTrigger()
  {
  	auto et = dynamic_cast<EdgeTrigger*>(m_trigger);
  	if(et)
  		PushEdgeTrigger(et);
  
  	else
  		LogWarning("Unknown trigger type (not an edge)\n");
  }
  
  /**
  	@brief Pushes settings for an edge trigger to the instrument
   */
  void RohdeSchwarzOscilloscope::PushEdgeTrigger(EdgeTrigger* trig)
  {
  	lock_guard<recursive_mutex> lock(m_mutex);
  	char tmp[81];
  
  	// They use CH1, CH2 and so on here :-(
  
  	snprintf(tmp, sizeof(tmp), "TRIG:A:SOUR CH%zd", trig->GetInput(0).m_channel->GetIndex()+1);
  	m_transport->SendCommand(tmp);
  
  	snprintf(tmp, sizeof(tmp), "TRIG:A:LEV%zd %f", trig->GetInput(0).m_channel->GetIndex()+1, trig->GetLevel());
  	m_transport->SendCommand(tmp);
  
  	string slope_str;
  	switch(trig->GetType())
  	{
  		case EdgeTrigger::EDGE_RISING:
  			slope_str = "POS";
  			break;
  		case EdgeTrigger::EDGE_FALLING:
  			slope_str = "NEG";
  			break;
  		case EdgeTrigger::EDGE_ANY:
  			slope_str = "EITH";
  			break;
  		default:
  			LogDebug("Unsupported edge type: %d\n", trig->GetType());
  			return;
  	}
  	m_transport->SendCommand("TRIG:A:EDGE:SLOP " + slope_str);
  }
  
  /**
  	@brief Sends float, assumes transport is already mutexed
   */
  
  void RohdeSchwarzOscilloscope::PushFloat(string path, float f)
  {
  	m_transport->SendCommand(path + " " + to_string_sci(f));
  }
  
  