LCOV - code coverage report
Current view: top level - src/cli - metercommand.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 121 145 83.4 %
Version: Functions: 6 9 66.7 %

          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        1260 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
      23             : {
      24             : 
      25        1260 : }
      26             : 
      27         954 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
      28             : {
      29        3180 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      30             :         QLatin1String("mode"),
      31        2809 :     };
      32             : }
      33             : 
      34         468 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
      35             : {
      36        2496 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      37             :         QLatin1String("interval"),
      38             :         QLatin1String("range"),
      39             :         QLatin1String("samples"),
      40        2314 :     };
      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         450 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
      50             : {
      51         450 :     QStringList errors = DeviceCommand::processOptions(parser);
      52         450 :     if (!errors.isEmpty()) {
      53             :         return errors;
      54             :     }
      55             : 
      56             :     // Parse the (required) mode option.
      57         864 :     const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
      58         432 :     if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
      59          18 :         settings.mode = MultimeterService::Mode::AcVoltage;
      60          18 :         minRangeFunc = minVoltageRange;
      61         414 :     } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
      62         198 :         settings.mode = MultimeterService::Mode::DcVoltage;
      63         198 :         minRangeFunc = minVoltageRange;
      64         216 :     } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
      65          18 :         settings.mode = MultimeterService::Mode::AcCurrent;
      66          18 :         minRangeFunc = minCurrentRange;
      67         198 :     } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
      68          36 :         settings.mode = MultimeterService::Mode::DcCurrent;
      69          36 :         minRangeFunc = minCurrentRange;
      70         162 :     } else if (mode.startsWith(QLatin1String("res"))) {
      71          36 :         settings.mode = MultimeterService::Mode::Resistance;
      72          36 :         minRangeFunc = minResistanceRange;
      73         126 :     } else if (mode.startsWith(QLatin1String("dio"))) {
      74          36 :         settings.mode = MultimeterService::Mode::Diode;
      75          36 :         minRangeFunc = nullptr;
      76          90 :     } else if (mode.startsWith(QLatin1String("cont"))) {
      77          18 :         settings.mode = MultimeterService::Mode::Continuity;
      78          18 :         minRangeFunc = nullptr;
      79          72 :     } else if (mode.startsWith(QLatin1String("temp"))) {
      80          18 :         settings.mode = MultimeterService::Mode::Temperature;
      81          18 :         minRangeFunc = nullptr;
      82          54 :     } else if (mode.startsWith(QLatin1String("cap"))) {
      83          36 :         settings.mode = MultimeterService::Mode::Capacitance;
      84          36 :         minRangeFunc = minCapacitanceRange;
      85             :     } else {
      86          36 :         errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
      87          18 :         return errors;
      88             :     }
      89             : 
      90             :     // Parse the interval option.
      91         506 :     if (parser.isSet(QLatin1String("interval"))) {
      92         150 :         const QString value = parser.value(QLatin1String("interval"));
      93          90 :         const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
      94          90 :         if (interval == 0) {
      95          44 :             errors.append(tr("Invalid interval value: %1").arg(value));
      96             :         } else {
      97          54 :             settings.updateInterval = interval;
      98             :         }
      99          70 :     }
     100             : 
     101             :     // Parse the range option.
     102         414 :     rangeOptionValue = 0; // Default to auto.
     103         506 :     if (parser.isSet(QLatin1String("range"))) {
     104         330 :         const QString value = parser.value(QLatin1String("range"));
     105         198 :         if (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) != 0) {
     106         126 :             switch (settings.mode) {
     107             :             case MultimeterService::Mode::DcVoltage:
     108             :             case MultimeterService::Mode::AcVoltage:
     109          54 :                 rangeOptionValue =  parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
     110          54 :                 break;
     111             :             case MultimeterService::Mode::DcCurrent:
     112             :             case MultimeterService::Mode::AcCurrent:
     113          36 :                 rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
     114          36 :                 break;
     115             :             case MultimeterService::Mode::Resistance:
     116           0 :                 rangeOptionValue = parseNumber<std::ratio<1>>(value, QLatin1String("ohms"));
     117           0 :                 break;
     118             :             case MultimeterService::Mode::Capacitance:
     119          18 :                 rangeOptionValue = parseNumber<std::nano>(value, QLatin1String("F"), 500); // pF.
     120          18 :                 break;
     121          18 :             default:
     122          40 :                 qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
     123             :             }
     124         126 :             if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
     125          22 :                 errors.append(tr("Invalid range value: %1").arg(value));
     126             :             }
     127             :         }
     128         154 :     }
     129             : 
     130             :     // Parse the samples option.
     131         506 :     if (parser.isSet(QLatin1String("samples"))) {
     132          90 :         const QString value = parser.value(QLatin1String("samples"));
     133          54 :         const quint32 samples = parseNumber<std::ratio<1>>(value, QLatin1String("S"));
     134          54 :         if (samples == 0) {
     135          44 :             errors.append(tr("Invalid samples value: %1").arg(value));
     136             :         } else {
     137          18 :             samplesToGo = samples;
     138             :         }
     139          42 :     }
     140             :     return errors;
     141         336 : }
     142             : 
     143             : /*!
     144             :  * \copybrief DeviceCommand::getService
     145             :  *
     146             :  * This override returns a pointer to a MultimeterService object.
     147             :  */
     148           0 : AbstractPokitService * MeterCommand::getService()
     149             : {
     150             :     Q_ASSERT(device);
     151           0 :     if (!service) {
     152           0 :         service = device->multimeter();
     153             :         Q_ASSERT(service);
     154           0 :         connect(service, &MultimeterService::settingsWritten,
     155             :                 this, &MeterCommand::settingsWritten);
     156             :     }
     157           0 :     return service;
     158             : }
     159             : 
     160             : /*!
     161             :  * \copybrief DeviceCommand::serviceDetailsDiscovered
     162             :  *
     163             :  * This override fetches the current device's status, and outputs it in the selected format.
     164             :  */
     165           0 : void MeterCommand::serviceDetailsDiscovered()
     166             : {
     167           0 :     DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
     168           0 :     settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
     169           0 :     const QString range = service->toString(settings.range, settings.mode);
     170           0 :     qCInfo(lc).noquote() << tr("Measuring %1, with range %2, every %L3ms.").arg(
     171           0 :         MultimeterService::toString(settings.mode),
     172           0 :         (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
     173           0 :     service->setSettings(settings);
     174           0 : }
     175             : 
     176             : /*!
     177             :  * \var MeterCommand::minRangeFunc
     178             :  *
     179             :  * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
     180             :  * is assigned during the command line parsing, but is not invoked until after the device's services are discovere,
     181             :  * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
     182             :  * which enumerator list to be using.
     183             :  *
     184             :  * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
     185             :  *
     186             :  * \see processOptions
     187             :  * \see serviceDetailsDiscovered
     188             :  */
     189             : 
     190             : /*!
     191             :  * Invoked when the multimeter settings have been written, to begin reading the meter values.
     192             :  */
     193           0 : void MeterCommand::settingsWritten()
     194             : {
     195           0 :     qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
     196           0 :     connect(service, &MultimeterService::readingRead,
     197             :             this, &MeterCommand::outputReading);
     198           0 :     service->enableReadingNotifications();
     199           0 : }
     200             : 
     201             : /*!
     202             :  * Outputs meter \a reading in the selected ouput format.
     203             :  */
     204        1350 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
     205             : {
     206         300 :     QString status;
     207        1350 :     if (reading.status == MultimeterService::MeterStatus::Error) {
     208         108 :         status = QLatin1String("Error");
     209        1242 :     } else switch (reading.mode) {
     210             :     case MultimeterService::Mode::Idle:
     211             :         break;
     212         648 :     case MultimeterService::Mode::DcVoltage:
     213             :     case MultimeterService::Mode::AcVoltage:
     214             :     case MultimeterService::Mode::DcCurrent:
     215             :     case MultimeterService::Mode::AcCurrent:
     216             :     case MultimeterService::Mode::Resistance:
     217             :     case MultimeterService::Mode::Capacitance:
     218             :         status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
     219        1152 :             ? tr("Auto Range On") : tr("Auto Range Off");
     220         648 :         break;
     221         216 :     case MultimeterService::Mode::Continuity:
     222             :         status = (reading.status == MultimeterService::MeterStatus::Continuity)
     223         384 :             ? tr("Continuity") : tr("No continuity");
     224         216 :         break;
     225         216 :     case MultimeterService::Mode::Temperature:
     226             :     case MultimeterService::Mode::ExternalTemperature:
     227             :     case MultimeterService::Mode::Diode:
     228         216 :         status = tr("Ok");
     229         216 :         break;
     230             :     }
     231             : 
     232        1350 :     QString unit;
     233        1350 :     switch (reading.mode) {
     234             :     case MultimeterService::Mode::Idle:        break;
     235         108 :     case MultimeterService::Mode::DcVoltage:   unit = QLatin1String("Vdc"); break;
     236         108 :     case MultimeterService::Mode::AcVoltage:   unit = QLatin1String("Vac"); break;
     237         108 :     case MultimeterService::Mode::DcCurrent:   unit = QLatin1String("Adc"); break;
     238         108 :     case MultimeterService::Mode::AcCurrent:   unit = QLatin1String("Aac"); break;
     239         132 :     case MultimeterService::Mode::Resistance:  unit = QString::fromUtf8("Ω"); break;
     240             :     case MultimeterService::Mode::Diode:       break;
     241             :     case MultimeterService::Mode::Continuity:  break;
     242         132 :     case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
     243         132 :     case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F");  break;
     244           0 :     case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
     245             :     }
     246             : 
     247        2400 :     const QString range = service->toString(reading.range, reading.mode);
     248             : 
     249        1350 :     switch (format) {
     250             :     case OutputFormat::Csv:
     251         702 :         for (; showCsvHeader; showCsvHeader = false) {
     252         308 :             std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
     253             :         }
     254        1000 :         std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
     255             :             .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
     256             :             .arg(reading.value, 0, 'f').arg(unit, status, range)
     257             :             );
     258         450 :         break;
     259         150 :     case OutputFormat::Json: {
     260             :         QJsonObject object{
     261         100 :             { QLatin1String("status"), status },
     262         450 :             { QLatin1String("value"), qIsInf(reading.value) ?
     263         500 :                 QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
     264         900 :             { QLatin1String("mode"),   MultimeterService::toString(reading.mode) },
     265        3025 :         };
     266         450 :         if (!unit.isNull()) {
     267         378 :             object.insert(QLatin1String("unit"), unit);
     268             :         }
     269         450 :         if (!range.isNull()) {
     270         297 :             object.insert(QLatin1String("range"), range);
     271             :         }
     272         900 :         std::cout << QJsonDocument(object).toJson().toStdString();
     273         450 :     }   break;
     274         450 :     case OutputFormat::Text:
     275         650 :         std::cout << qUtf8Printable(tr("Mode:   %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
     276             :             .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
     277        1000 :         std::cout << qUtf8Printable(tr("Value:  %1 %2\n").arg(reading.value,0,'f').arg(unit));
     278         650 :         std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
     279             :             .arg((quint8)reading.status,2,16,QLatin1Char('0')));
     280         650 :         std::cout << qUtf8Printable(tr("Range:  %1 (0x%2)\n").arg(range)
     281             :             .arg((quint8)reading.range,2,16,QLatin1Char('0')));
     282         450 :         break;
     283             :     }
     284             : 
     285        1350 :     if ((samplesToGo > 0) && (--samplesToGo == 0)) {
     286           0 :         if (device) disconnect(); // Will exit the application once disconnected.
     287             :     }
     288        2250 : }

Generated by: LCOV version 1.14