#include "virtualdevice.h" #include "preferences.h" #include "CustomWidgets/informationbox.h" #include "../../VNA_embedded/Application/Communication/Protocol.hpp" #include static VirtualDevice *connected = nullptr; using namespace std; class Reference { public: enum class TypeIn { Internal, External, Auto, None }; enum class OutFreq { MHZ10, MHZ100, Off, None }; static QString OutFreqToLabel(Reference::OutFreq t) { switch(t) { case OutFreq::MHZ10: return "10 MHz"; case OutFreq::MHZ100: return "100 MHz"; case OutFreq::Off: return "Off"; default: return "Invalid"; } } static QString OutFreqToKey(Reference::OutFreq f) { switch(f) { case OutFreq::MHZ10: return "10 MHz"; case OutFreq::MHZ100: return "100 MHz"; case OutFreq::Off: return "Off"; default: return "Invalid"; } } static Reference::OutFreq KeyToOutFreq(QString key) { for (auto r: Reference::getOutFrequencies()) { if(OutFreqToKey(r) == key|| OutFreqToLabel(r) == key) { return r; } } // not found return Reference::OutFreq::None; } static QString TypeToLabel(TypeIn t) { switch(t) { case TypeIn::Internal: return "Internal"; case TypeIn::External: return "External"; case TypeIn::Auto: return "Auto"; default: return "Invalid"; } } static const QString TypeToKey(TypeIn t) { switch(t) { case TypeIn::Internal: return "Int"; case TypeIn::External: return "Ext"; case TypeIn::Auto: return "Auto"; default: return "Invalid"; } } static TypeIn KeyToType(QString key) { for (auto r: Reference::getReferencesIn()) { if(TypeToKey(r) == key || TypeToLabel(r) == key) { return r; } } // not found return TypeIn::None; } static std::vector getReferencesIn() { return {TypeIn::Internal, TypeIn::External, TypeIn::Auto}; } static std::vector getOutFrequencies() { return {OutFreq::Off, OutFreq::MHZ10, OutFreq::MHZ100}; } }; VirtualDevice::VirtualDevice(QString serial) : QObject(), info{}, status{} { cdev = nullptr; zerospan = false; // Check if this is a compound device auto& pref = Preferences::getInstance(); for(auto cd : pref.compoundDevices) { if(cd->name == serial) { // connect request to this compound device cdev = cd; break; } } if(!isCompoundDevice()) { // just acting as a wrapper for device, pass on signals auto dev = new Device(serial); devices.push_back(dev); connect(dev, &Device::ConnectionLost, this, &VirtualDevice::ConnectionLost, Qt::QueuedConnection); connect(dev, &Device::DeviceInfoUpdated, this, [=](){ info = Info(devices[0]); emit InfoUpdated(); }, Qt::QueuedConnection); connect(dev, &Device::LogLineReceived, this, &VirtualDevice::LogLineReceived, Qt::QueuedConnection); connect(dev, &Device::DeviceStatusUpdated, this, [=](){ status = Status(devices[0]); emit StatusUpdated(status); }, Qt::QueuedConnection); connect(dev, &Device::NeedsFirmwareUpdate, this, &VirtualDevice::NeedsFirmwareUpdate, Qt::QueuedConnection); connect(dev, &Device::SpectrumResultReceived, this, &VirtualDevice::singleSpectrumResultReceived, Qt::QueuedConnection); connect(dev, &Device::DatapointReceived, this, &VirtualDevice::singleDatapointReceived, Qt::QueuedConnection); } else { // Connect to the actual devices for(auto devSerial : cdev->deviceSerials) { auto dev = new Device(devSerial, true); devices.push_back(dev); // Create device connections connect(dev, &Device::ConnectionLost, this, &VirtualDevice::ConnectionLost, Qt::QueuedConnection); connect(dev, &Device::NeedsFirmwareUpdate, this, &VirtualDevice::NeedsFirmwareUpdate, Qt::QueuedConnection); connect(dev, &Device::LogLineReceived, this, [=](QString line){ emit LogLineReceived(line.prepend(dev->serial()+": ")); }, Qt::QueuedConnection); connect(dev, &Device::DeviceInfoUpdated, this, &VirtualDevice::compoundInfoUpdated, Qt::QueuedConnection); connect(dev, &Device::DeviceStatusUpdated, this, &VirtualDevice::compoundStatusUpdated, Qt::QueuedConnection); connect(dev, &Device::DatapointReceived, this, &VirtualDevice::compoundDatapointReceivecd, Qt::QueuedConnection); connect(dev, &Device::SpectrumResultReceived, this, &VirtualDevice::compoundSpectrumResultReceived, Qt::QueuedConnection); } if(cdev->sync == CompoundDevice::Synchronization::USB) { // create trigger connections for USB synchronization for(unsigned int i=0;i("Status"); qRegisterMetaType("VNAMeasurement"); qRegisterMetaType("SAMeasurement"); } void VirtualDevice::initialize() { for(auto dev : devices) { dev->SendCommandWithoutPayload(Protocol::PacketType::RequestDeviceInfo); dev->SendCommandWithoutPayload(Protocol::PacketType::RequestDeviceStatus); } } bool VirtualDevice::isCompoundDevice() const { return cdev != nullptr; } Device *VirtualDevice::getDevice() { if(isCompoundDevice() || devices.size() < 1) { return nullptr; } else { return devices[0]; } } CompoundDevice *VirtualDevice::getCompoundDevice() { return cdev; } std::vector VirtualDevice::getDevices() { return devices; } const VirtualDevice::Info &VirtualDevice::getInfo() const { return info; } VirtualDevice::Info VirtualDevice::getInfo(VirtualDevice *vdev) { if(vdev) { return vdev->info; } else { return Info(); } } const VirtualDevice::Status &VirtualDevice::getStatus() const { return status; } VirtualDevice::Status VirtualDevice::getStatus(VirtualDevice *vdev) { if(vdev) { return vdev->status; } else { return Status(); } } QStringList VirtualDevice::availableVNAMeasurements() { QStringList ret; for(unsigned int i=1;i<=info.ports;i++) { for(unsigned int j=1;j<=info.ports;j++) { ret.push_back("S"+QString::number(i)+QString::number(j)); } } auto &pref = Preferences::getInstance(); if(pref.Debug.makeRawReceiverValuesAvailable) { for(unsigned int i=1;i<=info.ports;i++) { for(unsigned int j=0;j cb) { if(!info.supportsVNAmode) { return false; } if(s.excitedPorts.size() == 0) { return setIdle(cb); } // create port->stage mapping portStageMapping.clear(); for(unsigned int i=0;iConfigure(sd, [=](Device::TransmissionResult r){ if(cb) { cb(r == Device::TransmissionResult::Ack); } }); } else { // set the synchronization mode switch(cdev->sync) { case CompoundDevice::Synchronization::USB: sd.syncMode = 1; break; case CompoundDevice::Synchronization::ExtRef: sd.syncMode = 2; break; case CompoundDevice::Synchronization::Trigger: sd.syncMode = 3; break; case CompoundDevice::Synchronization::Last: sd.syncMode = 1; break; // should never get here } // create vector of currently used stimulus ports vector activeMapping; for(auto p : s.excitedPorts) { activeMapping.push_back(cdev->portMapping[p]); } // Configure the devices results.clear(); bool success = true; for(unsigned int i=0;iConfigure(sd, [=](Device::TransmissionResult r){ if(cb) { results[devices[i]] = r; checkIfAllTransmissionsComplete(cb); } }); } return success; } } QString VirtualDevice::serial() { if(!isCompoundDevice()) { return devices[0]->serial(); } else { return cdev->name; } } QStringList VirtualDevice::availableSAMeasurements() { QStringList ret; for(unsigned int i=1;i<=info.ports;i++) { ret.push_back("PORT"+QString::number(i)); } return ret; } bool VirtualDevice::setSA(const VirtualDevice::SASettings &s, std::function cb) { if(!info.supportsSAmode) { return false; } zerospan = s.freqStart == s.freqStop; auto& pref = Preferences::getInstance(); Protocol::SpectrumAnalyzerSettings sd = {}; sd.f_start = s.freqStart; sd.f_stop = s.freqStop; sd.pointNum = s.points; sd.RBW = s.RBW; sd.WindowType = (int) s.window; sd.SignalID = s.signalID ? 1 : 0; sd.Detector = (int) s.detector; sd.UseDFT = 0; if(!s.trackingGenerator && pref.Acquisition.useDFTinSAmode && s.RBW <= pref.Acquisition.RBWLimitForDFT) { sd.UseDFT = 1; } sd.applyReceiverCorrection = 1; sd.trackingGeneratorOffset = s.trackingOffset; sd.trackingPower = s.trackingPower; if(!isCompoundDevice()) { sd.trackingGenerator = s.trackingGenerator ? 1 : 0; sd.trackingGeneratorPort = s.trackingPort; sd.syncMode = 0; sd.syncMaster = 0; return devices[0]->Configure(sd, [=](Device::TransmissionResult r){ if(cb) { cb(r == Device::TransmissionResult::Ack); } }); } else { // set the synchronization mode switch(cdev->sync) { case CompoundDevice::Synchronization::USB: sd.syncMode = 1; break; case CompoundDevice::Synchronization::ExtRef: sd.syncMode = 2; break; case CompoundDevice::Synchronization::Trigger: sd.syncMode = 3; break; case CompoundDevice::Synchronization::Last: sd.syncMode = 1; break; // should never get here } // Configure the devices results.clear(); bool success = true; for(unsigned int i=0;iportMapping, i, 0) == s.trackingPort) { sd.trackingGenerator = 1; sd.trackingGeneratorPort = 0; } else if(CompoundDevice::PortMapping::findActiveStage(cdev->portMapping, i, 1) == s.trackingPort) { sd.trackingGenerator = 1; sd.trackingGeneratorPort = 1; } } sd.syncMaster = i == 0 ? 1 : 0; success &= devices[i]->Configure(sd, [=](Device::TransmissionResult r){ if(cb) { results[devices[i]] = r; checkIfAllTransmissionsComplete(cb); } }); } return success; } } QStringList VirtualDevice::availableSGPorts() { QStringList ret; for(unsigned int i=1;iSendPacket(packet); } else { // configure all devices bool success = true; for(unsigned int i=0;i 0) { if(cdev->portMapping[s.port-1].device == i) { // this device has the active port sd.activePort = cdev->portMapping[s.port-1].port+1; } } success &= devices[i]->SendPacket(packet); } return success; } } bool VirtualDevice::setIdle(std::function cb) { auto success = true; results.clear(); for(auto dev : devices) { success &= dev->SetIdle([=](Device::TransmissionResult r){ if(cb) { results[dev] = r; checkIfAllTransmissionsComplete(cb); } }); } return success; } QStringList VirtualDevice::availableExtRefInSettings() { QStringList ret; for(auto r : Reference::getReferencesIn()) { ret.push_back(Reference::TypeToLabel(r)); } return ret; } QStringList VirtualDevice::availableExtRefOutSettings() { QStringList ret; for(auto r : Reference::getOutFrequencies()) { ret.push_back(Reference::OutFreqToLabel(r)); } return ret; } bool VirtualDevice::setExtRef(QString option_in, QString option_out) { if(!info.supportsExtRef) { return false; } auto refIn = Reference::KeyToType(option_in); if(refIn == Reference::TypeIn::None) { refIn = Reference::TypeIn::Internal; } auto refOut = Reference::KeyToOutFreq(option_out); if(refOut == Reference::OutFreq::None) { refOut = Reference::OutFreq::Off; } Protocol::PacketInfo p = {}; p.type = Protocol::PacketType::Reference; switch(refIn) { case Reference::TypeIn::Internal: case Reference::TypeIn::None: p.reference.UseExternalRef = 0; p.reference.AutomaticSwitch = 0; break; case Reference::TypeIn::Auto: p.reference.UseExternalRef = 0; p.reference.AutomaticSwitch = 1; break; case Reference::TypeIn::External: p.reference.UseExternalRef = 1; p.reference.AutomaticSwitch = 0; break; } switch(refOut) { case Reference::OutFreq::None: case Reference::OutFreq::Off: p.reference.ExtRefOuputFreq = 0; break; case Reference::OutFreq::MHZ10: p.reference.ExtRefOuputFreq = 10000000; break; case Reference::OutFreq::MHZ100: p.reference.ExtRefOuputFreq = 100000000; break; } bool success = true; for(auto dev : devices) { success &= dev->SendPacket(p); } return success; } std::set VirtualDevice::GetAvailableVirtualDevices() { auto& pref = Preferences::getInstance(); auto ret = Device::GetDevices(); // Add compound devices as well for(auto vdev : pref.compoundDevices) { // check if all serial number required for this compound device are available bool serialMissing = false; for(auto s : vdev->deviceSerials) { if(ret.count(s) == 0) { serialMissing = true; break; } } if(!serialMissing) { // this compound device is available ret.insert(vdev->name); } } return ret; } VirtualDevice *VirtualDevice::getConnected() { return connected; } void VirtualDevice::singleDatapointReceived(Device *dev, Protocol::VNADatapoint<32> *res) { Q_UNUSED(dev) auto &pref = Preferences::getInstance(); VNAMeasurement m; m.pointNum = res->pointNum; m.Z0 = 50.0; if(zerospan) { m.us = res->us; } else { m.frequency = res->frequency; m.dBm = (double) res->cdBm / 100; } for(auto map : portStageMapping) { // map.first is the port (starts at zero) // map.second is the stage at which this port had the stimulus (starts at zero) complex ref = res->getValue(map.second, map.first, true); for(int i=0;i input = res->getValue(map.second, i, false); if(!std::isnan(ref.real()) && !std::isnan(input.real())) { // got both required measurements QString name = "S"+QString::number(i+1)+QString::number(map.first+1); m.measurements[name] = input / ref; } if(pref.Debug.makeRawReceiverValuesAvailable) { QString name = "RawPort"+QString::number(i+1)+"Stage"+QString::number(map.first); m.measurements[name] = input; name = "RawPort"+QString::number(i+1)+"Stage"+QString::number(map.first)+"Ref"; m.measurements[name] = res->getValue(map.second, i, true); } } } delete res; emit VNAmeasurementReceived(m); } void VirtualDevice::compoundDatapointReceivecd(Device *dev, Protocol::VNADatapoint<32> *data) { if(!compoundVNABuffer.count(data->pointNum)) { compoundVNABuffer[data->pointNum] = std::map*>(); } auto &buf = compoundVNABuffer[data->pointNum]; buf[dev] = data; if(buf.size() == devices.size()) { // Got datapoints from all devices, can create merged VNA result VNAMeasurement m; m.pointNum = data->pointNum; m.Z0 = 50.0; if(zerospan) { m.us = data->us; } else { m.frequency = data->frequency; m.dBm = (double) data->cdBm / 100; } // assemble data for(auto map : portStageMapping) { // map.first is the port (starts at zero) // map.second is the stage at which this port had the stimulus (starts at zero) // figure out which device had the stimulus for the port... auto stimulusDev = devices[cdev->portMapping[map.first].device]; // ...and which device port was used for the stimulus... auto stimulusDevPort = cdev->portMapping[map.first].port; // ...grab the reference receiver data complex ref = buf[stimulusDev]->getValue(map.second, stimulusDevPort, true); // for all ports of the compound device... for(unsigned int i=0;iportMapping.size();i++) { // ...figure out which physical device and port was used for this input... auto inputDevice = devices[cdev->portMapping[i].device]; // ...and grab the data auto inputPort = cdev->portMapping[i].port; complex input = buf[inputDevice]->getValue(map.second, inputPort, false); if(!std::isnan(ref.real()) && !std::isnan(input.real())) { // got both required measurements QString name = "S"+QString::number(i+1)+QString::number(map.first+1); auto S = input / ref; if(inputDevice != stimulusDev) { // can't use phase information when measuring across devices S = abs(S); } m.measurements[name] = S; } } } emit VNAmeasurementReceived(m); // Clear this and all (incomplete) older datapoint buffers int pointNum = data->pointNum; auto it = compoundVNABuffer.begin(); while(it != compoundVNABuffer.end()) { if(it->first <= pointNum) { for(auto d : it->second) { delete d.second; } it = compoundVNABuffer.erase(it); } else { it++; } } } } void VirtualDevice::singleSpectrumResultReceived(Device *dev, Protocol::SpectrumAnalyzerResult res) { Q_UNUSED(dev) SAMeasurement m; m.pointNum = res.pointNum; if(zerospan) { m.us = res.us; } else { m.frequency = res.frequency; } m.measurements["PORT1"] = res.port1; m.measurements["PORT2"] = res.port2; emit SAmeasurementReceived(m); } void VirtualDevice::compoundSpectrumResultReceived(Device *dev, Protocol::SpectrumAnalyzerResult res) { if(!compoundSABuffer.count(res.pointNum)) { compoundSABuffer[res.pointNum] = std::map(); } auto &buf = compoundSABuffer[res.pointNum]; buf[dev] = res; if(buf.size() == devices.size()) { // Got datapoints from all devices, can create merged VNA result SAMeasurement m; m.pointNum = res.pointNum; if(zerospan) { m.us = res.us; } else { m.frequency = res.frequency; } // assemble data for(unsigned int port=0;portportMapping.size();port++) { auto device = devices[cdev->portMapping[port].device]; auto devicePort = cdev->portMapping[port].port; QString name = "PORT"+QString::number(port+1); if(devicePort == 0) { m.measurements[name] = buf[device].port1; } else { m.measurements[name] = buf[device].port2; } } emit SAmeasurementReceived(m); // Clear this and all (incomplete) older datapoint buffers auto it = compoundSABuffer.begin(); while(it != compoundSABuffer.end()) { if(it->first <= res.pointNum) { it = compoundSABuffer.erase(it); } else { it++; } } } } void VirtualDevice::compoundInfoUpdated(Device *dev) { compoundInfoBuffer[dev] = dev->Info(); if(compoundInfoBuffer.size() == devices.size()) { // got information of all devices info = Info(devices[0]); for(unsigned int i=1;isync == CompoundDevice::Synchronization::ExtRef) { // can't use the external reference if it is used for synchronization info.supportsExtRef = false; } info.ports = cdev->portMapping.size(); emit InfoUpdated(); } } void VirtualDevice::compoundStatusUpdated(Device *dev) { compoundStatusBuffer[dev] = dev->StatusV1(); if(compoundStatusBuffer.size() == devices.size()) { // got status of all devices status = Status(devices[0]); for(unsigned int i=1;i cb) { if(results.size() == devices.size()) { // got all responses bool success = true; for(auto res : results) { if(res.second != Device::TransmissionResult::Ack) { success = false; break; } } if(cb) { cb(success); } } } Sparam VirtualDevice::VNAMeasurement::toSparam(int port1, int port2) const { Sparam S; S.m11 = measurements.at("S"+QString::number(port1)+QString::number(port1)); S.m12 = measurements.at("S"+QString::number(port1)+QString::number(port2)); S.m21 = measurements.at("S"+QString::number(port2)+QString::number(port1)); S.m22 = measurements.at("S"+QString::number(port2)+QString::number(port2)); return S; } void VirtualDevice::VNAMeasurement::fromSparam(Sparam S, int port1, int port2) { QString s11 = "S"+QString::number(port1)+QString::number(port1); QString s12 = "S"+QString::number(port1)+QString::number(port2); QString s21 = "S"+QString::number(port2)+QString::number(port1); QString s22 = "S"+QString::number(port2)+QString::number(port2); if(measurements.count(s11)) { measurements[s11] = S.m11; } if(measurements.count(s12)) { measurements[s12] = S.m12; } if(measurements.count(s21)) { measurements[s21] = S.m21; } if(measurements.count(s22)) { measurements[s22] = S.m22; } } VirtualDevice::VNAMeasurement VirtualDevice::VNAMeasurement::interpolateTo(const VirtualDevice::VNAMeasurement &to, double a) { VNAMeasurement ret; ret.frequency = frequency * (1.0 - a) + to.frequency * a; ret.dBm = dBm * (1.0 - a) + to.dBm * a; ret.Z0 = Z0 * (1.0 - a) + to.Z0 * a; for(auto m : measurements) { if(to.measurements.count(m.first) == 0) { throw runtime_error("Nothing to interpolate to, expected measurement +\""+m.first.toStdString()+"\""); } ret.measurements[m.first] = measurements[m.first] * (1.0 - a) + to.measurements.at(m.first) * a; } return ret; } VirtualDevice::Info::Info() { ProtocolVersion = Protocol::Version; FW_major = 0; FW_minor = 0; FW_patch = 0; hardware_version = 1; HW_Revision = '0'; ports = 2; supportsVNAmode = true; supportsSAmode = true; supportsSGmode = true; supportsExtRef = true; Limits = { .minFreq = 0, .maxFreq = 6000000000, .maxFreqHarmonic = 18000000000, .minIFBW = 10, .maxIFBW = 1000000, .maxPoints = 10000, .mindBm = -100, .maxdBm = 30, .minRBW = 1, .maxRBW = 1000000, }; } VirtualDevice::Info::Info(Device *dev) { auto info = dev->Info(); ProtocolVersion = info.ProtocolVersion; FW_major = info.FW_major; FW_minor = info.FW_minor; FW_patch = info.FW_patch; hardware_version = info.hardware_version; HW_Revision = info.HW_Revision; ports = 2; supportsVNAmode = true; supportsSAmode = true; supportsSGmode = true; supportsExtRef = true; Limits.minFreq = info.limits_minFreq; Limits.maxFreq = info.limits_maxFreq; Limits.maxFreqHarmonic = info.limits_maxFreqHarmonic; Limits.minIFBW = info.limits_minIFBW; Limits.maxIFBW = info.limits_maxIFBW; Limits.maxPoints = info.limits_maxPoints; Limits.mindBm = (double) info.limits_cdbm_min / 100; Limits.maxdBm = (double) info.limits_cdbm_max / 100; Limits.minRBW = info.limits_minRBW; Limits.maxRBW = info.limits_maxRBW; } void VirtualDevice::Info::subset(const VirtualDevice::Info &merge) { if((merge.ProtocolVersion != ProtocolVersion) || (merge.FW_major != FW_major) || (merge.FW_minor != FW_minor) || (merge.FW_patch != FW_patch)) { throw runtime_error("Incompatible device, unable to create compound device. All devices must run the same firmware version."); } ports += merge.ports; supportsVNAmode &= merge.supportsVNAmode; supportsSGmode &= merge.supportsSGmode; supportsSAmode &= merge.supportsSAmode; supportsExtRef &= merge.supportsExtRef; Limits.minFreq = max(Limits.minFreq, merge.Limits.minFreq); Limits.maxFreq = min(Limits.maxFreq, merge.Limits.maxFreq); Limits.maxFreqHarmonic = min(Limits.maxFreqHarmonic, merge.Limits.maxFreqHarmonic); Limits.minIFBW = max(Limits.minIFBW, merge.Limits.minIFBW); Limits.maxIFBW = min(Limits.maxIFBW, merge.Limits.maxIFBW); Limits.maxPoints = min(Limits.maxPoints, merge.Limits.maxPoints); Limits.mindBm = max(Limits.mindBm, merge.Limits.mindBm); Limits.maxdBm = min(Limits.maxdBm, merge.Limits.maxdBm); Limits.minRBW = max(Limits.minRBW, merge.Limits.minRBW); Limits.maxRBW = min(Limits.maxRBW, merge.Limits.maxRBW); } VirtualDevice::Status::Status() { statusString = ""; overload = false; unlocked = false; unlevel = false; extRef = false; } VirtualDevice::Status::Status(Device *dev) { auto status = dev->StatusV1(); statusString = dev->getLastDeviceInfoString(); overload = status.ADC_overload; unlevel = status.unlevel; unlocked = !status.LO1_locked || !status.source_locked; extRef = status.extRefInUse; } void VirtualDevice::Status::merge(const VirtualDevice::Status &merge) { statusString += " / "+merge.statusString; overload |= merge.overload; unlevel |= merge.unlevel; unlocked |= merge.unlocked; extRef &= merge.extRef; }