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        17142 : DsoCommand::DsoCommand(QObject * const parent) : DeviceCommand(parent)
      26         6224 : {
      27              : 
      28        17142 : }
      29              : 
      30        17613 : QStringList DsoCommand::requiredOptions(const QCommandLineParser &parser) const
      31         9792 : {
      32        76482 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      33         9792 :         u"mode"_s,
      34         9792 :         u"range"_s,
      35        74088 :     };
      36         9792 : }
      37              : 
      38         8755 : QStringList DsoCommand::supportedOptions(const QCommandLineParser &parser) const
      39         4832 : {
      40        62802 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      41         4832 :         u"interval"_s,
      42         4832 :         u"samples"_s,
      43         4832 :         u"sample-rate"_s,
      44         4832 :         u"trigger-level"_s,
      45         4832 :         u"trigger-mode"_s,
      46         4832 :         u"window-size"_s,
      47        71812 :     };
      48         4832 : }
      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         8652 : QStringList DsoCommand::processOptions(const QCommandLineParser &parser)
      57         4704 : {
      58        13356 :     QStringList errors = DeviceCommand::processOptions(parser);
      59        13356 :     if (!errors.isEmpty()) {
      60          336 :         return errors;
      61          336 :     }
      62              : 
      63              :     // Parse the (required) mode option.
      64        20748 :     if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
      65        25896 :         mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
      66          318 :         settings.mode = DsoService::Mode::AcVoltage;
      67        25232 :     } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
      68        11130 :         settings.mode = DsoService::Mode::DcVoltage;
      69         5576 :     } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
      70          318 :         settings.mode = DsoService::Mode::AcCurrent;
      71         1328 :     } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
      72          318 :         settings.mode = DsoService::Mode::DcCurrent;
      73          112 :     } else {
      74          578 :         errors.append(tr("Unknown DSO mode: %1").arg(parser.value(u"mode"_s)));
      75          112 :         return errors;
      76         2686 :     }
      77              : 
      78              :     // Parse the (required) range option.
      79         9576 :     QString unit;
      80         4256 :     {
      81        12084 :         const QString value = parser.value(u"range"_s);
      82         4256 :         quint32 sensibleMinimum = 0;
      83        12084 :         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        11336 :         case DsoService::Mode::DcVoltage:
      88         4032 :         case DsoService::Mode::AcVoltage:
      89        11448 :             minRangeFunc = minVoltageRange;
      90        11448 :             unit = u"V"_s;
      91         4032 :             sensibleMinimum = 50; // mV.
      92        11448 :             break;
      93          524 :         case DsoService::Mode::DcCurrent:
      94          224 :         case DsoService::Mode::AcCurrent:
      95          636 :             minRangeFunc = minCurrentRange;
      96          636 :             unit = u"A"_s;
      97          224 :             sensibleMinimum = 5; // mA.
      98          636 :             break;
      99         4256 :         }
     100         4256 :         Q_ASSERT(!unit.isEmpty());
     101        12084 :         rangeOptionValue = parseNumber<std::milli>(value, unit, sensibleMinimum);
     102        12084 :         if (rangeOptionValue == 0) {
     103          458 :             errors.append(tr("Invalid range value: %1").arg(value));
     104          112 :         }
     105         6764 :     }
     106              : 
     107              :     // Parse the trigger-level option.
     108        17404 :     if (parser.isSet(u"trigger-level"_s)) {
     109          896 :         float sign = 1.0;
     110         3808 :         const QString rawValue = parser.value(u"trigger-level"_s);
     111          896 :         QString absValue = rawValue;
     112          896 :         DOKIT_STRING_INDEX_TYPE nonSpacePos;
     113         2544 :         for (nonSpacePos = 0; (nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u' '); ++nonSpacePos);
     114         2544 :         if ((nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u'-')) {
     115            0 :             absValue = rawValue.mid(nonSpacePos+1);
     116            0 :             sign = -1.0;
     117            0 :         }
     118         2544 :         const float level = parseNumber<std::ratio<1>,float>(absValue, unit, 0.f);
     119         3424 :         qCDebug(lc) << "Trigger level" << rawValue << absValue << nonSpacePos << sign << level;
     120         2544 :         if (qIsNaN(level)) {
     121          458 :             errors.append(tr("Invalid trigger-level value: %1").arg(rawValue));
     122          784 :         } else {
     123         2226 :             settings.triggerLevel = sign * level;
     124         2996 :             qCDebug(lc) << "Trigger level" << settings.triggerLevel;
     125              :             // Check the trigger level is within the Votage / Current range.
     126         3668 :             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          784 :         }
     131         1424 :     }
     132              : 
     133              :     // Parse the trigger-mode option.
     134        17404 :     if (parser.isSet(u"trigger-mode"_s)) {
     135         4192 :         const QString triggerMode = parser.value(u"trigger-mode"_s).trimmed().toLower();
     136         3664 :         if (triggerMode.startsWith(u"free"_s)) {
     137          954 :             settings.command = DsoService::Command::FreeRunning;
     138         2290 :         } else if (triggerMode.startsWith(u"ris"_s)) {
     139          636 :            settings.command = DsoService::Command::RisingEdgeTrigger;
     140         1374 :         } else if (triggerMode.startsWith(u"fall"_s)) {
     141          636 :             settings.command = DsoService::Command::FallingEdgeTrigger;
     142          224 :         } else {
     143          578 :             errors.append(tr("Unknown trigger mode: %1").arg(parser.value(u"trigger-mode"_s)));
     144          112 :         }
     145         1424 :     }
     146              : 
     147              :     // Ensure that if either trigger option is present, then both are.
     148        25232 :     if (parser.isSet(u"trigger-level"_s) != parser.isSet(u"trigger-mode"_s)) {
     149          636 :         errors.append(tr("If either option is provided, then both must be: trigger-level, trigger-mode"));
     150          224 :     }
     151              : 
     152              :     // Parse the sample-rate option.
     153        17404 :     if (parser.isSet(u"sample-rate"_s)) {
     154        17136 :         const QString value = parser.value(u"sample-rate"_s);
     155        11448 :         sampleRateValue = parseNumber<std::ratio<1,1>>(value, u"Hz"_s, (quint32)50'000);
     156        11448 :         if (sampleRateValue == 0) {
     157          916 :             errors.append(tr("Invalid sample-rate value: %1").arg(value));
     158        10812 :         } else if (sampleRateValue > 1'000'000) {
     159          774 :             qCWarning(lc).noquote() << tr("Pokit devices do not officially support sample rates greater than 1Mhz");
     160          112 :         }
     161         6408 :     }
     162              : 
     163              :     // Parse the interval option.
     164        17404 :     if (parser.isSet(u"interval"_s)) {
     165         7140 :         const QString value = parser.value(u"interval"_s);
     166         4770 :         const quint32 interval = parseNumber<std::micro>(value, u"s"_s, (quint32)500'000);
     167         4770 :         if (interval == 0) {
     168          916 :             errors.append(tr("Invalid interval value: %1").arg(value));
     169         1456 :         } else {
     170         4134 :             settings.samplingWindow = interval;
     171         1456 :         }
     172         2670 :     }
     173              : 
     174              :     // Parse the window-size option.
     175        17404 :     if (parser.isSet(u"window-size"_s)) {
     176         3808 :         const QString value = parser.value(u"window-size"_s);
     177         2544 :         const quint32 samples = parseNumber<std::ratio<1>>(value, u"S"_s);
     178         2544 :         if (samples == 0) {
     179          916 :             errors.append(tr("Invalid window-size value: %1").arg(value));
     180         1908 :         } 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          672 :         } else {
     184         1908 :             settings.numberOfSamples = (quint16)samples;
     185         1908 :             if (const auto maxSamples = maxWindowSize(PokitProduct::PokitPro); settings.numberOfSamples > maxSamples) {
     186          634 :                 qCWarning(lc).noquote() <<
     187          458 :                     tr("No Pokit device officially supports windows greater than %L1 samples").arg(maxSamples);
     188          112 :             }
     189          672 :         }
     190         1424 :     }
     191              : 
     192              :     // Ensure that we have at least: sample-rate, or both interval and window-size.
     193        13122 :     if ((!parser.isSet(u"sample-rate"_s)) && !(parser.isSet(u"interval"_s) && parser.isSet(u"window-size"_s))) {
     194          318 :         errors.append(tr("Missing required option/s: either sample-rate, or both interval and window-size"));
     195          112 :     }
     196              : 
     197              :     // If we have all three sample-rate related options, ensure they agree.
     198        12084 :     if ((sampleRateValue != 0) && (settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
     199          636 :         const quint32 sampleRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
     200          636 :         if (sampleRate != sampleRateValue) {
     201          384 :             errors.append(tr("Windows size (%1 samples) and interval (%2ns) yield a sample rate of %3Hz, which does "
     202          112 :                 "not match the supplied sample-rate (%4Hz). Tip: leave one option unset to have dokit calculate the "
     203          412 :                 "remaining option.").arg(settings.numberOfSamples).arg(settings.samplingWindow).arg(sampleRate)
     204          506 :                 .arg(sampleRateValue));
     205          112 :         }
     206          224 :     }
     207              : 
     208              :     // Parse the samples option.
     209        17404 :     if (parser.isSet(u"samples"_s)) {
     210         1428 :         const QString value = parser.value(u"samples"_s);
     211          954 :         samplesValue = parseNumber<std::ratio<1>>(value, u"S"_s);
     212          954 :         if (samplesValue == 0) {
     213          916 :             errors.append(tr("Invalid samples value: %1").arg(value));
     214          224 :         }
     215          534 :     }
     216         4256 :     return errors;
     217         6764 : }
     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         2190 : quint16 DsoCommand::maxWindowSize(const PokitProduct product)
     244         3488 : {
     245         5678 :     switch (product) {
     246         1408 :     case PokitProduct::PokitMeter:
     247         1408 :         return 8'191;
     248         3957 :     case PokitProduct::PokitPro:
     249         3957 :         return 16'384;
     250         3488 :     }
     251         2140 :     Q_ASSERT_X(false, "DsoCommand::maxWindowSize", "Unknown PokitProduct enum value");
     252            0 :     return 0;
     253         3488 : }
     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         2060 : bool DsoCommand::configureWindow(const PokitProduct product, const quint32 sampleRate, DsoService::Settings &settings)
     263         2560 : {
     264         2560 :     const quint32 maxSampleRate = 1'000'000; // Pokit Meter and Pokit Pro both sample up to 1MHz.
     265         4620 :     const quint32 maxWindowSize = DsoCommand::maxWindowSize(product);
     266              : 
     267         4620 :     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         4620 :     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         4620 :     if ((settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
     280         1144 :         qCDebug(lc).noquote() << "Both numberOfSamples and samplingWindow are set, so no need to derive either";
     281          924 :         if (sampleRate != 0) {
     282          924 :             const quint32 derivedRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
     283         1144 :             qCDebug(lc).noquote() << "derivedRate" << derivedRate << sampleRate;
     284          924 :             if (sampleRate != derivedRate) {
     285          918 :                 qCWarning(lc).noquote() << tr("Ignoring sample-rate, as interval and window-size both provided");
     286          256 :             }
     287          512 :         }
     288          924 :         return true; // Nothing more to do.
     289          512 :     }
     290         2376 :     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         3696 :     if ((settings.numberOfSamples == 0) && (settings.samplingWindow == 0)) {
     294         1716 :         qCDebug(lc).noquote() << tr("Choosing best number-of-samples for sample-rate %2Hz").arg(sampleRate);
     295          768 :         double smallestDifference = std::numeric_limits<double>::quiet_NaN();
     296     17031861 :         for (quint32 windowSize = maxWindowSize; windowSize > 0; --windowSize) {
     297     17030475 :             const quint32 period = windowSize * 1'000'000ull / sampleRate;
     298     17030475 :             const double effectiveRate = double(windowSize) * 1'000'000.0 / (double)period;
     299     17030475 :             if (effectiveRate > maxSampleRate) continue; // Skip sizes that would exceed the device's max sample rate.
     300     17030475 :             if (const quint32 effectivePeriod = windowSize * 1'000'000ull / sampleRate;
     301     11967407 :                 effectivePeriod > 1'000'000) continue; // Skip sizes that would take longer than 1s to fetch.
     302     11355036 :             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     11355036 :             if ((settings.numberOfSamples == 0) || (difference < smallestDifference)) {
     306         4158 :                 settings.numberOfSamples = windowSize;
     307         2304 :                 smallestDifference = difference;
     308         2304 :             }
     309      6291968 :         }
     310         1716 :         qCDebug(lc).noquote() << tr("Chose %Ln sample/s, with error ±%2Hz", nullptr,
     311            0 :             settings.numberOfSamples).arg(smallestDifference, 0, 'f');
     312         1386 :         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          768 :     }
     317              : 
     318         3696 :     if (settings.numberOfSamples == 0) {
     319         2288 :         qCDebug(lc).noquote() << tr("Calculating number-of-samples for %1us window at %2Hz")
     320            0 :             .arg(settings.samplingWindow).arg(sampleRate);
     321         1024 :         Q_ASSERT(settings.samplingWindow != 0);
     322         1848 :         const auto numberOfSamples = sampleRate * settings.samplingWindow / 1'000'000ull;
     323         2288 :         qCDebug(lc).noquote() << tr("Calculated %Ln sample/s", nullptr, numberOfSamples);
     324         1848 :         if ((numberOfSamples == 0) || (numberOfSamples > maxWindowSize)) {
     325          826 :             qCCritical(lc).noquote() << tr("Failed to calculate a valid number of samples for a %L1us period at %2Hz")
     326          650 :                 .arg(settings.samplingWindow).arg(sampleRate);
     327          352 :             return false;
     328          256 :         }
     329         1386 :         settings.numberOfSamples = numberOfSamples; // Note the implicit uint64 to uint16 conversion.
     330          768 :         Q_ASSERT(settings.numberOfSamples * 1'000'000ull / settings.samplingWindow <= sampleRate); // Due to integer truncation.
     331          768 :     }
     332              : 
     333         3234 :     if (settings.samplingWindow == 0) {
     334         2288 :         qCDebug(lc).noquote() << tr("Calculating sampling-window for %Ln sample/s at %1Hz", nullptr,
     335            0 :             settings.numberOfSamples).arg(sampleRate);
     336         1024 :         Q_ASSERT(settings.numberOfSamples != 0);
     337         1848 :         settings.samplingWindow = settings.numberOfSamples * 1'000'000ull / sampleRate;
     338         2288 :         qCDebug(lc).noquote() << tr("Calculated %1us").arg(settings.samplingWindow);
     339         1848 :         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         1024 :     }
     345         1792 :     return true;
     346         1792 : }
     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         6283 : void DsoCommand::metadataRead(const DsoService::Metadata &data)
     411         3488 : {
     412        13126 :     qCDebug(lc) << "status:" << (int)(data.status);
     413        13126 :     qCDebug(lc) << "scale:" << data.scale;
     414        13126 :     qCDebug(lc) << "mode:" << DsoService::toString(data.mode);
     415        13126 :     qCDebug(lc) << "range:" << service->toString(data.range, data.mode);
     416        13126 :     qCDebug(lc) << "samplingWindow:" << data.samplingWindow;
     417        13126 :     qCDebug(lc) << "numberOfSamples:" << data.numberOfSamples;
     418        13126 :     qCDebug(lc) << "samplingRate:" << data.samplingRate << "Hz";
     419         9771 :     this->metadata = data;
     420         9771 :     this->samplesToGo = data.numberOfSamples;
     421         9771 : }
     422              : 
     423              : /*!
     424              :  * Outputs DSO \a samples in the selected output format.
     425              :  */
     426         7416 : void DsoCommand::outputSamples(const DsoService::Samples &samples)
     427         4032 : {
     428         9072 :     QString unit;
     429        11448 :     switch (metadata.mode) {
     430         4122 :     case DsoService::Mode::DcVoltage: unit = u"Vdc"_s; break;
     431         4122 :     case DsoService::Mode::AcVoltage: unit = u"Vac"_s; break;
     432         4122 :     case DsoService::Mode::DcCurrent: unit = u"Adc"_s; break;
     433         4122 :     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         4032 :     }
     438        13824 :     const QString range = service->toString(metadata.range, metadata.mode);
     439              : 
     440        60840 :     for (const qint16 &sample: samples) {
     441        53424 :         static quint32 sampleNumber = 0; ++sampleNumber;
     442        53424 :         const float value = sample * metadata.scale;
     443        53424 :         switch (format) {
     444         6272 :         case OutputFormat::Csv:
     445        20352 :             for (; showCsvHeader; showCsvHeader = false) {
     446         3664 :                 std::cout << qUtf8Printable(tr("sample_number,value,unit,range\n"));
     447          896 :             }
     448        34272 :             std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
     449         6272 :                 .arg(sampleNumber).arg(value).arg(unit, range));
     450        17808 :             break;
     451        17808 :         case OutputFormat::Json:
     452        66416 :             std::cout << QJsonDocument(QJsonObject{
     453        20496 :                     { u"value"_s,  value },
     454        20496 :                     { u"unit"_s,   unit },
     455        20496 :                     { u"range"_s,  range },
     456        29344 :                     { u"mode"_s,   DsoService::toString(metadata.mode) },
     457        73696 :                 }).toJson().toStdString();
     458        17808 :             break;
     459        17808 :         case OutputFormat::Text:
     460        36512 :             std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
     461        17808 :             break;
     462        18816 :         }
     463        53424 :         --samplesToGo;
     464              : 
     465        53424 :         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        18816 :     }
     471              : 
     472              :     // If we've received all the data for the current window, begin fetching another.
     473        11448 :     if (samplesToGo <= 0) {
     474        15408 :         qCDebug(lc).noquote() << tr("Finished fetching %Ln window sample/s (with %L2 to remaining).",
     475            0 :             nullptr, metadata.numberOfSamples).arg(samplesToGo);
     476        11448 :         if (settings.range != +PokitMeter::VoltageRange::AutoRange) service->setSettings(settings);
     477         4032 :     }
     478        45160 : }
        

Generated by: LCOV version 2.5-0