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 : }