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

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2022-2025 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         4690 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
      23         2380 : {
      24              : 
      25         5530 : }
      26              : 
      27         2385 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
      28         2756 : {
      29        11713 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      30         2756 :         QLatin1String("mode"),
      31         8639 :     };
      32         4028 : }
      33              : 
      34         1170 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
      35         1352 : {
      36         7878 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      37         1352 :         QLatin1String("interval"),
      38         1352 :         QLatin1String("range"),
      39         1352 :         QLatin1String("samples"),
      40         6578 :     };
      41         1976 : }
      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         1125 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
      50         1300 : {
      51         2425 :     QStringList errors = DeviceCommand::processOptions(parser);
      52         2425 :     if (!errors.isEmpty()) {
      53           52 :         return errors;
      54           52 :     }
      55              : 
      56              :     // Parse the (required) mode option.
      57         3408 :     if (const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
      58         2544 :         mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
      59           97 :         settings.mode = MultimeterService::Mode::AcVoltage;
      60           97 :         minRangeFunc = minVoltageRange;
      61         2438 :     } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
      62         1067 :         settings.mode = MultimeterService::Mode::DcVoltage;
      63         1067 :         minRangeFunc = minVoltageRange;
      64         1272 :     } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
      65           97 :         settings.mode = MultimeterService::Mode::AcCurrent;
      66           97 :         minRangeFunc = minCurrentRange;
      67         1166 :     } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
      68          194 :         settings.mode = MultimeterService::Mode::DcCurrent;
      69          194 :         minRangeFunc = minCurrentRange;
      70          873 :     } else if (mode.startsWith(QLatin1String("res"))) {
      71          194 :         settings.mode = MultimeterService::Mode::Resistance;
      72          194 :         minRangeFunc = minResistanceRange;
      73          679 :     } else if (mode.startsWith(QLatin1String("dio"))) {
      74          194 :         settings.mode = MultimeterService::Mode::Diode;
      75          194 :         minRangeFunc = nullptr;
      76          485 :     } else if (mode.startsWith(QLatin1String("cont"))) {
      77           97 :         settings.mode = MultimeterService::Mode::Continuity;
      78           97 :         minRangeFunc = nullptr;
      79          388 :     } else if (mode.startsWith(QLatin1String("temp"))) {
      80           97 :         settings.mode = MultimeterService::Mode::Temperature;
      81           97 :         minRangeFunc = nullptr;
      82          291 :     } else if (mode.startsWith(QLatin1String("cap"))) {
      83          194 :         settings.mode = MultimeterService::Mode::Capacitance;
      84          194 :         minRangeFunc = minCapacitanceRange;
      85          104 :     } else {
      86          148 :         errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
      87           52 :         return errors;
      88          628 :     }
      89              : 
      90              :     // Parse the interval option.
      91         2714 :     if (parser.isSet(QLatin1String("interval"))) {
      92          590 :         const QString value = parser.value(QLatin1String("interval"));
      93          485 :         const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
      94          485 :         if (interval == 0) {
      95          236 :             errors.append(tr("Invalid interval value: %1").arg(value));
      96          156 :         } else {
      97          291 :             settings.updateInterval = interval;
      98          156 :         }
      99          380 :     }
     100              : 
     101              :     // Parse the range option.
     102         2231 :     rangeOptionValue = 0; // Default to auto.
     103         2714 :     if (parser.isSet(QLatin1String("range"))) {
     104         1298 :         const QString value = parser.value(QLatin1String("range"));
     105         1067 :         if (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) != 0) {
     106          679 :             switch (settings.mode) {
     107          104 :             case MultimeterService::Mode::DcVoltage:
     108          156 :             case MultimeterService::Mode::AcVoltage:
     109          291 :                 rangeOptionValue =  parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
     110          291 :                 break;
     111           52 :             case MultimeterService::Mode::DcCurrent:
     112          104 :             case MultimeterService::Mode::AcCurrent:
     113          194 :                 rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
     114          194 :                 break;
     115            0 :             case MultimeterService::Mode::Resistance:
     116            0 :                 rangeOptionValue = parseNumber<std::ratio<1>>(value, QLatin1String("ohms"));
     117            0 :                 break;
     118           52 :             case MultimeterService::Mode::Capacitance:
     119           97 :                 rangeOptionValue = parseNumber<std::nano>(value, QLatin1String("F"), 500); // pF.
     120           97 :                 break;
     121           97 :             default:
     122          176 :                 qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
     123          364 :             }
     124          679 :             if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
     125          118 :                 errors.append(tr("Invalid range value: %1").arg(value));
     126           52 :             }
     127          364 :         }
     128          836 :     }
     129              : 
     130              :     // Parse the samples option.
     131         2714 :     if (parser.isSet(QLatin1String("samples"))) {
     132          354 :         const QString value = parser.value(QLatin1String("samples"));
     133          291 :         const quint32 samples = parseNumber<std::ratio<1>>(value, QLatin1String("S"));
     134          291 :         if (samples == 0) {
     135          236 :             errors.append(tr("Invalid samples value: %1").arg(value));
     136          104 :         } else {
     137           97 :             samplesToGo = samples;
     138           52 :         }
     139          228 :     }
     140         1196 :     return errors;
     141         1196 : }
     142              : 
     143              : /*!
     144              :  * \copybrief DeviceCommand::getService
     145              :  *
     146              :  * This override returns a pointer to a MultimeterService object.
     147              :  */
     148            0 : AbstractPokitService * MeterCommand::getService()
     149            0 : {
     150            0 :     Q_ASSERT(device);
     151            0 :     if (!service) {
     152            0 :         service = device->multimeter();
     153            0 :         Q_ASSERT(service);
     154            0 :         connect(service, &MultimeterService::settingsWritten,
     155            0 :                 this, &MeterCommand::settingsWritten);
     156            0 :     }
     157            0 :     return service;
     158            0 : }
     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            0 : {
     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            0 : {
     195            0 :     qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
     196            0 :     connect(service, &MultimeterService::readingRead,
     197            0 :             this, &MeterCommand::outputReading);
     198            0 :     service->enableReadingNotifications();
     199            0 : }
     200              : 
     201              : /*!
     202              :  * Outputs meter \a reading in the selected ouput format.
     203              :  */
     204         3375 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
     205         1650 : {
     206         3225 :     QString status;
     207         5025 :     if (reading.status == MultimeterService::MeterStatus::Error) {
     208          402 :         status = QLatin1String("Error");
     209         4623 :     } else switch (reading.mode) {
     210          198 :     case MultimeterService::Mode::Idle:
     211          198 :         break;
     212         1752 :     case MultimeterService::Mode::DcVoltage:
     213          264 :     case MultimeterService::Mode::AcVoltage:
     214          396 :     case MultimeterService::Mode::DcCurrent:
     215          528 :     case MultimeterService::Mode::AcCurrent:
     216          660 :     case MultimeterService::Mode::Resistance:
     217          792 :     case MultimeterService::Mode::Capacitance:
     218          792 :         status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
     219         3276 :             ? tr("Auto Range On") : tr("Auto Range Off");
     220         2412 :         break;
     221          804 :     case MultimeterService::Mode::Continuity:
     222          264 :         status = (reading.status == MultimeterService::MeterStatus::Continuity)
     223         1044 :             ? tr("Continuity") : tr("No continuity");
     224          804 :         break;
     225          672 :     case MultimeterService::Mode::Temperature:
     226          132 :     case MultimeterService::Mode::ExternalTemperature:
     227          264 :     case MultimeterService::Mode::Diode:
     228          552 :         status = tr("Ok");
     229          804 :         break;
     230         1518 :     }
     231              : 
     232         5025 :     QString unit;
     233         5025 :     switch (reading.mode) {
     234          330 :     case MultimeterService::Mode::Idle:        break;
     235          402 :     case MultimeterService::Mode::DcVoltage:   unit = QLatin1String("Vdc"); break;
     236          402 :     case MultimeterService::Mode::AcVoltage:   unit = QLatin1String("Vac"); break;
     237          402 :     case MultimeterService::Mode::DcCurrent:   unit = QLatin1String("Adc"); break;
     238          402 :     case MultimeterService::Mode::AcCurrent:   unit = QLatin1String("Aac"); break;
     239          528 :     case MultimeterService::Mode::Resistance:  unit = QString::fromUtf8("Ω"); break;
     240          132 :     case MultimeterService::Mode::Diode:       break;
     241          264 :     case MultimeterService::Mode::Continuity:  break;
     242          528 :     case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
     243          528 :     case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F");  break;
     244            0 :     case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
     245         1650 :     }
     246              : 
     247         6825 :     const QString range = service->toString(reading.range, reading.mode);
     248              : 
     249         5025 :     switch (format) {
     250          550 :     case OutputFormat::Csv:
     251         2613 :         for (; showCsvHeader; showCsvHeader = false) {
     252         1232 :             std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
     253          308 :         }
     254         3850 :         std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
     255          550 :             .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
     256          550 :             .arg(reading.value, 0, 'f').arg(unit, status, range)
     257          550 :             );
     258         1675 :         break;
     259         1675 :     case OutputFormat::Json: {
     260          550 :         QJsonObject object{
     261         1075 :             { QLatin1String("status"), status },
     262         1675 :             { QLatin1String("value"), qIsInf(reading.value) ?
     263         1725 :                 QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
     264         2800 :             { QLatin1String("mode"),   MultimeterService::toString(reading.mode) },
     265         6025 :         };
     266         1675 :         if (!unit.isNull()) {
     267         1218 :             object.insert(QLatin1String("unit"), unit);
     268          308 :         }
     269         1675 :         if (!range.isNull()) {
     270          957 :             object.insert(QLatin1String("range"), range);
     271          242 :         }
     272         2800 :         std::cout << QJsonDocument(object).toJson().toStdString();
     273         1675 :     }   break;
     274         1675 :     case OutputFormat::Text:
     275         2800 :         std::cout << qUtf8Printable(tr("Mode:   %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
     276          550 :             .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
     277         3325 :         std::cout << qUtf8Printable(tr("Value:  %1 %2\n").arg(reading.value,0,'f').arg(unit));
     278         2725 :         std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
     279          550 :             .arg((quint8)reading.status,2,16,QLatin1Char('0')));
     280         2650 :         std::cout << qUtf8Printable(tr("Range:  %1 (0x%2)\n").arg(range)
     281          550 :             .arg((quint8)reading.range,2,16,QLatin1Char('0')));
     282         1675 :         break;
     283         1650 :     }
     284              : 
     285         5025 :     if ((samplesToGo > 0) && (--samplesToGo == 0)) {
     286            0 :         if (device) disconnect(); // Will exit the application once disconnected.
     287            0 :     }
     288         9600 : }
        

Generated by: LCOV version 2.3-1