LCOV - code coverage report
Current view: top level - src/cli - metercommand.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 182 202 90.1 %
Version: Functions: 10 13 76.9 %

          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 "metercommand.h"
       5             : 
       6             : #include <qtpokit/pokitdevice.h>
       7             : 
       8             : #include <QJsonDocument>
       9             : #include <QJsonObject>
      10             : 
      11             : #include <iostream>
      12             : 
      13             : /*!
      14             :  * \class MeterCommand
      15             :  *
      16             :  * The MeterCommand class implements the `meter` CLI command.
      17             :  */
      18             : 
      19             : /*!
      20             :  * Construct a new MeterCommand object with \a parent.
      21             :  */
      22        1170 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
      23             : {
      24             : 
      25        1170 : }
      26             : 
      27         882 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
      28             : {
      29        2940 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      30             :         QLatin1String("mode"),
      31        2597 :     };
      32             : }
      33             : 
      34         432 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
      35             : {
      36        2304 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      37             :         QLatin1String("interval"),
      38             :         QLatin1String("range"),
      39             :         QLatin1String("samples"),
      40        2136 :     };
      41           0 : }
      42             : 
      43             : /*!
      44             :  * \copybrief DeviceCommand::processOptions
      45             :  *
      46             :  * This implementation extends DeviceCommand::processOptions to process additional CLI options
      47             :  * supported (or required) by this command.
      48             :  */
      49         414 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
      50             : {
      51         414 :     QStringList errors = DeviceCommand::processOptions(parser);
      52         414 :     if (!errors.isEmpty()) {
      53             :         return errors;
      54             :     }
      55             : 
      56             :     // Parse the (required) mode option.
      57         792 :     const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
      58         396 :     if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
      59          18 :         settings.mode = MultimeterService::Mode::AcVoltage;
      60         378 :     } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
      61         198 :         settings.mode = MultimeterService::Mode::DcVoltage;
      62         180 :     } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
      63          18 :         settings.mode = MultimeterService::Mode::AcCurrent;
      64         162 :     } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
      65          36 :         settings.mode = MultimeterService::Mode::DcCurrent;
      66         126 :     } else if (mode.startsWith(QLatin1String("res"))) {
      67          36 :         settings.mode = MultimeterService::Mode::Resistance;
      68          90 :     } else if (mode.startsWith(QLatin1String("dio"))) {
      69          36 :         settings.mode = MultimeterService::Mode::Diode;
      70          54 :     } else if (mode.startsWith(QLatin1String("cont"))) {
      71          18 :        settings.mode = MultimeterService::Mode::Continuity;
      72          36 :     } else if (mode.startsWith(QLatin1String("temp"))) {
      73          18 :        settings.mode = MultimeterService::Mode::Temperature;
      74             :     } else {
      75          36 :         errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
      76          18 :         return errors;
      77             :     }
      78             : 
      79             :     // Parse the interval option.
      80         462 :     if (parser.isSet(QLatin1String("interval"))) {
      81         150 :         const QString value = parser.value(QLatin1String("interval"));
      82          90 :         const quint32 interval = parseMilliValue(value, QLatin1String("s"), 500);
      83          90 :         if (interval == 0) {
      84          44 :             errors.append(tr("Invalid interval value: %1").arg(value));
      85             :         } else {
      86          54 :             settings.updateInterval = interval;
      87             :         }
      88          70 :     }
      89             : 
      90             :     // Parse the range option.
      91         462 :     if (parser.isSet(QLatin1String("range"))) {
      92         270 :         const QString value = parser.value(QLatin1String("range"));
      93         162 :         const bool isAuto = (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) == 0);
      94          36 :         QString unit; quint32 sensibleMinimum = 0;
      95         162 :         switch (settings.mode) {
      96          72 :         case MultimeterService::Mode::DcVoltage:
      97             :         case MultimeterService::Mode::AcVoltage:
      98          72 :             if (isAuto) {
      99          18 :                 settings.range.voltageRange = MultimeterService::VoltageRange::AutoRange;
     100             :             }
     101          72 :             unit = QLatin1String("V");
     102             :             sensibleMinimum = 50; // mV.
     103          72 :             break;
     104          54 :         case MultimeterService::Mode::DcCurrent:
     105             :         case MultimeterService::Mode::AcCurrent:
     106          54 :             if (isAuto) {
     107          18 :                 settings.range.currentRange = MultimeterService::CurrentRange::AutoRange;
     108             :             }
     109          54 :             unit = QLatin1String("A");
     110             :             sensibleMinimum = 5; // mA.
     111          54 :             break;
     112          18 :         case MultimeterService::Mode::Resistance:
     113          18 :             if (isAuto) {
     114          18 :                 settings.range.resistanceRange = MultimeterService::ResistanceRange::AutoRange;
     115             :             }
     116          18 :             unit = QLatin1String("ohms");
     117             :             sensibleMinimum = 0; // Unused.
     118          18 :             break;
     119          18 :         default:
     120          40 :             qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
     121             :         }
     122         162 :         if ((!unit.isEmpty()) && (!isAuto)) { // isEmpty indicates a mode that has no range option.
     123             :             const quint32 rangeMax = (sensibleMinimum == 0)
     124          90 :                 ? parseWholeValue(value, unit) : parseMilliValue(value, unit, sensibleMinimum);
     125          90 :             if (rangeMax == 0) {
     126          22 :                 errors.append(tr("Invalid range value: %1").arg(value));
     127             :             } else {
     128          72 :                 settings.range = lowestRange(settings.mode, rangeMax);
     129             :             }
     130             :         }
     131         126 :     }
     132             : 
     133             :     // Parse the samples option.
     134         462 :     if (parser.isSet(QLatin1String("samples"))) {
     135          90 :         const QString value = parser.value(QLatin1String("samples"));
     136          54 :         const quint32 samples = parseWholeValue(value, QLatin1String("S"));
     137          54 :         if (samples == 0) {
     138          44 :             errors.append(tr("Invalid samples value: %1").arg(value));
     139             :         } else {
     140          18 :             samplesToGo = samples;
     141             :         }
     142          42 :     }
     143             :     return errors;
     144         308 : }
     145             : 
     146             : /*!
     147             :  * \copybrief DeviceCommand::getService
     148             :  *
     149             :  * This override returns a pointer to a MultimeterService object.
     150             :  */
     151           0 : AbstractPokitService * MeterCommand::getService()
     152             : {
     153             :     Q_ASSERT(device);
     154           0 :     if (!service) {
     155           0 :         service = device->multimeter();
     156             :         Q_ASSERT(service);
     157           0 :         connect(service, &MultimeterService::settingsWritten,
     158             :                 this, &MeterCommand::settingsWritten);
     159             :     }
     160           0 :     return service;
     161             : }
     162             : 
     163             : /*!
     164             :  * \copybrief DeviceCommand::serviceDetailsDiscovered
     165             :  *
     166             :  * This override fetches the current device's status, and outputs it in the selected format.
     167             :  */
     168           0 : void MeterCommand::serviceDetailsDiscovered()
     169             : {
     170           0 :     DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
     171           0 :     const QString range = MultimeterService::toString(settings.range, settings.mode);
     172           0 :     qCInfo(lc).noquote() << tr("Measuring %1, with range %2, every %L3ms.").arg(
     173           0 :         MultimeterService::toString(settings.mode),
     174           0 :         (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
     175           0 :     service->setSettings(settings);
     176           0 : }
     177             : 
     178             : /*!
     179             :  * Returns the lowest \a mode range that can measure at least up to \a desired max, or AutoRange
     180             :  * if no such range is available.
     181             :  */
     182        1944 : MultimeterService::Range MeterCommand::lowestRange(
     183             :     const MultimeterService::Mode mode, const quint32 desiredMax)
     184             : {
     185        1944 :     MultimeterService::Range range;
     186        1944 :     switch (mode) {
     187         756 :     case MultimeterService::Mode::DcVoltage:
     188             :     case MultimeterService::Mode::AcVoltage:
     189         756 :         range.voltageRange = lowestVoltageRange(desiredMax);
     190         756 :         break;
     191         648 :     case MultimeterService::Mode::DcCurrent:
     192             :     case MultimeterService::Mode::AcCurrent:
     193         648 :         range.currentRange = lowestCurrentRange(desiredMax);
     194         648 :         break;
     195         468 :     case MultimeterService::Mode::Resistance:
     196         468 :         range.resistanceRange = lowestResistanceRange(desiredMax);
     197         468 :         break;
     198          72 :     default:
     199         160 :         qCWarning(lc).noquote() << tr("Mode does not support range.");
     200          72 :         range.voltageRange = MultimeterService::VoltageRange::AutoRange;
     201             :     }
     202        1944 :     return range;
     203             : }
     204             : 
     205             : #define DOKIT_CLI_IF_LESS_THAN_RETURN(value, label) \
     206             : if (value <=  MultimeterService::maxValue(MultimeterService::label).toUInt()) { \
     207             :     return MultimeterService::label; \
     208             : }
     209             : 
     210             : /*!
     211             :  * Returns the lowest current range that can measure at least up to \a desired max, or AutoRange
     212             :  * if no such range is available.
     213             :  */
     214         954 : MultimeterService::CurrentRange MeterCommand::lowestCurrentRange(const quint32 desiredMax)
     215             : {
     216         954 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_0_to_10mA)
     217         792 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_10mA_to_30mA)
     218         630 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_30mA_to_150mA)
     219         450 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_150mA_to_300mA)
     220         288 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_300mA_to_3A)
     221             :     return MultimeterService::CurrentRange::AutoRange;
     222             : }
     223             : 
     224             : /*!
     225             :  * Returns the lowest resistance range that can measure at least up to \a desired max, or AutoRange
     226             :  * if no such range is available.
     227             :  */
     228         936 : MultimeterService::ResistanceRange MeterCommand::lowestResistanceRange(const quint32 desiredMax)
     229             : {
     230         936 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_0_to_160)
     231         828 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_160_to_330)
     232         720 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_330_to_890)
     233         612 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_890_to_1K5)
     234         504 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_1K5_to_10K)
     235         396 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_10K_to_100K)
     236         288 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_100K_to_470K)
     237         180 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_470K_to_1M)
     238             :     return MultimeterService::ResistanceRange::AutoRange;
     239             : }
     240             : 
     241             : /*!
     242             :  * Returns the lowest voltage range that can measure at least up to \a desired max, or AutoRange
     243             :  * if no such range is available.
     244             :  */
     245        1116 : MultimeterService::VoltageRange MeterCommand::lowestVoltageRange(const quint32 desiredMax)
     246             : {
     247        1116 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_0_to_300mV)
     248         954 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_300mV_to_2V)
     249         774 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_2V_to_6V)
     250         594 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_6V_to_12V)
     251         432 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_12V_to_30V)
     252         270 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_30V_to_60V)
     253             :     return MultimeterService::VoltageRange::AutoRange;
     254             : }
     255             : 
     256             : #undef DOKIT_CLI_IF_LESS_THAN_RETURN
     257             : 
     258             : /*!
     259             :  * Invoked when the multimeter settings have been written, to begin reading the meter values.
     260             :  */
     261           0 : void MeterCommand::settingsWritten()
     262             : {
     263           0 :     qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
     264           0 :     connect(service, &MultimeterService::readingRead,
     265             :             this, &MeterCommand::outputReading);
     266           0 :     service->enableReadingNotifications();
     267           0 : }
     268             : 
     269             : /*!
     270             :  * Outputs meter \a reading in the selected ouput format.
     271             :  */
     272        1242 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
     273             : {
     274         276 :     QString status;
     275        1242 :     if (reading.status == MultimeterService::MeterStatus::Error) {
     276         108 :         status = QLatin1String("Error");
     277        1134 :     } else switch (reading.mode) {
     278             :     case MultimeterService::Mode::Idle:
     279             :         break;
     280         540 :     case MultimeterService::Mode::DcVoltage:
     281             :     case MultimeterService::Mode::AcVoltage:
     282             :     case MultimeterService::Mode::DcCurrent:
     283             :     case MultimeterService::Mode::AcCurrent:
     284             :     case MultimeterService::Mode::Resistance:
     285             :         status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
     286         960 :             ? tr("Auto Range On") : tr("Auto Range Off");
     287         540 :         break;
     288         216 :     case MultimeterService::Mode::Continuity:
     289             :         status = (reading.status == MultimeterService::MeterStatus::Continuity)
     290         384 :             ? tr("Continuity") : tr("No continuity");
     291         216 :         break;
     292         216 :     case MultimeterService::Mode::Temperature:
     293             :     case MultimeterService::Mode::Diode:
     294         216 :         status = tr("Ok");
     295         216 :         break;
     296             :     }
     297             : 
     298        1242 :     QString units;
     299        1242 :     switch (reading.mode) {
     300             :     case MultimeterService::Mode::Idle:        break;
     301         108 :     case MultimeterService::Mode::DcVoltage:   units = QLatin1String("Vdc"); break;
     302         108 :     case MultimeterService::Mode::AcVoltage:   units = QLatin1String("Vac"); break;
     303         108 :     case MultimeterService::Mode::DcCurrent:   units = QLatin1String("Adc"); break;
     304         108 :     case MultimeterService::Mode::AcCurrent:   units = QLatin1String("Aac"); break;
     305         132 :     case MultimeterService::Mode::Resistance:  units = QString::fromUtf8("Ω"); break;
     306             :     case MultimeterService::Mode::Diode:       break;
     307             :     case MultimeterService::Mode::Continuity:  break;
     308         132 :     case MultimeterService::Mode::Temperature: units = QString::fromUtf8("°C"); break;
     309             :     }
     310             : 
     311        1242 :     QString range;
     312        1242 :     QVariant rangeMin, rangeMax;
     313        1242 :     switch (reading.mode) {
     314             :     case MultimeterService::Mode::Idle:        break;
     315         216 :     case MultimeterService::Mode::DcVoltage:
     316             :     case MultimeterService::Mode::AcVoltage:
     317         216 :         range = MultimeterService::toString(reading.range.voltageRange);
     318         216 :         rangeMin = MultimeterService::minValue(reading.range.voltageRange);
     319         216 :         rangeMax = MultimeterService::maxValue(reading.range.voltageRange);
     320         216 :         break;
     321         216 :     case MultimeterService::Mode::DcCurrent:
     322             :     case MultimeterService::Mode::AcCurrent:
     323         216 :         range = MultimeterService::toString(reading.range.currentRange);
     324         216 :         rangeMin = MultimeterService::minValue(reading.range.currentRange);
     325         216 :         rangeMax = MultimeterService::maxValue(reading.range.currentRange);
     326         216 :         break;
     327         108 :     case MultimeterService::Mode::Resistance:
     328         108 :         range = MultimeterService::toString(reading.range.resistanceRange);
     329         108 :         rangeMin = MultimeterService::minValue(reading.range.resistanceRange);
     330         108 :         rangeMax = MultimeterService::maxValue(reading.range.resistanceRange);
     331         108 :         break;
     332             :     case MultimeterService::Mode::Diode:       break;
     333             :     case MultimeterService::Mode::Continuity:  break;
     334             :     case MultimeterService::Mode::Temperature: break;
     335             :     }
     336             : 
     337        1242 :     switch (format) {
     338             :     case OutputFormat::Csv:
     339         648 :         for (; showCsvHeader; showCsvHeader = false) {
     340         286 :             std::cout << qUtf8Printable(tr("mode,value,units,status,range_min_milli,range_max_milli\n"));
     341             :         }
     342         920 :         std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5,%6\n")
     343             :             .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
     344             :             .arg(reading.value, 0, 'f').arg(units, status, rangeMin.toString(), rangeMax.toString())
     345             :             );
     346         414 :         break;
     347         138 :     case OutputFormat::Json: {
     348             :         QJsonObject jsonObject{
     349          92 :             { QLatin1String("status"), status },
     350         414 :             { QLatin1String("value"), qIsInf(reading.value) ?
     351         460 :                 QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
     352         828 :             { QLatin1String("mode"),   MultimeterService::toString(reading.mode) },
     353        2783 :         };
     354         414 :         if ((!rangeMin.isNull()) || (!rangeMax.isNull())) {
     355         790 :             jsonObject.insert(QLatin1String("range"), QJsonObject{
     356          40 :                 { QLatin1String("min"),
     357             :                     #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
     358             :                     (rangeMin.typeId() == QMetaType::Int)
     359             :                     #else
     360         140 :                     (rangeMin.type() == QVariant::Int)
     361             :                     #endif
     362         300 :                     ? QJsonValue(rangeMin.toInt()/1000.0) : rangeMin.toJsonValue() },
     363          40 :                 { QLatin1String("max"),
     364             :                     #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
     365         120 :                     (rangeMax.typeId() == QMetaType::Int) ?
     366             :                     #else
     367         420 :                     (rangeMax.type() == QVariant::Int) ?
     368             :                     #endif
     369         180 :                     QJsonValue(rangeMax.toInt()/1000.0) : rangeMax.toJsonValue() },
     370         360 :             });
     371             :         }
     372         828 :         std::cout << QJsonDocument(jsonObject).toJson().toStdString();
     373         414 :     }   break;
     374         414 :     case OutputFormat::Text:
     375         598 :         std::cout << qUtf8Printable(tr("Mode:   %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
     376             :             .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
     377         920 :         std::cout << qUtf8Printable(tr("Value:  %1 %2\n").arg(reading.value,0,'f').arg(units));
     378         598 :         std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
     379             :             .arg((quint8)reading.status,2,16,QLatin1Char('0')));
     380         598 :         std::cout << qUtf8Printable(tr("Range:  %1 (0x%2)\n").arg(range)
     381             :             .arg((quint8)reading.range.voltageRange,2,16,QLatin1Char('0')));
     382         414 :         break;
     383             :     }
     384             : 
     385        1242 :     if ((samplesToGo > 0) && (--samplesToGo == 0)) {
     386           0 :         if (device) disconnect(); // Will exit the application once disconnected.
     387             :     }
     388        2250 : }

Generated by: LCOV version 1.14