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 "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 1539 : LoggerStartCommand::LoggerStartCommand(QObject * const parent) : DeviceCommand(parent)
24 1269 : {
25 :
26 2349 : }
27 :
28 1800 : QStringList LoggerStartCommand::requiredOptions(const QCommandLineParser &parser) const
29 2115 : {
30 8865 : return DeviceCommand::requiredOptions(parser) + QStringList{
31 2115 : QLatin1String("mode"),
32 6570 : };
33 3105 : }
34 :
35 880 : QStringList LoggerStartCommand::supportedOptions(const QCommandLineParser &parser) const
36 1034 : {
37 5918 : return DeviceCommand::supportedOptions(parser) + QStringList{
38 1034 : QLatin1String("interval"),
39 1034 : QLatin1String("range"), // May still be required by processOptions(), depending on the --mode option's value.
40 1034 : QLatin1String("timestamp"),
41 4972 : };
42 1518 : }
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 840 : QStringList LoggerStartCommand::processOptions(const QCommandLineParser &parser)
51 987 : {
52 1827 : QStringList errors = DeviceCommand::processOptions(parser);
53 1827 : if (!errors.isEmpty()) {
54 94 : return errors;
55 94 : }
56 :
57 : // Parse the (required) mode option.
58 2413 : if (const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
59 1767 : mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
60 87 : settings.mode = DataLoggerService::Mode::AcVoltage;
61 87 : minRangeFunc = minVoltageRange;
62 1674 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
63 1131 : settings.mode = DataLoggerService::Mode::DcVoltage;
64 1131 : minRangeFunc = minVoltageRange;
65 841 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
66 87 : settings.mode = DataLoggerService::Mode::AcCurrent;
67 87 : minRangeFunc = minCurrentRange;
68 372 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
69 87 : settings.mode = DataLoggerService::Mode::DcCurrent;
70 87 : minRangeFunc = minCurrentRange;
71 261 : } else if (mode.startsWith(QLatin1String("temp"))) {
72 174 : settings.mode = DataLoggerService::Mode::Temperature;
73 174 : minRangeFunc = nullptr;
74 94 : } else {
75 87 : minRangeFunc = nullptr;
76 129 : errors.append(tr("Unknown logger mode: %1").arg(parser.value(QLatin1String("mode"))));
77 47 : return errors;
78 484 : }
79 :
80 : // Parse the range option.
81 1566 : rangeOptionValue = 0;
82 1872 : if (parser.isSet(QLatin1String("range"))) {
83 1479 : const QString value = parser.value(QLatin1String("range"));
84 1479 : switch (settings.mode) {
85 564 : case DataLoggerService::Mode::DcVoltage:
86 611 : case DataLoggerService::Mode::AcVoltage:
87 1131 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
88 1131 : break;
89 47 : case DataLoggerService::Mode::DcCurrent:
90 94 : case DataLoggerService::Mode::AcCurrent:
91 174 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
92 174 : break;
93 174 : default:
94 308 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
95 799 : }
96 1479 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
97 104 : errors.append(tr("Invalid range value: %1").arg(value));
98 47 : }
99 1230 : } else if (settings.mode != DataLoggerService::Mode::Temperature) {
100 110 : errors.append(tr("Missing required option for logger mode '%1': range")
101 129 : .arg(parser.value(QLatin1String("mode"))));
102 47 : }
103 :
104 : // Parse the interval option.
105 1872 : if (parser.isSet(QLatin1String("interval"))) {
106 520 : const QString value = parser.value(QLatin1String("interval"));
107 435 : const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
108 435 : if (interval == 0) {
109 208 : errors.append(tr("Invalid interval value: %1").arg(value));
110 141 : } else {
111 261 : settings.updateInterval = interval;
112 141 : }
113 350 : }
114 :
115 : // Parse the timestamp option.
116 1566 : settings.timestamp = (quint32)QDateTime::currentSecsSinceEpoch(); // Note, subject to Y2038 epochalypse.
117 1872 : if (parser.isSet(QLatin1String("timestamp"))) {
118 435 : const QString value = parser.value(QLatin1String("timestamp"));
119 435 : QLocale locale; bool ok;
120 235 : static_assert(sizeof(uint) == sizeof(settings.timestamp), "QLocale has no toUint32().");
121 350 : const int timestamp = locale.toUInt(value, &ok);
122 435 : if (!ok) {
123 208 : errors.append(tr("Invalid timestamp value: %1").arg(value));
124 141 : } else {
125 261 : settings.timestamp = timestamp;
126 141 : }
127 435 : }
128 846 : return errors;
129 846 : }
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 120 : void LoggerStartCommand::settingsWritten()
182 141 : {
183 291 : qCDebug(lc).noquote() << tr("Settings written; data logger has started.");
184 261 : switch (format) {
185 87 : case OutputFormat::Csv:
186 87 : std::cout << qUtf8Printable(tr("logger_start_result\nsuccess\n"));
187 87 : break;
188 47 : case OutputFormat::Json:
189 98 : std::cout << qUtf8Printable(QLatin1String("true\n"));
190 87 : break;
191 87 : case OutputFormat::Text:
192 87 : std::cout << qUtf8Printable(tr("Done.\n"));
193 87 : break;
194 141 : }
195 261 : if (device) disconnect(); // Will exit the application once disconnected.
196 261 : }
|