LCOV - code coverage report
Current view: top level - src/cli - dsocommand.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 138 169 81.7 %
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        1656 : DsoCommand::DsoCommand(QObject * const parent) : DeviceCommand(parent)
      23             : {
      24             : 
      25        1656 : }
      26             : 
      27        1062 : QStringList DsoCommand::requiredOptions(const QCommandLineParser &parser) const
      28             : {
      29        4602 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      30             :         QLatin1String("mode"),
      31             :         QLatin1String("range"),
      32        4189 :     };
      33           0 : }
      34             : 
      35         522 : QStringList DsoCommand::supportedOptions(const QCommandLineParser &parser) const
      36             : {
      37        3306 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      38             :         QLatin1String("interval"),
      39             :         QLatin1String("samples"),
      40             :         QLatin1String("trigger-level"),
      41             :         QLatin1String("trigger-mode"),
      42        3103 :     };
      43           0 : }
      44             : 
      45             : /*!
      46             :  * \copybrief DeviceCommand::processOptions
      47             :  *
      48             :  * This implementation extends DeviceCommand::processOptions to process additional CLI options
      49             :  * supported (or required) by this command.
      50             :  */
      51         504 : QStringList DsoCommand::processOptions(const QCommandLineParser &parser)
      52             : {
      53         504 :     QStringList errors = DeviceCommand::processOptions(parser);
      54         504 :     if (!errors.isEmpty()) {
      55             :         return errors;
      56             :     }
      57             : 
      58             :     // Parse the (required) mode option.
      59         900 :     const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
      60         450 :     if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
      61          18 :         settings.mode = DsoService::Mode::AcVoltage;
      62         432 :     } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
      63         378 :         settings.mode = DsoService::Mode::DcVoltage;
      64          54 :     } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
      65          18 :         settings.mode = DsoService::Mode::AcCurrent;
      66          36 :     } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
      67          18 :         settings.mode = DsoService::Mode::DcCurrent;
      68             :     } else {
      69          36 :         errors.append(tr("Unknown DSO mode: %1").arg(parser.value(QLatin1String("mode"))));
      70          18 :         return errors;
      71             :     }
      72             : 
      73             :     // Parse the (required) range option.
      74          96 :     QString unit;
      75             :     {
      76         432 :         const QString value = parser.value(QLatin1String("range"));
      77             :         quint32 sensibleMinimum = 0;
      78         432 :         switch (settings.mode) {
      79             :         case DsoService::Mode::Idle:
      80             :             Q_ASSERT(false); // Not possible, since the mode parsing above never allows Idle.
      81             :             break;
      82             :         case DsoService::Mode::DcVoltage:
      83             :         case DsoService::Mode::AcVoltage:
      84         396 :             unit = QLatin1String("V");
      85             :             sensibleMinimum = 50; // mV.
      86         396 :             break;
      87             :         case DsoService::Mode::DcCurrent:
      88             :         case DsoService::Mode::AcCurrent:
      89          36 :             unit = QLatin1String("A");
      90             :             sensibleMinimum = 5; // mA.
      91          36 :             break;
      92             :         }
      93             :         Q_ASSERT(!unit.isEmpty());
      94         432 :         const quint32 rangeMax = parseMilliValue(value, unit, sensibleMinimum);
      95         432 :         if (rangeMax == 0) {
      96          22 :             errors.append(tr("Invalid range value: %1").arg(value));
      97             :         } else {
      98         414 :             settings.range = lowestRange(settings.mode, rangeMax);
      99             :         }
     100         336 :     }
     101             : 
     102             :     // Parse the trigger-level option.
     103         528 :     if (parser.isSet(QLatin1String("trigger-level"))) {
     104         144 :         const QString value = parser.value(QLatin1String("trigger-level"));
     105         144 :         const quint32 level = parseMicroValue(value, unit);
     106         144 :         if (level == 0) {
     107          22 :             errors.append(tr("Invalid trigger-level value: %1").arg(value));
     108             :         } else {
     109         126 :             settings.triggerLevel = (float)(level/1000.0/1000.0);
     110             :         }
     111         112 :     }
     112             : 
     113             :     // Parse the trigger-mode option.
     114         528 :     if (parser.isSet(QLatin1String("trigger-mode"))) {
     115         288 :         const QString triggerMode = parser.value(QLatin1String("trigger-mode")).trimmed().toLower();
     116         144 :         if (triggerMode.startsWith(QLatin1String("free"))) {
     117          54 :             settings.command = DsoService::Command::FreeRunning;
     118          90 :         } else if (triggerMode.startsWith(QLatin1String("ris"))) {
     119          36 :            settings.command = DsoService::Command::RisingEdgeTrigger;
     120          54 :         } else if (triggerMode.startsWith(QLatin1String("fall"))) {
     121          36 :             settings.command = DsoService::Command::FallingEdgeTrigger;
     122             :         } else {
     123          54 :             errors.append(tr("Unknown trigger mode: %1").arg(
     124          36 :                 parser.value(QLatin1String("trigger-mode"))));
     125             :         }
     126         112 :     }
     127             : 
     128             :     // Ensure that if either trigger option is present, then both are.
     129         864 :     if (parser.isSet(QLatin1String("trigger-level")) !=
     130         528 :         parser.isSet(QLatin1String("trigger-mode"))) {
     131          36 :         errors.append(tr("If either option is provided, then both must be: trigger-level, trigger-mode"));
     132             :     }
     133             : 
     134             :     // Parse the interval option.
     135         528 :     if (parser.isSet(QLatin1String("interval"))) {
     136         150 :         const QString value = parser.value(QLatin1String("interval"));
     137          90 :         const quint32 interval = parseMicroValue(value, QLatin1String("s"), 500*1000);
     138          90 :         if (interval == 0) {
     139          44 :             errors.append(tr("Invalid interval value: %1").arg(value));
     140             :         } else {
     141          54 :             settings.samplingWindow = interval;
     142             :         }
     143          70 :     }
     144             : 
     145             :     // Parse the samples option.
     146         528 :     if (parser.isSet(QLatin1String("samples"))) {
     147         150 :         const QString value = parser.value(QLatin1String("samples"));
     148          90 :         const quint32 samples = parseWholeValue(value, QLatin1String("S"));
     149          90 :         if (samples == 0) {
     150          44 :             errors.append(tr("Invalid samples value: %1").arg(value));
     151          54 :         } else if (samples > std::numeric_limits<quint16>::max()) {
     152          26 :             errors.append(tr("Samples value (%1) must be no greater than %2")
     153          40 :                 .arg(value).arg(std::numeric_limits<quint16>::max()));
     154             :         } else {
     155          36 :             if (samples > 8192) {
     156          40 :                 qCWarning(lc).noquote() << tr("Pokit devices do not officially support great than 8192 samples");
     157             :             }
     158          36 :             settings.numberOfSamples = (quint16)samples;
     159             :         }
     160          70 :     }
     161             :     return errors;
     162         350 : }
     163             : 
     164             : /*!
     165             :  * \copybrief DeviceCommand::getService
     166             :  *
     167             :  * This override returns a pointer to a DsoService object.
     168             :  */
     169           0 : AbstractPokitService * DsoCommand::getService()
     170             : {
     171             :     Q_ASSERT(device);
     172           0 :     if (!service) {
     173           0 :         service = device->dso();
     174             :         Q_ASSERT(service);
     175           0 :         connect(service, &DsoService::settingsWritten,
     176             :                 this, &DsoCommand::settingsWritten);
     177             :     }
     178           0 :     return service;
     179             : }
     180             : 
     181             : /*!
     182             :  * \copybrief DeviceCommand::serviceDetailsDiscovered
     183             :  *
     184             :  * This override fetches the current device's status, and outputs it in the selected format.
     185             :  */
     186           0 : void DsoCommand::serviceDetailsDiscovered()
     187             : {
     188           0 :     DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
     189           0 :     const QString range = DsoService::toString(settings.range, settings.mode);
     190           0 :     qCInfo(lc).noquote() << tr("Sampling %1, with range %2, %Ln sample/s over %L3us", nullptr, settings.numberOfSamples)
     191           0 :         .arg(DsoService::toString(settings.mode), (range.isNull()) ? QString::fromLatin1("N/A") : range)
     192           0 :         .arg(settings.samplingWindow);
     193           0 :     service->setSettings(settings);
     194           0 : }
     195             : 
     196             : /*!
     197             :  * Returns the lowest \a mode range that can measure at least up to \a desired max, or AutoRange
     198             :  * if no such range is available.
     199             :  */
     200        1530 : DsoService::Range DsoCommand::lowestRange(
     201             :     const DsoService::Mode mode, const quint32 desiredMax)
     202             : {
     203        1530 :     switch (mode) {
     204           0 :     case DsoService::Mode::Idle:
     205           0 :         qCWarning(lc).noquote() << tr("Idle has no defined ranges.");
     206             :         Q_ASSERT(false); // Should never have been called with this Idle mode.
     207           0 :         break;
     208         990 :     case DsoService::Mode::DcVoltage:
     209             :     case DsoService::Mode::AcVoltage:
     210         990 :         return lowestVoltageRange(desiredMax);
     211         540 :     case DsoService::Mode::DcCurrent:
     212             :     case DsoService::Mode::AcCurrent:
     213         540 :         return lowestCurrentRange(desiredMax);
     214           0 :     default:
     215           0 :         qCWarning(lc).noquote() << tr("No defined ranges for mode %1.").arg((quint8)mode);
     216             :         Q_ASSERT(false); // Should never have been called with this invalid mode.
     217             :     }
     218           0 :     return DsoService::Range();
     219             : }
     220             : 
     221             : #define DOKIT_CLI_IF_LESS_THAN_RETURN(value, label) \
     222             : if (value <=  DsoService::maxValue(DsoService::label).toUInt()) { \
     223             :     return DsoService::label; \
     224             : }
     225             : 
     226             : /*!
     227             :  * Returns the lowest current range that can measure at least up to \a desired max, or AutoRange
     228             :  * if no such range is available.
     229             :  */
     230         792 : DsoService::CurrentRange DsoCommand::lowestCurrentRange(const quint32 desiredMax)
     231             : {
     232         792 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_0_to_10mA)
     233         630 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_10mA_to_30mA)
     234         468 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_30mA_to_150mA)
     235         288 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_150mA_to_300mA)
     236         126 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_300mA_to_3A)
     237             :     return DsoService::CurrentRange::_300mA_to_3A; // Out of range, so go with the biggest.
     238             : }
     239             : 
     240             : /*!
     241             :  * Returns the lowest voltage range that can measure at least up to \a desired max, or AutoRange
     242             :  * if no such range is available.
     243             :  */
     244        1296 : DsoService::VoltageRange DsoCommand::lowestVoltageRange(const quint32 desiredMax)
     245             : {
     246        1296 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_0_to_300mV)
     247        1134 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_300mV_to_2V)
     248         612 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_2V_to_6V)
     249         432 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_6V_to_12V)
     250         270 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_12V_to_30V)
     251         108 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_30V_to_60V)
     252             :     return DsoService::VoltageRange::_30V_to_60V; // Out of range, so go with the biggest.
     253             : }
     254             : 
     255             : #undef DOKIT_CLI_IF_LESS_THAN_RETURN
     256             : 
     257             : /*!
     258             :  * Invoked when the DSO settings have been written.
     259             :  */
     260           0 : void DsoCommand::settingsWritten()
     261             : {
     262             :     Q_ASSERT(service);
     263           0 :     qCDebug(lc).noquote() << tr("Settings written; DSO has started.");
     264           0 :     connect(service, &DsoService::metadataRead, this, &DsoCommand::metadataRead);
     265           0 :     connect(service, &DsoService::samplesRead, this, &DsoCommand::outputSamples);
     266           0 :     service->enableMetadataNotifications();
     267           0 :     service->enableReadingNotifications();
     268           0 : }
     269             : 
     270             : /*!
     271             :  * Invoked when \a metadata has been received from the DSO.
     272             :  */
     273        1098 : void DsoCommand::metadataRead(const DsoService::Metadata &data)
     274             : {
     275        1098 :     qCDebug(lc) << "status:" << (int)(data.status);
     276        1098 :     qCDebug(lc) << "scale:" << data.scale;
     277        1098 :     qCDebug(lc) << "mode:" << DsoService::toString(data.mode);
     278        1098 :     qCDebug(lc) << "range:" << DsoService::toString(data.range.voltageRange);
     279        1098 :     qCDebug(lc) << "samplingWindow:" << (int)data.samplingWindow;
     280        1098 :     qCDebug(lc) << "numberOfSamples:" << data.numberOfSamples;
     281        1098 :     qCDebug(lc) << "samplingRate:" << data.samplingRate << "Hz";
     282        1098 :     this->metadata = data;
     283        1098 :     this->samplesToGo = data.numberOfSamples;
     284        1098 : }
     285             : 
     286             : /*!
     287             :  * Outputs DSO \a samples in the selected ouput format.
     288             :  */
     289        1296 : void DsoCommand::outputSamples(const DsoService::Samples &samples)
     290             : {
     291         288 :     QString unit;
     292        1296 :     switch (metadata.mode) {
     293         324 :     case DsoService::Mode::DcVoltage: unit = QLatin1String("Vdc"); break;
     294         324 :     case DsoService::Mode::AcVoltage: unit = QLatin1String("Vac"); break;
     295         324 :     case DsoService::Mode::DcCurrent: unit = QLatin1String("Adc"); break;
     296         324 :     case DsoService::Mode::AcCurrent: unit = QLatin1String("Aac"); break;
     297           0 :     default:
     298           0 :         qCDebug(lc).noquote() << tr("No known unit for mode %1 \"%2\".").arg((int)metadata.mode)
     299           0 :             .arg(DsoService::toString(metadata.mode));
     300             :     }
     301        2304 :     const QString range = DsoService::toString(metadata.range, metadata.mode);
     302             : 
     303        7344 :     for (const qint16 &sample: samples) {
     304        6048 :         static int sampleNumber = 0; ++sampleNumber;
     305        6048 :         const float value = sample * metadata.scale;
     306        6048 :         switch (format) {
     307             :         case OutputFormat::Csv:
     308        2304 :             for (; showCsvHeader; showCsvHeader = false) {
     309         352 :                 std::cout << qUtf8Printable(tr("sample_number,value,unit,range\n"));
     310             :             }
     311        4480 :             std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
     312             :                 .arg(sampleNumber).arg(value).arg(unit, range));
     313        2016 :             break;
     314         672 :         case OutputFormat::Json:
     315       16128 :             std::cout << QJsonDocument(QJsonObject{
     316         448 :                     { QLatin1String("value"),  value },
     317         448 :                     { QLatin1String("unit"),   unit },
     318         448 :                     { QLatin1String("range"),  range },
     319        3584 :                     { QLatin1String("mode"),   DsoService::toString(metadata.mode) },
     320       12096 :                 }).toJson().toStdString();
     321        2016 :             break;
     322        2016 :         case OutputFormat::Text:
     323        4480 :             std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
     324        2016 :             break;
     325             :         }
     326        6048 :         --samplesToGo;
     327             :     }
     328        1296 :     if (samplesToGo <= 0) {
     329        3024 :         qCInfo(lc).noquote() << tr("Finished fetching %Ln sample/s (with %L2 to remaining).",
     330        2016 :             nullptr, metadata.numberOfSamples).arg(samplesToGo);
     331        1296 :         if (device) disconnect(); // Will exit the application once disconnected.
     332             :     }
     333        5328 : }

Generated by: LCOV version 1.14