Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2026 Paul Colby <git@colby.id.au>
2 : // SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 : #include "loggerstartcommand.h"
5 : #include "../stringliterals_p.h"
6 :
7 : #include <qtpokit/pokitdevice.h>
8 :
9 : #include <QDateTime>
10 : #include <QJsonDocument>
11 : #include <QJsonObject>
12 :
13 : #include <iostream>
14 :
15 : DOKIT_USE_STRINGLITERALS
16 :
17 : /*!
18 : * \class LoggerStartCommand
19 : *
20 : * The LoggerStartCommand class implements the `logger` CLI command.
21 : */
22 :
23 : /*!
24 : * Construct a new LoggerStartCommand object with \a parent.
25 : */
26 4428 : LoggerStartCommand::LoggerStartCommand(QObject * const parent) : DeviceCommand(parent)
27 2997 : {
28 :
29 4887 : }
30 :
31 3150 : QStringList LoggerStartCommand::requiredOptions(const QCommandLineParser &parser) const
32 4995 : {
33 16470 : return DeviceCommand::requiredOptions(parser) + QStringList{
34 4995 : u"mode"_s,
35 12915 : };
36 4995 : }
37 :
38 1540 : QStringList LoggerStartCommand::supportedOptions(const QCommandLineParser &parser) const
39 2442 : {
40 11044 : return DeviceCommand::supportedOptions(parser) + QStringList{
41 2442 : u"interval"_s,
42 2442 : u"range"_s, // May still be required by processOptions(), depending on the --mode option's value.
43 2442 : u"timestamp"_s,
44 9394 : };
45 2442 : }
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 1470 : QStringList LoggerStartCommand::processOptions(const QCommandLineParser &parser)
54 2331 : {
55 3801 : QStringList errors = DeviceCommand::processOptions(parser);
56 3801 : if (!errors.isEmpty()) {
57 222 : return errors;
58 222 : }
59 :
60 : // Parse the (required) mode option.
61 4769 : if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
62 5643 : mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
63 181 : settings.mode = DataLoggerService::Mode::AcVoltage;
64 181 : minRangeFunc = minVoltageRange;
65 5346 : } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
66 2353 : settings.mode = DataLoggerService::Mode::DcVoltage;
67 2353 : minRangeFunc = minVoltageRange;
68 2373 : } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
69 181 : settings.mode = DataLoggerService::Mode::AcCurrent;
70 181 : minRangeFunc = minCurrentRange;
71 1188 : } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
72 181 : settings.mode = DataLoggerService::Mode::DcCurrent;
73 181 : minRangeFunc = minCurrentRange;
74 681 : } else if (mode.startsWith(u"temp"_s)) {
75 362 : settings.mode = DataLoggerService::Mode::Temperature;
76 362 : minRangeFunc = nullptr;
77 222 : } else {
78 181 : minRangeFunc = nullptr;
79 269 : errors.append(tr("Unknown logger mode: %1").arg(parser.value(u"mode"_s)));
80 111 : return errors;
81 567 : }
82 :
83 : // Parse the range option.
84 3258 : rangeOptionValue = 0;
85 4086 : if (parser.isSet(u"range"_s)) {
86 3077 : const QString value = parser.value(u"range"_s);
87 3077 : switch (settings.mode) {
88 2242 : case DataLoggerService::Mode::DcVoltage:
89 1443 : case DataLoggerService::Mode::AcVoltage:
90 2353 : rangeOptionValue = parseNumber<std::milli>(value, u"V"_s, 50); // mV.
91 2353 : break;
92 251 : case DataLoggerService::Mode::DcCurrent:
93 222 : case DataLoggerService::Mode::AcCurrent:
94 362 : rangeOptionValue = parseNumber<std::milli>(value, u"A"_s, 5); // mA.
95 362 : break;
96 362 : default:
97 662 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
98 1887 : }
99 3077 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
100 227 : errors.append(tr("Invalid range value: %1").arg(value));
101 111 : }
102 2365 : } else if (settings.mode != DataLoggerService::Mode::Temperature) {
103 269 : errors.append(tr("Missing required option for logger mode '%1': range").arg(parser.value(u"mode"_s)));
104 111 : }
105 :
106 : // Parse the interval option.
107 4086 : if (parser.isSet(u"interval"_s)) {
108 1180 : const QString value = parser.value(u"interval"_s);
109 905 : const quint32 interval = parseNumber<std::milli>(value, u"s"_s, 500);
110 905 : if (interval == 0) {
111 454 : errors.append(tr("Invalid interval value: %1").arg(value));
112 333 : } else {
113 543 : settings.updateInterval = interval;
114 333 : }
115 675 : }
116 :
117 : // Parse the timestamp option.
118 3258 : settings.timestamp = (quint32)QDateTime::currentSecsSinceEpoch(); // Note, subject to Y2038 epochalypse.
119 4086 : if (parser.isSet(u"timestamp"_s)) {
120 905 : const QString value = parser.value(u"timestamp"_s);
121 905 : QLocale locale; bool ok;
122 555 : static_assert(sizeof(uint) == sizeof(settings.timestamp), "QLocale has no toUint32().");
123 675 : const int timestamp = locale.toUInt(value, &ok);
124 905 : if (!ok) {
125 454 : errors.append(tr("Invalid timestamp value: %1").arg(value));
126 333 : } else {
127 543 : settings.timestamp = timestamp;
128 333 : }
129 905 : }
130 1998 : return errors;
131 1998 : }
132 :
133 : /*!
134 : * \copybrief DeviceCommand::getService
135 : *
136 : * This override returns a pointer to a DataLoggerService object.
137 : */
138 0 : AbstractPokitService * LoggerStartCommand::getService()
139 0 : {
140 0 : Q_ASSERT(device);
141 0 : if (!service) {
142 0 : service = device->dataLogger();
143 0 : Q_ASSERT(service);
144 0 : connect(service, &DataLoggerService::settingsWritten, this, &LoggerStartCommand::settingsWritten);
145 0 : }
146 0 : return service;
147 0 : }
148 :
149 : /*!
150 : * \copybrief DeviceCommand::serviceDetailsDiscovered
151 : *
152 : * This override fetches the current device's status, and outputs it in the selected format.
153 : */
154 0 : void LoggerStartCommand::serviceDetailsDiscovered()
155 0 : {
156 0 : DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
157 0 : settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
158 0 : const QString range = service->toString(settings.range, settings.mode);
159 0 : qCInfo(lc).noquote() << tr("Logging %1, with range %2, every %L3ms.").arg(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 discovered,
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 210 : void LoggerStartCommand::settingsWritten()
182 333 : {
183 645 : qCDebug(lc).noquote() << tr("Settings written; data logger has started.");
184 543 : switch (format) {
185 181 : case OutputFormat::Csv:
186 194 : std::cout << qUtf8Printable(tr("logger_start_result\nsuccess\n"));
187 181 : break;
188 181 : case OutputFormat::Json:
189 194 : std::cout << qUtf8Printable(u"true\n"_s);
190 181 : break;
191 181 : case OutputFormat::Text:
192 194 : std::cout << qUtf8Printable(tr("Done.\n"));
193 181 : break;
194 333 : }
195 543 : if (device) disconnect(); // Will exit the application once disconnected.
196 543 : }
|