LCOV - code coverage report
Current view: top level - src/app - metercommand.cpp (source / functions) Hit Total Coverage
Project: QtPokit Lines: 183 203 90.1 %
Version: Functions: 10 13 76.9 %

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

Generated by: LCOV version 1.14