LCOV - code coverage report
Current view: top level - src/cli - dsocommand.cpp (source / functions) Coverage Total Hit
Project: Dokit Lines: 76.1 % 355 270
Version: Functions: 75.0 % 12 9

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2022-2026 Paul Colby <git@colby.id.au>
       2              : // SPDX-License-Identifier: LGPL-3.0-or-later
       3              : 
       4              : #include "dsocommand.h"
       5              : #include "../stringliterals_p.h"
       6              : 
       7              : #include <qtpokit/pokitdevice.h>
       8              : 
       9              : #include <QJsonDocument>
      10              : #include <QJsonObject>
      11              : 
      12              : #include <iostream>
      13              : 
      14              : DOKIT_USE_STRINGLITERALS
      15              : 
      16              : /*!
      17              :  * \class DsoCommand
      18              :  *
      19              :  * The DsoCommand class implements the `dso` CLI command.
      20              :  */
      21              : 
      22              : /*!
      23              :  * Construct a new DsoCommand object with \a parent.
      24              :  */
      25        10448 : DsoCommand::DsoCommand(QObject * const parent) : DeviceCommand(parent)
      26         3096 : {
      27              : 
      28        10516 : }
      29              : 
      30        11970 : QStringList DsoCommand::requiredOptions(const QCommandLineParser &parser) const
      31         4701 : {
      32        59934 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      33         4701 :         u"mode"_s,
      34         4701 :         u"range"_s,
      35        46767 :     };
      36         4701 : }
      37              : 
      38         5950 : QStringList DsoCommand::supportedOptions(const QCommandLineParser &parser) const
      39         2295 : {
      40        52870 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      41         2295 :         u"interval"_s,
      42         2295 :         u"samples"_s,
      43         2295 :         u"sample-rate"_s,
      44         2295 :         u"trigger-level"_s,
      45         2295 :         u"trigger-mode"_s,
      46         2295 :         u"window-size"_s,
      47        47005 :     };
      48         2295 : }
      49              : 
      50              : /*!
      51              :  * \copybrief DeviceCommand::processOptions
      52              :  *
      53              :  * This implementation extends DeviceCommand::processOptions to process additional CLI options
      54              :  * supported (or required) by this command.
      55              :  */
      56         5880 : QStringList DsoCommand::processOptions(const QCommandLineParser &parser)
      57         2184 : {
      58         8064 :     QStringList errors = DeviceCommand::processOptions(parser);
      59         8064 :     if (!errors.isEmpty()) {
      60          156 :         return errors;
      61          156 :     }
      62              : 
      63              :     // Parse the (required) mode option.
      64        13104 :     if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
      65        16536 :         mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
      66          192 :         settings.mode = DsoService::Mode::AcVoltage;
      67        16112 :     } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
      68         6720 :         settings.mode = DsoService::Mode::DcVoltage;
      69         2936 :     } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
      70          192 :         settings.mode = DsoService::Mode::AcCurrent;
      71          848 :     } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
      72          192 :         settings.mode = DsoService::Mode::DcCurrent;
      73           52 :     } else {
      74          368 :         errors.append(tr("Unknown DSO mode: %1").arg(parser.value(u"mode"_s)));
      75           52 :         return errors;
      76         1924 :     }
      77              : 
      78              :     // Parse the (required) range option.
      79         5472 :     QString unit;
      80         1976 :     {
      81         7296 :         const QString value = parser.value(u"range"_s);
      82         1976 :         quint32 sensibleMinimum = 0;
      83         7296 :         switch (settings.mode) {
      84            0 :         case DsoService::Mode::Idle:
      85            0 :             Q_ASSERT(false); // Not possible, since the mode parsing above never allows Idle.
      86            0 :             break;
      87         6860 :         case DsoService::Mode::DcVoltage:
      88         1872 :         case DsoService::Mode::AcVoltage:
      89         6912 :             minRangeFunc = minVoltageRange;
      90         6912 :             unit = u"V"_s;
      91         1872 :             sensibleMinimum = 50; // mV.
      92         6912 :             break;
      93          332 :         case DsoService::Mode::DcCurrent:
      94          104 :         case DsoService::Mode::AcCurrent:
      95          384 :             minRangeFunc = minCurrentRange;
      96          384 :             unit = u"A"_s;
      97          104 :             sensibleMinimum = 5; // mA.
      98          384 :             break;
      99         1976 :         }
     100         1976 :         Q_ASSERT(!unit.isEmpty());
     101         7296 :         rangeOptionValue = parseNumber<std::milli>(value, unit, sensibleMinimum);
     102         7296 :         if (rangeOptionValue == 0) {
     103          284 :             errors.append(tr("Invalid range value: %1").arg(value));
     104           52 :         }
     105         3800 :     }
     106              : 
     107              :     // Parse the trigger-level option.
     108        10792 :     if (parser.isSet(u"trigger-level"_s)) {
     109          416 :         float sign = 1.0;
     110         2416 :         const QString rawValue = parser.value(u"trigger-level"_s);
     111          416 :         QString absValue = rawValue;
     112          416 :         DOKIT_STRING_INDEX_TYPE nonSpacePos;
     113         1536 :         for (nonSpacePos = 0; (nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u' '); ++nonSpacePos);
     114         1536 :         if ((nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u'-')) {
     115            0 :             absValue = rawValue.mid(nonSpacePos+1);
     116            0 :             sign = -1.0;
     117            0 :         }
     118         1536 :         const float level = parseNumber<std::ratio<1>,float>(absValue, unit, 0.f);
     119         2080 :         qCDebug(lc) << "Trigger level" << rawValue << absValue << nonSpacePos << sign << level;
     120         1536 :         if (qIsNaN(level)) {
     121          284 :             errors.append(tr("Invalid trigger-level value: %1").arg(rawValue));
     122          364 :         } else {
     123         1344 :             settings.triggerLevel = sign * level;
     124         1820 :             qCDebug(lc) << "Trigger level" << settings.triggerLevel;
     125              :             // Check the trigger level is within the Votage / Current range.
     126         2324 :             if ((rangeOptionValue != 0) && (qAbs(settings.triggerLevel) > (rangeOptionValue/1000.0))) {
     127            0 :                 errors.append(tr("Trigger-level %1%2 is outside range ±%3%2").arg(
     128            0 :                     appendSiPrefix(settings.triggerLevel), unit, appendSiPrefix(rangeOptionValue / 1000.0)));
     129            0 :             }
     130          364 :         }
     131          800 :     }
     132              : 
     133              :     // Parse the trigger-mode option.
     134        10792 :     if (parser.isSet(u"trigger-mode"_s)) {
     135         2656 :         const QString triggerMode = parser.value(u"trigger-mode"_s).trimmed().toLower();
     136         2272 :         if (triggerMode.startsWith(u"free"_s)) {
     137          576 :             settings.command = DsoService::Command::FreeRunning;
     138         1420 :         } else if (triggerMode.startsWith(u"ris"_s)) {
     139          384 :            settings.command = DsoService::Command::RisingEdgeTrigger;
     140          852 :         } else if (triggerMode.startsWith(u"fall"_s)) {
     141          384 :             settings.command = DsoService::Command::FallingEdgeTrigger;
     142          104 :         } else {
     143          368 :             errors.append(tr("Unknown trigger mode: %1").arg(parser.value(u"trigger-mode"_s)));
     144           52 :         }
     145          800 :     }
     146              : 
     147              :     // Ensure that if either trigger option is present, then both are.
     148        16112 :     if (parser.isSet(u"trigger-level"_s) != parser.isSet(u"trigger-mode"_s)) {
     149          384 :         errors.append(tr("If either option is provided, then both must be: trigger-level, trigger-mode"));
     150          104 :     }
     151              : 
     152              :     // Parse the sample-rate option.
     153        10792 :     if (parser.isSet(u"sample-rate"_s)) {
     154        10872 :         const QString value = parser.value(u"sample-rate"_s);
     155         6912 :         sampleRateValue = parseNumber<std::ratio<1,1>>(value, u"Hz"_s, (quint32)50'000);
     156         6912 :         if (sampleRateValue == 0) {
     157          568 :             errors.append(tr("Invalid sample-rate value: %1").arg(value));
     158         6528 :         } else if (sampleRateValue > 1'000'000) {
     159          492 :             qCWarning(lc).noquote() << tr("Pokit devices do not officially support sample rates greater than 1Mhz");
     160           52 :         }
     161         3600 :     }
     162              : 
     163              :     // Parse the interval option.
     164        10792 :     if (parser.isSet(u"interval"_s)) {
     165         4530 :         const QString value = parser.value(u"interval"_s);
     166         2880 :         const quint32 interval = parseNumber<std::micro>(value, u"s"_s, (quint32)500'000);
     167         2880 :         if (interval == 0) {
     168          568 :             errors.append(tr("Invalid interval value: %1").arg(value));
     169          676 :         } else {
     170         2496 :             settings.samplingWindow = interval;
     171          676 :         }
     172         1500 :     }
     173              : 
     174              :     // Parse the window-size option.
     175        10792 :     if (parser.isSet(u"window-size"_s)) {
     176         2416 :         const QString value = parser.value(u"window-size"_s);
     177         1536 :         const quint32 samples = parseNumber<std::ratio<1>>(value, u"S"_s);
     178         1536 :         if (samples == 0) {
     179          568 :             errors.append(tr("Invalid window-size value: %1").arg(value));
     180         1152 :         } else if (samples > std::numeric_limits<quint16>::max()) {
     181            0 :             errors.append(tr("Window size value (%1) must be no greater than %2")
     182            0 :                 .arg(value).arg(std::numeric_limits<quint16>::max()));
     183          312 :         } else {
     184         1152 :             settings.numberOfSamples = (quint16)samples;
     185         1152 :             if (const auto maxSamples = maxWindowSize(PokitProduct::PokitPro); settings.numberOfSamples > maxSamples) {
     186          400 :                 qCWarning(lc).noquote() <<
     187          284 :                     tr("No Pokit device officially supports windows greater than %L1 samples").arg(maxSamples);
     188           52 :             }
     189          312 :         }
     190          800 :     }
     191              : 
     192              :     // Ensure that we have at least: sample-rate, or both interval and window-size.
     193         7992 :     if ((!parser.isSet(u"sample-rate"_s)) && !(parser.isSet(u"interval"_s) && parser.isSet(u"window-size"_s))) {
     194          192 :         errors.append(tr("Missing required option/s: either sample-rate, or both interval and window-size"));
     195           52 :     }
     196              : 
     197              :     // If we have all three sample-rate related options, ensure they agree.
     198         7296 :     if ((sampleRateValue != 0) && (settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
     199          384 :         const quint32 sampleRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
     200          384 :         if (sampleRate != sampleRateValue) {
     201          240 :             errors.append(tr("Windows size (%1 samples) and interval (%2ns) yield a sample rate of %3Hz, which does "
     202           52 :                 "not match the supplied sample-rate (%4Hz). Tip: leave one option unset to have dokit calculate the "
     203          266 :                 "remaining option.").arg(settings.numberOfSamples).arg(settings.samplingWindow).arg(sampleRate)
     204          314 :                 .arg(sampleRateValue));
     205           52 :         }
     206          104 :     }
     207              : 
     208              :     // Parse the samples option.
     209        10792 :     if (parser.isSet(u"samples"_s)) {
     210          906 :         const QString value = parser.value(u"samples"_s);
     211          576 :         samplesValue = parseNumber<std::ratio<1>>(value, u"S"_s);
     212          576 :         if (samplesValue == 0) {
     213          568 :             errors.append(tr("Invalid samples value: %1").arg(value));
     214          104 :         }
     215          300 :     }
     216         1976 :     return errors;
     217         3800 : }
     218              : 
     219              : /*!
     220              :  * \copybrief DeviceCommand::getService
     221              :  *
     222              :  * This override returns a pointer to a DsoService object.
     223              :  */
     224            0 : AbstractPokitService * DsoCommand::getService()
     225            0 : {
     226            0 :     Q_ASSERT(device);
     227            0 :     if (!service) {
     228            0 :         service = device->dso();
     229            0 :         Q_ASSERT(service);
     230            0 :         connect(service, &DsoService::metadataRead,    this, &DsoCommand::metadataRead);
     231            0 :         connect(service, &DsoService::samplesRead,     this, &DsoCommand::outputSamples);
     232            0 :         connect(service, &DsoService::settingsWritten, this, &DsoCommand::settingsWritten);
     233            0 :     }
     234            0 :     return service;
     235            0 : }
     236              : 
     237              : /*!
     238              :  * Returns the \a product's maximum sampling window size.
     239              :  *
     240              :  * \pokitApi Pokit's official documentation claim the maximum is 8,192. However, my Pokit Meter fails for window size
     241              :  * greater than 8,191, while my Pokit Pro supports up to 16,384 samples per window.
     242              :  */
     243         1420 : quint16 DsoCommand::maxWindowSize(const PokitProduct product)
     244         2754 : {
     245         4174 :     switch (product) {
     246         1221 :     case PokitProduct::PokitMeter:
     247         1221 :         return 8'191;
     248         2783 :     case PokitProduct::PokitPro:
     249         2783 :         return 16'384;
     250         2754 :     }
     251         1122 :     Q_ASSERT_X(false, "DsoCommand::maxWindowSize", "Unknown PokitProduct enum value");
     252            0 :     return 0;
     253         2754 : }
     254              : 
     255              : /*!
     256              :  * Configures the \a settings.numberOfSamples and/or \a settings.samplingWindow, if not already set, according to the
     257              :  * requested \a sampleRate. The chosen \a settings will be limited to \a product's capaibilities.
     258              :  *
     259              :  * Returns \c true os settings were set (either by this function, or they were already set), or \c false if the
     260              :  * settings could not be determined succesfully (eg, because \a sampleRate was too high for the \a product).
     261              :  */
     262         1400 : bool DsoCommand::configureWindow(const PokitProduct product, const quint32 sampleRate, DsoService::Settings &settings)
     263         2220 : {
     264         2220 :     const quint32 maxSampleRate = 1'000'000; // Pokit Meter and Pokit Pro both sample up to 1MHz.
     265         3620 :     const quint32 maxWindowSize = DsoCommand::maxWindowSize(product);
     266              : 
     267         3620 :     if (sampleRate > maxSampleRate) {
     268            0 :         qCWarning(lc).noquote() <<
     269            0 :             tr("The requested sample rate (%1Hz) likely exceeds the connected device's limit (%2Hz)")
     270            0 :             .arg(sampleRate).arg(maxSampleRate);
     271            0 :     }
     272              : 
     273         3620 :     if (settings.numberOfSamples > maxWindowSize) {
     274            0 :         qCWarning(lc).noquote() <<
     275            0 :             tr("Requested window size (%1 samples) likely exceeds the connected device's limit (%2) samples")
     276            0 :             .arg(settings.samplingWindow).arg(maxWindowSize);
     277            0 :     }
     278              : 
     279         3620 :     if ((settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
     280          860 :         qCDebug(lc).noquote() << "Both numberOfSamples and samplingWindow are set, so no need to derive either";
     281          724 :         if (sampleRate != 0) {
     282          724 :             const quint32 derivedRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
     283          860 :             qCDebug(lc).noquote() << "derivedRate" << derivedRate << sampleRate;
     284          724 :             if (sampleRate != derivedRate) {
     285          662 :                 qCWarning(lc).noquote() << tr("Ignoring sample-rate, as interval and window-size both provided");
     286          222 :             }
     287          444 :         }
     288          724 :         return true; // Nothing more to do.
     289          444 :     }
     290         1980 :     Q_ASSERT_X(sampleRate > 0, "DsoCommand::configureWindow", "processOptions should have rejected already");
     291              : 
     292              :     // If both window parameters are unset, choose the best window size (we'll choose a window period later).
     293         2896 :     if ((settings.numberOfSamples == 0) && (settings.samplingWindow == 0)) {
     294         1290 :         qCDebug(lc).noquote() << tr("Choosing best number-of-samples for sample-rate %2Hz").arg(sampleRate);
     295          666 :         double smallestDifference = std::numeric_limits<double>::quiet_NaN();
     296     13345311 :         for (quint32 windowSize = maxWindowSize; windowSize > 0; --windowSize) {
     297     13344225 :             const quint32 period = windowSize * 1'000'000ull / sampleRate;
     298     13344225 :             const double effectiveRate = double(windowSize) * 1'000'000.0 / (double)period;
     299     13344225 :             if (effectiveRate > maxSampleRate) continue; // Skip sizes that would exceed the device's max sample rate.
     300     13344225 :             if (const quint32 effectivePeriod = windowSize * 1'000'000ull / sampleRate;
     301      9903305 :                 effectivePeriod > 1'000'000) continue; // Skip sizes that would take longer than 1s to fetch.
     302      8897236 :             const double difference = qAbs(effectiveRate - sampleRate);
     303              :             // qCDebug(lc).noquote() << tr("%1 samples, %2us, %3Hz, %4Hz, ±%5Hz").arg(windowSize).arg(period)
     304              :             //     .arg(effectiveRate, 0, 'f').arg(sampleRate).arg(difference, 0, 'f');
     305      8897236 :             if ((settings.numberOfSamples == 0) || (difference < smallestDifference)) {
     306         3258 :                 settings.numberOfSamples = windowSize;
     307         1998 :                 smallestDifference = difference;
     308         1998 :             }
     309      5456316 :         }
     310         1290 :         qCDebug(lc).noquote() << tr("Chose %Ln sample/s, with error ±%2Hz", nullptr,
     311            0 :             settings.numberOfSamples).arg(smallestDifference, 0, 'f');
     312         1086 :         if (settings.numberOfSamples == 0) {
     313            0 :             qCCritical(lc).noquote() << tr("Failed to select a compatible window size for sample rate %1Hz").arg(sampleRate);
     314            0 :             return false;
     315            0 :         }
     316          666 :     }
     317              : 
     318         2896 :     if (settings.numberOfSamples == 0) {
     319         1720 :         qCDebug(lc).noquote() << tr("Calculating number-of-samples for %1us window at %2Hz")
     320            0 :             .arg(settings.samplingWindow).arg(sampleRate);
     321          888 :         Q_ASSERT(settings.samplingWindow != 0);
     322         1448 :         const auto numberOfSamples = sampleRate * settings.samplingWindow / 1'000'000ull;
     323         1720 :         qCDebug(lc).noquote() << tr("Calculated %Ln sample/s", nullptr, numberOfSamples);
     324         1448 :         if ((numberOfSamples == 0) || (numberOfSamples > maxWindowSize)) {
     325          600 :             qCCritical(lc).noquote() << tr("Failed to calculate a valid number of samples for a %L1us period at %2Hz")
     326          484 :                 .arg(settings.samplingWindow).arg(sampleRate);
     327          294 :             return false;
     328          222 :         }
     329         1086 :         settings.numberOfSamples = numberOfSamples; // Note the implicit uint64 to uint16 conversion.
     330          666 :         Q_ASSERT(settings.numberOfSamples * 1'000'000ull / settings.samplingWindow <= sampleRate); // Due to integer truncation.
     331          666 :     }
     332              : 
     333         2534 :     if (settings.samplingWindow == 0) {
     334         1720 :         qCDebug(lc).noquote() << tr("Calculating sampling-window for %Ln sample/s at %1Hz", nullptr,
     335            0 :             settings.numberOfSamples).arg(sampleRate);
     336          888 :         Q_ASSERT(settings.numberOfSamples != 0);
     337         1448 :         settings.samplingWindow = settings.numberOfSamples * 1'000'000ull / sampleRate;
     338         1720 :         qCDebug(lc).noquote() << tr("Calculated %1us").arg(settings.samplingWindow);
     339         1448 :         if (settings.samplingWindow == 0) {
     340            0 :             qCCritical(lc).noquote() << tr("Failed to calculate a valid sampling window for a %L1 samples at %1Hz")
     341            0 :             .arg(settings.numberOfSamples).arg(sampleRate);
     342            0 :             return false;
     343            0 :         }
     344          888 :     }
     345         1554 :     return true;
     346         1554 : }
     347              : 
     348              : /*!
     349              :  * \copybrief DeviceCommand::serviceDetailsDiscovered
     350              :  *
     351              :  * This override fetches the current device's status, and outputs it in the selected format.
     352              :  */
     353            0 : void DsoCommand::serviceDetailsDiscovered()
     354            0 : {
     355            0 :     DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
     356            0 :     settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
     357            0 :     if (!configureWindow(*service->pokitProduct(), sampleRateValue, settings)) {
     358            0 :         disconnect(EXIT_FAILURE);
     359            0 :         return;
     360            0 :     }
     361            0 :     if (samplesValue == 0) samplesValue = settings.numberOfSamples;
     362            0 :     const QString range = service->toString(settings.range, settings.mode);
     363            0 :     const QString triggerInfo = (settings.command == DsoService::Command::FreeRunning) ? QString() :
     364            0 :         tr(", and a %1 at %2%3%4 (%5Hz)").arg(DsoService::toString(settings.command).toLower(),
     365            0 :             (settings.triggerLevel < 0.) ? u"-"_s : u""_s, appendSiPrefix(qAbs(settings.triggerLevel)),
     366            0 :             range.at(range.size()-1));
     367            0 :     qCInfo(lc).noquote() << tr("Sampling %1, with range %2, at %L3Hz (%Ln sample/s over %L4us)%5", nullptr, settings.numberOfSamples)
     368            0 :         .arg(DsoService::toString(settings.mode), (range.isNull()) ? QString::fromLatin1("N/A") : range)
     369            0 :         .arg(settings.numberOfSamples * 1'000'000ull / settings.samplingWindow).arg(settings.samplingWindow)
     370            0 :         .arg(triggerInfo);
     371            0 :     if (!service->enableMetadataNotifications()) {
     372            0 :         qCCritical(lc).noquote() << tr("Failed to enable metadata notifications");
     373            0 :         disconnect(EXIT_FAILURE);
     374            0 :         return;
     375            0 :     }
     376            0 :     if (!service->enableReadingNotifications()) {
     377            0 :         qCCritical(lc).noquote() << tr("Failed to enable reading notifications");
     378            0 :         disconnect(EXIT_FAILURE);
     379            0 :         return;
     380            0 :     }
     381            0 :     service->setSettings(settings);
     382            0 : }
     383              : 
     384              : /*!
     385              :  * \var DsoCommand::minRangeFunc
     386              :  *
     387              :  * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
     388              :  * is assigned during the command line parsing, but is not invoked until after the device's services are discovered,
     389              :  * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
     390              :  * which enumerator list to be using.
     391              :  *
     392              :  * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
     393              :  *
     394              :  * \see processOptions
     395              :  * \see serviceDetailsDiscovered
     396              :  */
     397              : 
     398              : /*!
     399              :  * Invoked when the DSO settings have been written.
     400              :  */
     401            0 : void DsoCommand::settingsWritten()
     402            0 : {
     403            0 :     Q_ASSERT(service);
     404            0 :     qCDebug(lc).noquote() << tr("Settings written; DSO has started.");
     405            0 : }
     406              : 
     407              : /*!
     408              :  * Invoked when \a metadata has been received from the DSO.
     409              :  */
     410         4270 : void DsoCommand::metadataRead(const DsoService::Metadata &data)
     411         1671 : {
     412         8015 :     qCDebug(lc) << "status:" << (int)(data.status);
     413         8015 :     qCDebug(lc) << "scale:" << data.scale;
     414         8015 :     qCDebug(lc) << "mode:" << DsoService::toString(data.mode);
     415         8015 :     qCDebug(lc) << "range:" << service->toString(data.range, data.mode);
     416         8015 :     qCDebug(lc) << "samplingWindow:" << data.samplingWindow;
     417         8015 :     qCDebug(lc) << "numberOfSamples:" << data.numberOfSamples;
     418         8015 :     qCDebug(lc) << "samplingRate:" << data.samplingRate << "Hz";
     419         5941 :     this->metadata = data;
     420         5941 :     this->samplesToGo = data.numberOfSamples;
     421         5941 : }
     422              : 
     423              : /*!
     424              :  * Outputs DSO \a samples in the selected output format.
     425              :  */
     426         5040 : void DsoCommand::outputSamples(const DsoService::Samples &samples)
     427         1872 : {
     428         5184 :     QString unit;
     429         6912 :     switch (metadata.mode) {
     430         2556 :     case DsoService::Mode::DcVoltage: unit = u"Vdc"_s; break;
     431         2556 :     case DsoService::Mode::AcVoltage: unit = u"Vac"_s; break;
     432         2556 :     case DsoService::Mode::DcCurrent: unit = u"Adc"_s; break;
     433         2556 :     case DsoService::Mode::AcCurrent: unit = u"Aac"_s; break;
     434            0 :     default:
     435            0 :         qCDebug(lc).noquote() << tr(R"(No known unit for mode %1 "%2".)").arg((int)metadata.mode)
     436            0 :             .arg(DsoService::toString(metadata.mode));
     437         1872 :     }
     438         8640 :     const QString range = service->toString(metadata.range, metadata.mode);
     439              : 
     440        37296 :     for (const qint16 &sample: samples) {
     441        32256 :         static quint32 sampleNumber = 0; ++sampleNumber;
     442        32256 :         const float value = sample * metadata.scale;
     443        32256 :         switch (format) {
     444         2912 :         case OutputFormat::Csv:
     445        12288 :             for (; showCsvHeader; showCsvHeader = false) {
     446         2272 :                 std::cout << qUtf8Printable(tr("sample_number,value,unit,range\n"));
     447          416 :             }
     448        22400 :             std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
     449         2912 :                 .arg(sampleNumber).arg(value).arg(unit, range));
     450        10752 :             break;
     451        10752 :         case OutputFormat::Json:
     452        56224 :             std::cout << QJsonDocument(QJsonObject{
     453        12432 :                     { u"value"_s,  value },
     454        12432 :                     { u"unit"_s,   unit },
     455        12432 :                     { u"range"_s,  range },
     456        18592 :                     { u"mode"_s,   DsoService::toString(metadata.mode) },
     457        47600 :                 }).toJson().toStdString();
     458        10752 :             break;
     459        10752 :         case OutputFormat::Text:
     460        23296 :             std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
     461        10752 :             break;
     462         8736 :         }
     463        32256 :         --samplesToGo;
     464              : 
     465        32256 :         if ((sampleNumber > samplesValue) && (samplesValue != 0)) {
     466            0 :             qCInfo(lc).noquote() << tr("Finished fetching %Ln sample/s. Disconnecting.", nullptr, sampleNumber);
     467            0 :             if (device) disconnect(); // Will exit the application once disconnected.
     468            0 :             return;
     469            0 :         }
     470         8736 :     }
     471              : 
     472              :     // If we've received all the data for the current window, begin fetching another.
     473         6912 :     if (samplesToGo <= 0) {
     474         9360 :         qCDebug(lc).noquote() << tr("Finished fetching %Ln window sample/s (with %L2 to remaining).",
     475            0 :             nullptr, metadata.numberOfSamples).arg(samplesToGo);
     476         6912 :         if (settings.range != +PokitMeter::VoltageRange::AutoRange) service->setSettings(settings);
     477         1872 :     }
     478        28016 : }
        

Generated by: LCOV version 2.4-0