LCOV - code coverage report
Current view: top level - src/cli - metercommand.cpp (source / functions) Coverage Total Hit
Project: Dokit Lines: 82.3 % 215 177
Version: Functions: 62.5 % 8 5

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2022-2026 Paul Colby <git@colby.id.au>
       2              : // SPDX-License-Identifier: LGPL-3.0-or-later
       3              : 
       4              : #include "metercommand.h"
       5              : #include "../stringliterals_p.h"
       6              : 
       7              : #include <qtpokit/pokitdevice.h>
       8              : 
       9              : #include <QJsonDocument>
      10              : #include <QJsonObject>
      11              : 
      12              : #include <iostream>
      13              : 
      14              : DOKIT_USE_STRINGLITERALS
      15              : 
      16              : /*!
      17              :  * \class MeterCommand
      18              :  *
      19              :  * The MeterCommand class implements the `meter` CLI command.
      20              :  */
      21              : 
      22              : /*!
      23              :  * Construct a new MeterCommand object with \a parent.
      24              :  */
      25        13146 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
      26         5936 : {
      27              : 
      28        13146 : }
      29              : 
      30         5459 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
      31         6784 : {
      32        23585 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      33         6784 :         u"mode"_s,
      34        21253 :     };
      35         6784 : }
      36              : 
      37         2678 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
      38         3328 : {
      39        15366 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      40         3328 :         u"interval"_s,
      41         3328 :         u"range"_s,
      42         3328 :         u"samples"_s,
      43        15782 :     };
      44         3328 : }
      45              : 
      46              : /*!
      47              :  * \copybrief DeviceCommand::processOptions
      48              :  *
      49              :  * This implementation extends DeviceCommand::processOptions to process additional CLI options
      50              :  * supported (or required) by this command.
      51              :  */
      52         2575 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
      53         3200 : {
      54         5775 :     QStringList errors = DeviceCommand::processOptions(parser);
      55         5775 :     if (!errors.isEmpty()) {
      56          128 :         return errors;
      57          128 :     }
      58              : 
      59              :     // Parse the (required) mode option.
      60         8016 :     if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
      61         9696 :         mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
      62          231 :         settings.mode = MultimeterService::Mode::AcVoltage;
      63          231 :         minRangeFunc = minVoltageRange;
      64         9292 :     } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
      65         2541 :         settings.mode = MultimeterService::Mode::DcVoltage;
      66         2541 :         minRangeFunc = minVoltageRange;
      67         4848 :     } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
      68          231 :         settings.mode = MultimeterService::Mode::AcCurrent;
      69          231 :         minRangeFunc = minCurrentRange;
      70         4444 :     } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
      71          462 :         settings.mode = MultimeterService::Mode::DcCurrent;
      72          462 :         minRangeFunc = minCurrentRange;
      73         2709 :     } else if (mode.startsWith(u"res"_s)) {
      74          462 :         settings.mode = MultimeterService::Mode::Resistance;
      75          462 :         minRangeFunc = minResistanceRange;
      76         2107 :     } else if (mode.startsWith(u"dio"_s)) {
      77          462 :         settings.mode = MultimeterService::Mode::Diode;
      78          462 :         minRangeFunc = nullptr;
      79         1505 :     } else if (mode.startsWith(u"cont"_s)) {
      80          231 :         settings.mode = MultimeterService::Mode::Continuity;
      81          231 :         minRangeFunc = nullptr;
      82         1204 :     } else if (mode.startsWith(u"temp"_s)) {
      83          231 :         settings.mode = MultimeterService::Mode::Temperature;
      84          231 :         minRangeFunc = nullptr;
      85          903 :     } else if (mode.startsWith(u"cap"_s)) {
      86          462 :         settings.mode = MultimeterService::Mode::Capacitance;
      87          462 :         minRangeFunc = minCapacitanceRange;
      88          256 :     } else {
      89          361 :         errors.append(tr("Unknown meter mode: %1").arg(parser.value(u"mode"_s)));
      90          128 :         return errors;
      91          920 :     }
      92              : 
      93              :     // Parse the interval option.
      94         6923 :     if (parser.isSet(u"interval"_s)) {
      95         1550 :         const QString value = parser.value(u"interval"_s);
      96         1155 :         const quint32 interval = parseNumber<std::milli>(value, u"s"_s, 500);
      97         1155 :         if (interval == 0) {
      98          602 :             errors.append(tr("Invalid interval value: %1").arg(value));
      99          384 :         } else {
     100          693 :             settings.updateInterval = interval;
     101          384 :         }
     102          805 :     }
     103              : 
     104              :     // Parse the range option.
     105         5313 :     rangeOptionValue = 0; // Default to auto.
     106         6923 :     if (parser.isSet(u"range"_s)) {
     107         3410 :         const QString value = parser.value(u"range"_s);
     108         3311 :         if (value.trimmed().compare(u"auto"_s, Qt::CaseInsensitive) != 0) {
     109         1617 :             switch (settings.mode) {
     110          565 :             case MultimeterService::Mode::DcVoltage:
     111          384 :             case MultimeterService::Mode::AcVoltage:
     112          693 :                 rangeOptionValue =  parseNumber<std::milli>(value, u"V"_s, 50); // mV.
     113          693 :                 break;
     114          334 :             case MultimeterService::Mode::DcCurrent:
     115          256 :             case MultimeterService::Mode::AcCurrent:
     116          462 :                 rangeOptionValue = parseNumber<std::milli>(value, u"A"_s, 5); // mA.
     117          462 :                 break;
     118            0 :             case MultimeterService::Mode::Resistance:
     119            0 :                 rangeOptionValue = parseNumber<std::ratio<1>>(value, u"ohms"_s);
     120            0 :                 break;
     121          231 :             case MultimeterService::Mode::Capacitance:
     122          231 :                 rangeOptionValue = parseNumber<std::nano>(value, u"F"_s, (quint32)500); // pF.
     123          231 :                 break;
     124          231 :             default:
     125          459 :                 qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
     126          896 :             }
     127         1617 :             if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
     128          301 :                 errors.append(tr("Invalid range value: %1").arg(value));
     129          128 :             }
     130          896 :         }
     131         1771 :     }
     132              : 
     133              :     // Parse the samples option.
     134         6923 :     if (parser.isSet(u"samples"_s)) {
     135          930 :         const QString value = parser.value(u"samples"_s);
     136          693 :         const quint32 samples = parseNumber<std::ratio<1>>(value, u"S"_s);
     137          693 :         if (samples == 0) {
     138          602 :             errors.append(tr("Invalid samples value: %1").arg(value));
     139          256 :         } else {
     140          231 :             samplesToGo = samples;
     141          128 :         }
     142          483 :     }
     143         2944 :     return errors;
     144         2944 : }
     145              : 
     146              : /*!
     147              :  * \copybrief DeviceCommand::getService
     148              :  *
     149              :  * This override returns a pointer to a MultimeterService object.
     150              :  */
     151            0 : AbstractPokitService * MeterCommand::getService()
     152            0 : {
     153            0 :     Q_ASSERT(device);
     154            0 :     if (!service) {
     155            0 :         service = device->multimeter();
     156            0 :         Q_ASSERT(service);
     157            0 :         connect(service, &MultimeterService::settingsWritten,
     158            0 :                 this, &MeterCommand::settingsWritten);
     159            0 :     }
     160            0 :     return service;
     161            0 : }
     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            0 : {
     170            0 :     DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
     171            0 :     settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
     172            0 :     const QString range = service->toString(settings.range, settings.mode);
     173            0 :     qCInfo(lc).noquote() << tr("Measuring %1, with range %2, every %L3ms.").arg(
     174            0 :         MultimeterService::toString(settings.mode),
     175            0 :         (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
     176            0 :     if (!service->enableReadingNotifications()) {
     177            0 :         qCCritical(lc).noquote() << tr("Failed to enable reading notifications");
     178            0 :         disconnect(EXIT_FAILURE);
     179            0 :         return;
     180            0 :     }
     181            0 :     service->setSettings(settings);
     182            0 : }
     183              : 
     184              : /*!
     185              :  * \var MeterCommand::minRangeFunc
     186              :  *
     187              :  * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
     188              :  * is assigned during the command line parsing, but is not invoked until after the device's services are discovered,
     189              :  * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
     190              :  * which enumerator list to be using.
     191              :  *
     192              :  * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
     193              :  *
     194              :  * \see processOptions
     195              :  * \see serviceDetailsDiscovered
     196              :  */
     197              : 
     198              : /*!
     199              :  * Invoked when the multimeter settings have been written, to begin reading the meter values.
     200              :  */
     201            0 : void MeterCommand::settingsWritten()
     202            0 : {
     203            0 :     qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
     204            0 :     connect(service, &MultimeterService::readingRead,
     205            0 :             this, &MeterCommand::outputReading);
     206            0 : }
     207              : 
     208              : /*!
     209              :  * Outputs meter \a reading in the selected output format.
     210              :  */
     211         7725 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
     212         4200 : {
     213         9450 :     QString status;
     214        11925 :     if (reading.status == MultimeterService::MeterStatus::Error) {
     215          954 :         status = u"Error"_s;
     216        10971 :     } else switch (reading.mode) {
     217          504 :     case MultimeterService::Mode::Idle:
     218          504 :         break;
     219         4044 :     case MultimeterService::Mode::DcVoltage:
     220          672 :     case MultimeterService::Mode::AcVoltage:
     221         1008 :     case MultimeterService::Mode::DcCurrent:
     222         1344 :     case MultimeterService::Mode::AcCurrent:
     223         1680 :     case MultimeterService::Mode::Resistance:
     224         2016 :     case MultimeterService::Mode::Capacitance:
     225         2016 :         status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
     226         6912 :             ? tr("Auto Range On") : tr("Auto Range Off");
     227         5724 :         break;
     228         1908 :     case MultimeterService::Mode::Continuity:
     229          672 :         status = (reading.status == MultimeterService::MeterStatus::Continuity)
     230         2304 :             ? tr("Continuity") : tr("No continuity");
     231         1908 :         break;
     232         1572 :     case MultimeterService::Mode::Temperature:
     233          336 :     case MultimeterService::Mode::ExternalTemperature:
     234          672 :     case MultimeterService::Mode::Diode:
     235         1236 :         status = tr("Ok");
     236         1908 :         break;
     237         3864 :     }
     238              : 
     239        11925 :     QString unit;
     240        11925 :     switch (reading.mode) {
     241          840 :     case MultimeterService::Mode::Idle:        break;
     242         1374 :     case MultimeterService::Mode::DcVoltage:   unit = u"Vdc"_s; break;
     243         1374 :     case MultimeterService::Mode::AcVoltage:   unit = u"Vac"_s; break;
     244         1374 :     case MultimeterService::Mode::DcCurrent:   unit = u"Adc"_s; break;
     245         1374 :     case MultimeterService::Mode::AcCurrent:   unit = u"Aac"_s; break;
     246         1374 :     case MultimeterService::Mode::Resistance:  unit = QString::fromUtf8("Ω"); break;
     247          336 :     case MultimeterService::Mode::Diode:       break;
     248          672 :     case MultimeterService::Mode::Continuity:  break;
     249         1374 :     case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
     250         1374 :     case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F");  break;
     251            0 :     case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
     252         4200 :     }
     253              : 
     254        14400 :     const QString range = service->toString(reading.range, reading.mode);
     255              : 
     256        11925 :     switch (format) {
     257         1400 :     case OutputFormat::Csv:
     258         6201 :         for (; showCsvHeader; showCsvHeader = false) {
     259         3206 :             std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
     260          784 :         }
     261         9800 :         std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
     262         1400 :             .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
     263         1400 :             .arg(reading.value, 0, 'f').arg(unit, status, range)
     264         1400 :             );
     265         3975 :         break;
     266         3975 :     case OutputFormat::Json: {
     267         1400 :         QJsonObject object{
     268         4575 :             { u"status"_s, status },
     269         6175 :             { u"value"_s, qIsInf(reading.value) ?
     270         4050 :                 QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
     271         6925 :             { u"mode"_s,   MultimeterService::toString(reading.mode) },
     272        15475 :         };
     273         3975 :         if (!unit.isNull()) {
     274         4158 :             object.insert(u"unit"_s, unit);
     275          784 :         }
     276         3975 :         if (!range.isNull()) {
     277         3267 :             object.insert(u"range"_s, range);
     278          616 :         }
     279         6550 :         std::cout << QJsonDocument(object).toJson().toStdString();
     280         3975 :     }   break;
     281         3975 :     case OutputFormat::Text:
     282         8225 :         std::cout << qUtf8Printable(tr("Mode:   %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
     283         1400 :             .arg((quint8)reading.mode,2,16,'0'_L1));
     284         8800 :         std::cout << qUtf8Printable(tr("Value:  %1 %2\n").arg(reading.value,0,'f').arg(unit));
     285         7475 :         std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
     286         1400 :             .arg((quint8)reading.status,2,16,'0'_L1));
     287         6825 :         std::cout << qUtf8Printable(tr("Range:  %1 (0x%2)\n").arg(range)
     288         1400 :             .arg((quint8)reading.range,2,16,'0'_L1));
     289         3975 :         break;
     290         4200 :     }
     291              : 
     292        11925 :     if ((samplesToGo > 0) && (--samplesToGo == 0)) {
     293            0 :         if (device) disconnect(); // Will exit the application once disconnected.
     294            0 :     }
     295        17500 : }
        

Generated by: LCOV version 2.5-0