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