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 1809 : LoggerStartCommand::LoggerStartCommand(QObject * const parent) : DeviceCommand(parent)
24 1404 : {
25 :
26 2619 : }
27 :
28 2025 : QStringList LoggerStartCommand::requiredOptions(const QCommandLineParser &parser) const
29 2340 : {
30 9945 : return DeviceCommand::requiredOptions(parser) + QStringList{
31 2340 : QLatin1String("mode"),
32 7335 : };
33 3420 : }
34 :
35 990 : QStringList LoggerStartCommand::supportedOptions(const QCommandLineParser &parser) const
36 1144 : {
37 6666 : return DeviceCommand::supportedOptions(parser) + QStringList{
38 1144 : QLatin1String("interval"),
39 1144 : QLatin1String("range"), // May still be required by processOptions(), depending on the --mode option's value.
40 1144 : QLatin1String("timestamp"),
41 5566 : };
42 1672 : }
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 945 : QStringList LoggerStartCommand::processOptions(const QCommandLineParser &parser)
51 1092 : {
52 2037 : QStringList errors = DeviceCommand::processOptions(parser);
53 2037 : if (!errors.isEmpty()) {
54 104 : return errors;
55 104 : }
56 :
57 : // Parse the (required) mode option.
58 2698 : if (const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
59 2014 : mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
60 97 : settings.mode = DataLoggerService::Mode::AcVoltage;
61 97 : minRangeFunc = minVoltageRange;
62 1908 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
63 1261 : settings.mode = DataLoggerService::Mode::DcVoltage;
64 1261 : minRangeFunc = minVoltageRange;
65 946 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
66 97 : settings.mode = DataLoggerService::Mode::AcCurrent;
67 97 : minRangeFunc = minCurrentRange;
68 424 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
69 97 : settings.mode = DataLoggerService::Mode::DcCurrent;
70 97 : minRangeFunc = minCurrentRange;
71 291 : } else if (mode.startsWith(QLatin1String("temp"))) {
72 194 : settings.mode = DataLoggerService::Mode::Temperature;
73 194 : minRangeFunc = nullptr;
74 104 : } else {
75 97 : minRangeFunc = nullptr;
76 148 : errors.append(tr("Unknown logger mode: %1").arg(parser.value(QLatin1String("mode"))));
77 52 : return errors;
78 508 : }
79 :
80 : // Parse the range option.
81 1746 : rangeOptionValue = 0;
82 2124 : if (parser.isSet(QLatin1String("range"))) {
83 1649 : const QString value = parser.value(QLatin1String("range"));
84 1649 : switch (settings.mode) {
85 624 : case DataLoggerService::Mode::DcVoltage:
86 676 : case DataLoggerService::Mode::AcVoltage:
87 1261 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
88 1261 : break;
89 52 : case DataLoggerService::Mode::DcCurrent:
90 104 : case DataLoggerService::Mode::AcCurrent:
91 194 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
92 194 : break;
93 194 : default:
94 352 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
95 884 : }
96 1649 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
97 118 : errors.append(tr("Invalid range value: %1").arg(value));
98 52 : }
99 1337 : } else if (settings.mode != DataLoggerService::Mode::Temperature) {
100 121 : errors.append(tr("Missing required option for logger mode '%1': range")
101 148 : .arg(parser.value(QLatin1String("mode"))));
102 52 : }
103 :
104 : // Parse the interval option.
105 2124 : if (parser.isSet(QLatin1String("interval"))) {
106 590 : const QString value = parser.value(QLatin1String("interval"));
107 485 : const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
108 485 : if (interval == 0) {
109 236 : errors.append(tr("Invalid interval value: %1").arg(value));
110 156 : } else {
111 291 : settings.updateInterval = interval;
112 156 : }
113 380 : }
114 :
115 : // Parse the timestamp option.
116 1746 : settings.timestamp = (quint32)QDateTime::currentSecsSinceEpoch(); // Note, subject to Y2038 epochalypse.
117 2124 : if (parser.isSet(QLatin1String("timestamp"))) {
118 485 : const QString value = parser.value(QLatin1String("timestamp"));
119 485 : QLocale locale; bool ok;
120 260 : static_assert(sizeof(uint) == sizeof(settings.timestamp), "QLocale has no toUint32().");
121 380 : const int timestamp = locale.toUInt(value, &ok);
122 485 : if (!ok) {
123 236 : errors.append(tr("Invalid timestamp value: %1").arg(value));
124 156 : } else {
125 291 : settings.timestamp = timestamp;
126 156 : }
127 485 : }
128 936 : return errors;
129 936 : }
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 135 : void LoggerStartCommand::settingsWritten()
182 156 : {
183 330 : qCDebug(lc).noquote() << tr("Settings written; data logger has started.");
184 291 : switch (format) {
185 97 : case OutputFormat::Csv:
186 97 : std::cout << qUtf8Printable(tr("logger_start_result\nsuccess\n"));
187 97 : break;
188 52 : case OutputFormat::Json:
189 109 : std::cout << qUtf8Printable(QLatin1String("true\n"));
190 97 : break;
191 97 : case OutputFormat::Text:
192 97 : std::cout << qUtf8Printable(tr("Done.\n"));
193 97 : break;
194 156 : }
195 291 : if (device) disconnect(); // Will exit the application once disconnected.
196 291 : }
|