Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2024 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 1458 : LoggerStartCommand::LoggerStartCommand(QObject * const parent) : DeviceCommand(parent)
24 1188 : {
25 :
26 2214 : }
27 :
28 1710 : QStringList LoggerStartCommand::requiredOptions(const QCommandLineParser &parser) const
29 1980 : {
30 8370 : return DeviceCommand::requiredOptions(parser) + QStringList{
31 1980 : QLatin1String("mode"),
32 6255 : };
33 2970 : }
34 :
35 836 : QStringList LoggerStartCommand::supportedOptions(const QCommandLineParser &parser) const
36 968 : {
37 5588 : return DeviceCommand::supportedOptions(parser) + QStringList{
38 968 : QLatin1String("interval"),
39 968 : QLatin1String("range"), // May still be required by processOptions(), depending on the --mode option's value.
40 968 : QLatin1String("timestamp"),
41 4730 : };
42 1452 : }
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 798 : QStringList LoggerStartCommand::processOptions(const QCommandLineParser &parser)
51 924 : {
52 1722 : QStringList errors = DeviceCommand::processOptions(parser);
53 1722 : if (!errors.isEmpty()) {
54 88 : return errors;
55 88 : }
56 :
57 : // Parse the (required) mode option.
58 2280 : if (const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
59 1634 : mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
60 82 : settings.mode = DataLoggerService::Mode::AcVoltage;
61 82 : minRangeFunc = minVoltageRange;
62 1548 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
63 1066 : settings.mode = DataLoggerService::Mode::DcVoltage;
64 1066 : minRangeFunc = minVoltageRange;
65 782 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
66 82 : settings.mode = DataLoggerService::Mode::AcCurrent;
67 82 : minRangeFunc = minCurrentRange;
68 344 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
69 82 : settings.mode = DataLoggerService::Mode::DcCurrent;
70 82 : minRangeFunc = minCurrentRange;
71 246 : } else if (mode.startsWith(QLatin1String("temp"))) {
72 164 : settings.mode = DataLoggerService::Mode::Temperature;
73 164 : minRangeFunc = nullptr;
74 88 : } else {
75 82 : minRangeFunc = nullptr;
76 120 : errors.append(tr("Unknown logger mode: %1").arg(parser.value(QLatin1String("mode"))));
77 44 : return errors;
78 481 : }
79 :
80 : // Parse the range option.
81 1476 : rangeOptionValue = 0;
82 1746 : if (parser.isSet(QLatin1String("range"))) {
83 1394 : const QString value = parser.value(QLatin1String("range"));
84 1394 : switch (settings.mode) {
85 528 : case DataLoggerService::Mode::DcVoltage:
86 572 : case DataLoggerService::Mode::AcVoltage:
87 1066 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
88 1066 : break;
89 44 : case DataLoggerService::Mode::DcCurrent:
90 88 : case DataLoggerService::Mode::AcCurrent:
91 164 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
92 164 : break;
93 164 : default:
94 286 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
95 748 : }
96 1394 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
97 97 : errors.append(tr("Invalid range value: %1").arg(value));
98 44 : }
99 1177 : } else if (settings.mode != DataLoggerService::Mode::Temperature) {
100 105 : errors.append(tr("Missing required option for logger mode '%1': range")
101 120 : .arg(parser.value(QLatin1String("mode"))));
102 44 : }
103 :
104 : // Parse the interval option.
105 1746 : if (parser.isSet(QLatin1String("interval"))) {
106 485 : const QString value = parser.value(QLatin1String("interval"));
107 410 : const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
108 410 : if (interval == 0) {
109 194 : errors.append(tr("Invalid interval value: %1").arg(value));
110 132 : } else {
111 246 : settings.updateInterval = interval;
112 132 : }
113 335 : }
114 :
115 : // Parse the timestamp option.
116 1476 : settings.timestamp = (quint32)QDateTime::currentSecsSinceEpoch(); // Note, subject to Y2038 epochalypse.
117 1746 : if (parser.isSet(QLatin1String("timestamp"))) {
118 410 : const QString value = parser.value(QLatin1String("timestamp"));
119 410 : QLocale locale; bool ok;
120 220 : static_assert(sizeof(uint) == sizeof(settings.timestamp), "QLocale has no toUint32().");
121 335 : const int timestamp = locale.toUInt(value, &ok);
122 410 : if (!ok) {
123 194 : errors.append(tr("Invalid timestamp value: %1").arg(value));
124 132 : } else {
125 246 : settings.timestamp = timestamp;
126 132 : }
127 410 : }
128 792 : return errors;
129 792 : }
130 :
131 : /*!
132 : * \copybrief DeviceCommand::getService
133 : *
134 : * This override returns a pointer to a DataLoggerService object.
135 : */
136 0 : AbstractPokitService * LoggerStartCommand::getService()
137 0 : {
138 0 : Q_ASSERT(device);
139 0 : if (!service) {
140 0 : service = device->dataLogger();
141 0 : Q_ASSERT(service);
142 0 : connect(service, &DataLoggerService::settingsWritten,
143 0 : this, &LoggerStartCommand::settingsWritten);
144 0 : }
145 0 : return service;
146 0 : }
147 :
148 : /*!
149 : * \copybrief DeviceCommand::serviceDetailsDiscovered
150 : *
151 : * This override fetches the current device's status, and outputs it in the selected format.
152 : */
153 0 : void LoggerStartCommand::serviceDetailsDiscovered()
154 0 : {
155 0 : DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
156 0 : settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
157 0 : const QString range = service->toString(settings.range, settings.mode);
158 0 : qCInfo(lc).noquote() << tr("Logging %1, with range %2, every %L3ms.").arg(
159 0 : DataLoggerService::toString(settings.mode),
160 0 : (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
161 0 : service->setSettings(settings);
162 0 : }
163 :
164 : /*!
165 : * \var LoggerStartCommand::minRangeFunc
166 : *
167 : * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
168 : * is assigned during the command line parsing, but is not invoked until after the device's services are discovere,
169 : * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
170 : * which enumerator list to be using.
171 : *
172 : * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
173 : *
174 : * \see processOptions
175 : * \see serviceDetailsDiscovered
176 : */
177 :
178 : /*!
179 : * Invoked when the data logger settings have been written.
180 : */
181 114 : void LoggerStartCommand::settingsWritten()
182 132 : {
183 270 : qCDebug(lc).noquote() << tr("Settings written; data logger has started.");
184 246 : switch (format) {
185 82 : case OutputFormat::Csv:
186 82 : std::cout << qUtf8Printable(tr("logger_start_result\nsuccess\n"));
187 82 : break;
188 44 : case OutputFormat::Json:
189 93 : std::cout << qUtf8Printable(QLatin1String("true\n"));
190 82 : break;
191 82 : case OutputFormat::Text:
192 82 : std::cout << qUtf8Printable(tr("Done.\n"));
193 82 : break;
194 132 : }
195 246 : if (device) disconnect(); // Will exit the application once disconnected.
196 246 : }
|