LCOV - code coverage report
Current view: top level - src/app - dsocommand.cpp (source / functions) Hit Total Coverage
Project: QtPokit Lines: 134 163 82.2 %
Version: Functions: 11 14 78.6 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2022-2023 Paul Colby <git@colby.id.au>
       2             : // SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4             : #include "dsocommand.h"
       5             : 
       6             : #include <qtpokit/pokitdevice.h>
       7             : 
       8             : #include <QJsonDocument>
       9             : #include <QJsonObject>
      10             : 
      11             : #include <iostream>
      12             : 
      13             : /*!
      14             :  * \class DsoCommand
      15             :  *
      16             :  * The DsoCommand class implements the `dso` CLI command.
      17             :  */
      18             : 
      19             : /*!
      20             :  * Construct a new DsoCommand object with \a parent.
      21             :  */
      22        1530 : DsoCommand::DsoCommand(QObject * const parent) : DeviceCommand(parent),
      23        1530 :     service(nullptr), settings{
      24             :         DsoService::Command::FreeRunning, 0.0f, DsoService::Mode::DcVoltage,
      25        1530 :         DsoService::VoltageRange::_30V_to_60V, 1000*1000, 1000}, showCsvHeader(true)
      26             : {
      27             : 
      28        1530 : }
      29             : 
      30         935 : QStringList DsoCommand::requiredOptions(const QCommandLineParser &parser) const
      31             : {
      32        3740 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      33             :         QLatin1String("mode"),
      34             :         QLatin1String("range"),
      35        3740 :     };
      36             : }
      37             : 
      38         459 : QStringList DsoCommand::supportedOptions(const QCommandLineParser &parser) const
      39             : {
      40        2754 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      41             :         QLatin1String("interval"),
      42             :         QLatin1String("samples"),
      43             :         QLatin1String("trigger-level"),
      44             :         QLatin1String("trigger-mode"),
      45        2754 :     };
      46             : }
      47             : 
      48             : /*!
      49             :  * \copybrief DeviceCommand::processOptions
      50             :  *
      51             :  * This implementation extends DeviceCommand::processOptions to process additional CLI options
      52             :  * supported (or required) by this command.
      53             :  */
      54         442 : QStringList DsoCommand::processOptions(const QCommandLineParser &parser)
      55             : {
      56         442 :     QStringList errors = DeviceCommand::processOptions(parser);
      57         442 :     if (!errors.isEmpty()) {
      58             :         return errors;
      59             :     }
      60             : 
      61             :     // Parse the (required) mode option.
      62         782 :     const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
      63         391 :     if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
      64          17 :         settings.mode = DsoService::Mode::AcVoltage;
      65         374 :     } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
      66         323 :         settings.mode = DsoService::Mode::DcVoltage;
      67          51 :     } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
      68          17 :         settings.mode = DsoService::Mode::AcCurrent;
      69          34 :     } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
      70          17 :         settings.mode = DsoService::Mode::DcCurrent;
      71             :     } else {
      72          34 :         errors.append(tr("Unknown DSO mode: %1").arg(parser.value(QLatin1String("mode"))));
      73          17 :         return errors;
      74             :     }
      75             : 
      76             :     // Parse the (required) range option.
      77          88 :     QString unit;
      78             :     {
      79         374 :         const QString value = parser.value(QLatin1String("range"));
      80             :         quint32 sensibleMinimum = 0;
      81         374 :         switch (settings.mode) {
      82             :         case DsoService::Mode::Idle:
      83             :             Q_ASSERT(false); // Not possible, since the mode parsing above never allows Idle.
      84             :             break;
      85             :         case DsoService::Mode::DcVoltage:
      86             :         case DsoService::Mode::AcVoltage:
      87         340 :             unit = QLatin1String("V");
      88             :             sensibleMinimum = 50; // mV.
      89         340 :             break;
      90             :         case DsoService::Mode::DcCurrent:
      91             :         case DsoService::Mode::AcCurrent:
      92          34 :             unit = QLatin1String("A");
      93             :             sensibleMinimum = 5; // mA.
      94          34 :             break;
      95             :         }
      96             :         Q_ASSERT(!unit.isEmpty());
      97         374 :         const quint32 rangeMax = parseMilliValue(value, unit, sensibleMinimum);
      98         374 :         if (rangeMax == 0) {
      99          20 :             errors.append(tr("Invalid range value: %1").arg(value));
     100             :         } else {
     101         357 :             settings.range = lowestRange(settings.mode, rangeMax);
     102             :         }
     103         308 :     }
     104             : 
     105             :     // Parse the trigger-level option.
     106         440 :     if (parser.isSet(QLatin1String("trigger-level"))) {
     107         136 :         const QString value = parser.value(QLatin1String("trigger-level"));
     108         136 :         const quint32 level = parseMicroValue(value, unit);
     109         136 :         if (level == 0) {
     110          20 :             errors.append(tr("Invalid trigger-level value: %1").arg(value));
     111             :         } else {
     112         119 :             settings.triggerLevel = level/1000.0/1000.0;
     113             :         }
     114         112 :     }
     115             : 
     116             :     // Parse the trigger-mode option.
     117         440 :     if (parser.isSet(QLatin1String("trigger-mode"))) {
     118         272 :         const QString triggerMode = parser.value(QLatin1String("trigger-mode")).trimmed().toLower();
     119         136 :         if (triggerMode.startsWith(QLatin1String("free"))) {
     120          51 :             settings.command = DsoService::Command::FreeRunning;
     121          85 :         } else if (triggerMode.startsWith(QLatin1String("ris"))) {
     122          34 :            settings.command = DsoService::Command::RisingEdgeTrigger;
     123          51 :         } else if (triggerMode.startsWith(QLatin1String("fall"))) {
     124          34 :             settings.command = DsoService::Command::FallingEdgeTrigger;
     125             :         } else {
     126          51 :             errors.append(tr("Unknown trigger mode: %1").arg(
     127          34 :                 parser.value(QLatin1String("trigger-mode"))));
     128             :         }
     129         112 :     }
     130             : 
     131             :     // Ensure that if either trigger option is present, then both are.
     132         748 :     if (parser.isSet(QLatin1String("trigger-level")) !=
     133         440 :         parser.isSet(QLatin1String("trigger-mode"))) {
     134          34 :         errors.append(tr("If either option is provided, then both must be: trigger-level, trigger-mode"));
     135             :     }
     136             : 
     137             :     // Parse the interval option.
     138         440 :     if (parser.isSet(QLatin1String("interval"))) {
     139         170 :         const QString value = parser.value(QLatin1String("interval"));
     140          85 :         const quint32 interval = parseMicroValue(value, QLatin1String("s"), 500*1000);
     141          85 :         if (interval == 0) {
     142          40 :             errors.append(tr("Invalid interval value: %1").arg(value));
     143             :         } else {
     144          51 :             settings.samplingWindow = interval;
     145             :         }
     146          70 :     }
     147             : 
     148             :     // Parse the samples option.
     149         440 :     if (parser.isSet(QLatin1String("samples"))) {
     150         102 :         const QString value = parser.value(QLatin1String("samples"));
     151          51 :         const quint32 samples = parseWholeValue(value, QLatin1String("S"));
     152          51 :         if (samples == 0) {
     153          40 :             errors.append(tr("Invalid samples value: %1").arg(value));
     154             :         } else {
     155          17 :             settings.numberOfSamples = samples;
     156             :         }
     157          42 :     }
     158             :     return errors;
     159         322 : }
     160             : 
     161             : /*!
     162             :  * \copybrief DeviceCommand::getService
     163             :  *
     164             :  * This override returns a pointer to a DsoService object.
     165             :  */
     166           0 : AbstractPokitService * DsoCommand::getService()
     167             : {
     168             :     Q_ASSERT(device);
     169           0 :     if (!service) {
     170           0 :         service = device->dso();
     171             :         Q_ASSERT(service);
     172           0 :         connect(service, &DsoService::settingsWritten,
     173             :                 this, &DsoCommand::settingsWritten);
     174             :     }
     175           0 :     return service;
     176             : }
     177             : 
     178             : /*!
     179             :  * \copybrief DeviceCommand::serviceDetailsDiscovered
     180             :  *
     181             :  * This override fetches the current device's status, and outputs it in the selected format.
     182             :  */
     183           0 : void DsoCommand::serviceDetailsDiscovered()
     184             : {
     185           0 :     DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
     186           0 :     const QString range = DsoService::toString(settings.range, settings.mode);
     187           0 :     qCInfo(lc).noquote() << tr("Sampling %1, with range %2, %L3 samples over %L4us").arg(
     188           0 :         DsoService::toString(settings.mode), (range.isNull()) ? QString::fromLatin1("N/A") : range)
     189           0 :         .arg(settings.numberOfSamples).arg(settings.samplingWindow);
     190           0 :     service->setSettings(settings);
     191           0 : }
     192             : 
     193             : /*!
     194             :  * Returns the lowest \a mode range that can measure at least up to \a desired max, or AutoRange
     195             :  * if no such range is available.
     196             :  */
     197        1411 : DsoService::Range DsoCommand::lowestRange(
     198             :     const DsoService::Mode mode, const quint32 desiredMax)
     199             : {
     200        1411 :     switch (mode) {
     201           0 :     case DsoService::Mode::Idle:
     202           0 :         qCWarning(lc).noquote() << tr("Idle has no defined ranges.");
     203             :         Q_ASSERT(false); // Should never have been called with this Idle mode.
     204           0 :         break;
     205         901 :     case DsoService::Mode::DcVoltage:
     206             :     case DsoService::Mode::AcVoltage:
     207         901 :         return lowestVoltageRange(desiredMax);
     208         510 :     case DsoService::Mode::DcCurrent:
     209             :     case DsoService::Mode::AcCurrent:
     210         510 :         return lowestCurrentRange(desiredMax);
     211           0 :     default:
     212           0 :         qCWarning(lc).noquote() << tr("No defined ranges for mode %1.").arg((quint8)mode);
     213             :         Q_ASSERT(false); // Should never have been called with this invalid mode.
     214             :     }
     215           0 :     return DsoService::Range();
     216             : }
     217             : 
     218             : #define POKIT_APP_IF_LESS_THAN_RETURN(value, label) \
     219             : if (value <=  DsoService::maxValue(DsoService::label).toUInt()) { \
     220             :     return DsoService::label; \
     221             : }
     222             : 
     223             : /*!
     224             :  * Returns the lowest current range that can measure at least up to \a desired max, or AutoRange
     225             :  * if no such range is available.
     226             :  */
     227         748 : DsoService::CurrentRange DsoCommand::lowestCurrentRange(const quint32 desiredMax)
     228             : {
     229         748 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_0_to_10mA)
     230         595 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_10mA_to_30mA)
     231         442 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_30mA_to_150mA)
     232         272 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_150mA_to_300mA)
     233         119 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_300mA_to_3A)
     234             :     return DsoService::CurrentRange::_300mA_to_3A; // Out of range, so go with the biggest.
     235             : }
     236             : 
     237             : /*!
     238             :  * Returns the lowest voltage range that can measure at least up to \a desired max, or AutoRange
     239             :  * if no such range is available.
     240             :  */
     241        1190 : DsoService::VoltageRange DsoCommand::lowestVoltageRange(const quint32 desiredMax)
     242             : {
     243        1190 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_0_to_300mV)
     244        1037 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_300mV_to_2V)
     245         578 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_2V_to_6V)
     246         408 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_6V_to_12V)
     247         255 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_12V_to_30V)
     248         102 :     POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_30V_to_60V)
     249             :     return DsoService::VoltageRange::_30V_to_60V; // Out of range, so go with the biggest.
     250             : }
     251             : 
     252             : #undef POKIT_APP_IF_LESS_THAN_RETURN
     253             : 
     254             : /*!
     255             :  * Invoked when the DSO settings have been written.
     256             :  */
     257           0 : void DsoCommand::settingsWritten()
     258             : {
     259             :     Q_ASSERT(service);
     260           0 :     qCDebug(lc).noquote() << tr("Settings written; DSO has started.");
     261           0 :     connect(service, &DsoService::metadataRead, this, &DsoCommand::metadataRead);
     262           0 :     connect(service, &DsoService::samplesRead, this, &DsoCommand::outputSamples);
     263           0 :     service->enableMetadataNotifications();
     264           0 :     service->enableReadingNotifications();
     265           0 : }
     266             : 
     267             : /*!
     268             :  * Invoked when \a metadata has been received from the DSO.
     269             :  */
     270        1037 : void DsoCommand::metadataRead(const DsoService::Metadata &metadata)
     271             : {
     272        1037 :     qCDebug(lc) << "status:" << (int)(metadata.status);
     273        1037 :     qCDebug(lc) << "scale:" << metadata.scale;
     274        1037 :     qCDebug(lc) << "mode:" << DsoService::toString(metadata.mode);
     275        1037 :     qCDebug(lc) << "range:" << DsoService::toString(metadata.range.voltageRange);
     276        1037 :     qCDebug(lc) << "samplingWindow:" << (int)metadata.samplingWindow;
     277        1037 :     qCDebug(lc) << "numberOfSamples:" << metadata.numberOfSamples;
     278        1037 :     qCDebug(lc) << "samplingRate:" << metadata.samplingRate << "Hz";
     279        1037 :     this->metadata = metadata;
     280        1037 :     this->samplesToGo = metadata.numberOfSamples;
     281        1037 : }
     282             : 
     283             : /*!
     284             :  * Outputs DSO \a samples in the selected ouput format.
     285             :  */
     286        1224 : void DsoCommand::outputSamples(const DsoService::Samples &samples)
     287             : {
     288         216 :     QString unit;
     289        1224 :     switch (metadata.mode) {
     290         306 :     case DsoService::Mode::DcVoltage: unit = QLatin1String("Vdc"); break;
     291         306 :     case DsoService::Mode::AcVoltage: unit = QLatin1String("Vac"); break;
     292         306 :     case DsoService::Mode::DcCurrent: unit = QLatin1String("Adc"); break;
     293         306 :     case DsoService::Mode::AcCurrent: unit = QLatin1String("Aac"); break;
     294           0 :     default:
     295           0 :         qCDebug(lc).noquote() << tr("No known unit for mode %1 \"%2\".").arg((int)metadata.mode)
     296           0 :             .arg(DsoService::toString(metadata.mode));
     297             :     }
     298        2232 :     const QString range = DsoService::toString(metadata.range, metadata.mode);
     299             : 
     300        6936 :     for (const qint16 &sample: samples) {
     301        5712 :         static int sampleNumber = 0; ++sampleNumber;
     302        5712 :         const float value = sample * metadata.scale;
     303        5712 :         switch (format) {
     304             :         case OutputFormat::Csv:
     305        2176 :             for (; showCsvHeader; showCsvHeader = false) {
     306         320 :                 std::cout << qUtf8Printable(tr("sample_number,value,unit,range\n"));
     307             :             }
     308        4144 :             std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
     309             :                 .arg(sampleNumber).arg(value).arg(unit, range));
     310        1904 :             break;
     311             :         case OutputFormat::Json:
     312       18032 :             std::cout << QJsonDocument(QJsonObject{
     313         336 :                     { QLatin1String("value"),  value },
     314         336 :                     { QLatin1String("unit"),   unit },
     315         336 :                     { QLatin1String("range"),  range },
     316        3472 :                     { QLatin1String("mode"),   DsoService::toString(metadata.mode) },
     317       11424 :                 }).toJson().toStdString();
     318        1904 :             break;
     319        1904 :         case OutputFormat::Text:
     320        4144 :             std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
     321        1904 :             break;
     322             :         }
     323        5712 :         --samplesToGo;
     324             :     }
     325        1224 :     if (samplesToGo <= 0) {
     326        2448 :         qCInfo(lc).noquote() << tr("Finished fetching %L1 samples (with %L3 to remaining).")
     327        1440 :             .arg(metadata.numberOfSamples).arg(samplesToGo);
     328        1224 :         if (device) disconnect(); // Will exit the application once disconnected.
     329             :     }
     330        1224 : }

Generated by: LCOV version 1.14