LCOV - code coverage report
Current view: top level - src/cli - loggerstartcommand.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 91 111 82.0 %
Version: Functions: 9 11 81.8 %

          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 "loggerstartcommand.h"
       5             : 
       6             : #include <qtpokit/pokitdevice.h>
       7             : 
       8             : #include <QDateTime>
       9             : #include <QJsonDocument>
      10             : #include <QJsonObject>
      11             : 
      12             : #include <iostream>
      13             : 
      14             : /*!
      15             :  * \class LoggerStartCommand
      16             :  *
      17             :  * The LoggerStartCommand class implements the `logger` CLI command.
      18             :  */
      19             : 
      20             : /*!
      21             :  * Construct a new LoggerStartCommand object with \a parent.
      22             :  */
      23         486 : LoggerStartCommand::LoggerStartCommand(QObject * const parent) : DeviceCommand(parent)
      24             : {
      25             : 
      26         486 : }
      27             : 
      28         810 : QStringList LoggerStartCommand::requiredOptions(const QCommandLineParser &parser) const
      29             : {
      30        2700 :     return DeviceCommand::requiredOptions(parser) + QStringList{
      31             :         QLatin1String("mode"),
      32        2385 :     };
      33             : }
      34             : 
      35         396 : QStringList LoggerStartCommand::supportedOptions(const QCommandLineParser &parser) const
      36             : {
      37        2112 :     return DeviceCommand::supportedOptions(parser) + QStringList{
      38             :         QLatin1String("interval"),
      39             :         QLatin1String("range"),
      40             :         QLatin1String("timestamp"),
      41        1958 :     };
      42           0 : }
      43             : 
      44             : /*!
      45             :  * \copybrief DeviceCommand::processOptions
      46             :  *
      47             :  * This implementation extends DeviceCommand::processOptions to process additional CLI options
      48             :  * supported (or required) by this command.
      49             :  */
      50         378 : QStringList LoggerStartCommand::processOptions(const QCommandLineParser &parser)
      51             : {
      52         378 :     QStringList errors = DeviceCommand::processOptions(parser);
      53         378 :     if (!errors.isEmpty()) {
      54             :         return errors;
      55             :     }
      56             : 
      57             :     // Parse the (required) mode option.
      58         684 :     const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
      59         342 :     if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
      60          18 :         settings.mode = DataLoggerService::Mode::AcVoltage;
      61         324 :     } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
      62         234 :         settings.mode = DataLoggerService::Mode::DcVoltage;
      63          90 :     } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
      64          18 :         settings.mode = DataLoggerService::Mode::AcCurrent;
      65          72 :     } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
      66          18 :         settings.mode = DataLoggerService::Mode::DcCurrent;
      67          54 :     } else if (mode.startsWith(QLatin1String("temp"))) {
      68          36 :         settings.mode = DataLoggerService::Mode::Temperature;
      69             :     } else {
      70          36 :         errors.append(tr("Unknown logger mode: %1").arg(parser.value(QLatin1String("mode"))));
      71          18 :         return errors;
      72             :     }
      73             : 
      74             :     // Parse the range option.
      75         396 :     if (parser.isSet(QLatin1String("range"))) {
      76         442 :         const QString value = parser.value(QLatin1String("range"));
      77          68 :         QString unit; quint32 sensibleMinimum = 0;
      78         306 :         switch (settings.mode) {
      79             :         case DataLoggerService::Mode::Idle:
      80             :             Q_ASSERT(false); // Not possible, since the mode parsing above never allows Idle.
      81             :             break;
      82             :         case DataLoggerService::Mode::DcVoltage:
      83             :         case DataLoggerService::Mode::AcVoltage:
      84         234 :             unit = QLatin1String("V");
      85             :             sensibleMinimum = 50; // mV.
      86         234 :             break;
      87             :         case DataLoggerService::Mode::DcCurrent:
      88             :         case DataLoggerService::Mode::AcCurrent:
      89          36 :             unit = QLatin1String("A");
      90             :             sensibleMinimum = 5; // mA.
      91          36 :             break;
      92          36 :         case DataLoggerService::Mode::Temperature:
      93             :         default:
      94          80 :             qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
      95             :         }
      96         306 :         if (unit.isEmpty()) {
      97             :             // The only mode that does not take a range, and thus we don't assign a unit above.
      98             :             Q_ASSERT(settings.mode == DataLoggerService::Mode::Temperature);
      99             :         } else {
     100         270 :             const quint32 rangeMax = parseMilliValue(value, unit, sensibleMinimum);
     101         270 :             if (rangeMax == 0) {
     102          27 :                 errors.append(tr("Invalid range value: %1").arg(value));
     103             :             } else {
     104         252 :                 settings.range = lowestRange(settings.mode, rangeMax);
     105             :             }
     106             :         }
     107         256 :     } else if (settings.mode != DataLoggerService::Mode::Temperature) {
     108          40 :         errors.append(tr("Missing required option for logger mode '%1': range")
     109          36 :             .arg(parser.value(QLatin1String("mode"))));
     110             :     }
     111             : 
     112             :     // Parse the interval option.
     113         396 :     if (parser.isSet(QLatin1String("interval"))) {
     114         150 :         const QString value = parser.value(QLatin1String("interval"));
     115          90 :         const quint32 interval = parseMilliValue(value, QLatin1String("s"), 500);
     116          90 :         if (interval == 0) {
     117          44 :             errors.append(tr("Invalid interval value: %1").arg(value));
     118             :         } else {
     119          54 :             settings.updateInterval = interval;
     120             :         }
     121          70 :     }
     122             : 
     123             :     // Parse the timestamp option.
     124         324 :     settings.timestamp = (quint32)QDateTime::currentSecsSinceEpoch(); // Note, subject to Y2038 epochalypse.
     125         396 :     if (parser.isSet(QLatin1String("timestamp"))) {
     126          90 :         const QString value = parser.value(QLatin1String("timestamp"));
     127          90 :         QLocale locale; bool ok;
     128             :         static_assert(sizeof(uint) == sizeof(settings.timestamp), "QLocale has no toUint32().");
     129          70 :         const int timestamp = locale.toUInt(value, &ok);
     130          90 :         if (!ok) {
     131          44 :             errors.append(tr("Invalid timestamp value: %1").arg(value));
     132             :         } else {
     133          54 :             settings.timestamp = timestamp;
     134             :         }
     135          90 :     }
     136             :     return errors;
     137         266 : }
     138             : 
     139             : /*!
     140             :  * \copybrief DeviceCommand::getService
     141             :  *
     142             :  * This override returns a pointer to a DataLoggerService object.
     143             :  */
     144           0 : AbstractPokitService * LoggerStartCommand::getService()
     145             : {
     146             :     Q_ASSERT(device);
     147           0 :     if (!service) {
     148           0 :         service = device->dataLogger();
     149             :         Q_ASSERT(service);
     150           0 :         connect(service, &DataLoggerService::settingsWritten,
     151             :                 this, &LoggerStartCommand::settingsWritten);
     152             :     }
     153           0 :     return service;
     154             : }
     155             : 
     156             : /*!
     157             :  * \copybrief DeviceCommand::serviceDetailsDiscovered
     158             :  *
     159             :  * This override fetches the current device's status, and outputs it in the selected format.
     160             :  */
     161           0 : void LoggerStartCommand::serviceDetailsDiscovered()
     162             : {
     163           0 :     DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
     164           0 :     const QString range = DataLoggerService::toString(settings.range, settings.mode);
     165           0 :     qCInfo(lc).noquote() << tr("Logging %1, with range %2, every %L3ms.").arg(
     166           0 :         DataLoggerService::toString(settings.mode),
     167           0 :         (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
     168           0 :     service->setSettings(settings);
     169           0 : }
     170             : 
     171             : /*!
     172             :  * Returns the lowest \a mode range that can measure at least up to \a desired max, or AutoRange
     173             :  * if no such range is available.
     174             :  */
     175        1368 : DataLoggerService::Range LoggerStartCommand::lowestRange(
     176             :     const DataLoggerService::Mode mode, const quint32 desiredMax)
     177             : {
     178        1368 :     switch (mode) {
     179           0 :     case DataLoggerService::Mode::Idle:
     180           0 :         qCWarning(lc).noquote() << tr("Idle has no defined ranges.");
     181             :         Q_ASSERT(false); // Should never have been called with this Idle mode.
     182           0 :         break;
     183         828 :     case DataLoggerService::Mode::DcVoltage:
     184             :     case DataLoggerService::Mode::AcVoltage:
     185         828 :         return lowestVoltageRange(desiredMax);
     186         540 :     case DataLoggerService::Mode::DcCurrent:
     187             :     case DataLoggerService::Mode::AcCurrent:
     188         540 :         return lowestCurrentRange(desiredMax);
     189           0 :     default:
     190           0 :         qCWarning(lc).noquote() << tr("No defined ranges for mode %1.").arg((quint8)mode);
     191             :         Q_ASSERT(false); // Should never have been called with this invalid mode.
     192             :     }
     193           0 :     return DataLoggerService::Range();
     194             : }
     195             : 
     196             : #define DOKIT_CLI_IF_LESS_THAN_RETURN(value, label) \
     197             : if (value <=  DataLoggerService::maxValue(DataLoggerService::label).toUInt()) { \
     198             :     return DataLoggerService::label; \
     199             : }
     200             : 
     201             : /*!
     202             :  * Returns the lowest current range that can measure at least up to \a desired max, or AutoRange
     203             :  * if no such range is available.
     204             :  */
     205         792 : DataLoggerService::CurrentRange LoggerStartCommand::lowestCurrentRange(const quint32 desiredMax)
     206             : {
     207         792 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_0_to_10mA)
     208         630 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_10mA_to_30mA)
     209         468 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_30mA_to_150mA)
     210         288 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_150mA_to_300mA)
     211         126 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_300mA_to_3A)
     212             :     return DataLoggerService::CurrentRange::_300mA_to_3A; // Out of range, so go with the biggest.
     213             : }
     214             : 
     215             : /*!
     216             :  * Returns the lowest voltage range that can measure at least up to \a desired max, or AutoRange
     217             :  * if no such range is available.
     218             :  */
     219        1134 : DataLoggerService::VoltageRange LoggerStartCommand::lowestVoltageRange(const quint32 desiredMax)
     220             : {
     221        1134 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_0_to_300mV)
     222         972 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_300mV_to_2V)
     223         612 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_2V_to_6V)
     224         432 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_6V_to_12V)
     225         270 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_12V_to_30V)
     226         108 :     DOKIT_CLI_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_30V_to_60V)
     227             :     return DataLoggerService::VoltageRange::_30V_to_60V; // Out of range, so go with the biggest.
     228             : }
     229             : 
     230             : #undef DOKIT_CLI_IF_LESS_THAN_RETURN
     231             : 
     232             : /*!
     233             :  * Invoked when the data logger settings have been written.
     234             :  */
     235          54 : void LoggerStartCommand::settingsWritten()
     236             : {
     237          54 :     qCDebug(lc).noquote() << tr("Settings written; data logger has started.");
     238          54 :     switch (format) {
     239          18 :     case OutputFormat::Csv:
     240          22 :         std::cout << qUtf8Printable(tr("logger_start_result\nsuccess\n"));
     241          18 :         break;
     242             :     case OutputFormat::Json:
     243          26 :         std::cout << qUtf8Printable(QLatin1String("true\n"));
     244          18 :         break;
     245          18 :     case OutputFormat::Text:
     246          22 :         std::cout << qUtf8Printable(tr("Done.\n"));
     247          18 :         break;
     248             :     }
     249          54 :     if (device) disconnect(); // Will exit the application once disconnected.
     250          54 : }

Generated by: LCOV version 1.14