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 : #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 2214 : LoggerStartCommand::LoggerStartCommand(QObject * const parent) : DeviceCommand(parent)
27 1404 : {
28 :
29 2619 : }
30 :
31 2025 : QStringList LoggerStartCommand::requiredOptions(const QCommandLineParser &parser) const
32 2340 : {
33 9945 : return DeviceCommand::requiredOptions(parser) + QStringList{
34 2340 : u"mode"_s,
35 7830 : };
36 2340 : }
37 :
38 990 : QStringList LoggerStartCommand::supportedOptions(const QCommandLineParser &parser) const
39 1144 : {
40 6666 : return DeviceCommand::supportedOptions(parser) + QStringList{
41 1144 : u"interval"_s,
42 1144 : u"range"_s, // May still be required by processOptions(), depending on the --mode option's value.
43 1144 : u"timestamp"_s,
44 5808 : };
45 1144 : }
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 945 : QStringList LoggerStartCommand::processOptions(const QCommandLineParser &parser)
54 1092 : {
55 2037 : QStringList errors = DeviceCommand::processOptions(parser);
56 2037 : if (!errors.isEmpty()) {
57 104 : return errors;
58 104 : }
59 :
60 : // Parse the (required) mode option.
61 2698 : if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
62 3097 : mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
63 97 : settings.mode = DataLoggerService::Mode::AcVoltage;
64 97 : minRangeFunc = minVoltageRange;
65 2934 : } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
66 1261 : settings.mode = DataLoggerService::Mode::DcVoltage;
67 1261 : minRangeFunc = minVoltageRange;
68 1231 : } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
69 97 : settings.mode = DataLoggerService::Mode::AcCurrent;
70 97 : minRangeFunc = minCurrentRange;
71 652 : } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
72 97 : settings.mode = DataLoggerService::Mode::DcCurrent;
73 97 : minRangeFunc = minCurrentRange;
74 354 : } else if (mode.startsWith(u"temp"_s)) {
75 194 : settings.mode = DataLoggerService::Mode::Temperature;
76 194 : minRangeFunc = nullptr;
77 104 : } else {
78 97 : minRangeFunc = nullptr;
79 148 : errors.append(tr("Unknown logger mode: %1").arg(parser.value(u"mode"_s)));
80 52 : return errors;
81 508 : }
82 :
83 : // Parse the range option.
84 1746 : rangeOptionValue = 0;
85 2124 : if (parser.isSet(u"range"_s)) {
86 1649 : const QString value = parser.value(u"range"_s);
87 1649 : switch (settings.mode) {
88 1209 : case DataLoggerService::Mode::DcVoltage:
89 676 : case DataLoggerService::Mode::AcVoltage:
90 1261 : rangeOptionValue = parseNumber<std::milli>(value, u"V"_s, 50); // mV.
91 1261 : break;
92 142 : case DataLoggerService::Mode::DcCurrent:
93 104 : case DataLoggerService::Mode::AcCurrent:
94 194 : rangeOptionValue = parseNumber<std::milli>(value, u"A"_s, 5); // mA.
95 194 : break;
96 194 : default:
97 352 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
98 884 : }
99 1649 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
100 118 : errors.append(tr("Invalid range value: %1").arg(value));
101 52 : }
102 1337 : } else if (settings.mode != DataLoggerService::Mode::Temperature) {
103 148 : errors.append(tr("Missing required option for logger mode '%1': range").arg(parser.value(u"mode"_s)));
104 52 : }
105 :
106 : // Parse the interval option.
107 2124 : if (parser.isSet(u"interval"_s)) {
108 590 : const QString value = parser.value(u"interval"_s);
109 485 : const quint32 interval = parseNumber<std::milli>(value, u"s"_s, 500);
110 485 : if (interval == 0) {
111 236 : errors.append(tr("Invalid interval value: %1").arg(value));
112 156 : } else {
113 291 : settings.updateInterval = interval;
114 156 : }
115 380 : }
116 :
117 : // Parse the timestamp option.
118 1746 : settings.timestamp = (quint32)QDateTime::currentSecsSinceEpoch(); // Note, subject to Y2038 epochalypse.
119 2124 : if (parser.isSet(u"timestamp"_s)) {
120 485 : const QString value = parser.value(u"timestamp"_s);
121 485 : QLocale locale; bool ok;
122 260 : static_assert(sizeof(uint) == sizeof(settings.timestamp), "QLocale has no toUint32().");
123 380 : const int timestamp = locale.toUInt(value, &ok);
124 485 : if (!ok) {
125 236 : errors.append(tr("Invalid timestamp value: %1").arg(value));
126 156 : } else {
127 291 : settings.timestamp = timestamp;
128 156 : }
129 485 : }
130 936 : return errors;
131 936 : }
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 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 97 : case OutputFormat::Json:
189 97 : std::cout << qUtf8Printable(u"true\n"_s);
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 : }
|