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 6237 : LoggerStartCommand::LoggerStartCommand(QObject * const parent) : DeviceCommand(parent)
27 3456 : {
28 :
29 6237 : }
30 :
31 4635 : QStringList LoggerStartCommand::requiredOptions(const QCommandLineParser &parser) const
32 5760 : {
33 20025 : return DeviceCommand::requiredOptions(parser) + QStringList{
34 5760 : u"mode"_s,
35 18045 : };
36 5760 : }
37 :
38 2266 : QStringList LoggerStartCommand::supportedOptions(const QCommandLineParser &parser) const
39 2816 : {
40 13002 : return DeviceCommand::supportedOptions(parser) + QStringList{
41 2816 : u"interval"_s,
42 2816 : u"range"_s, // May still be required by processOptions(), depending on the --mode option's value.
43 2816 : u"timestamp"_s,
44 13354 : };
45 2816 : }
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 2163 : QStringList LoggerStartCommand::processOptions(const QCommandLineParser &parser)
54 2688 : {
55 4851 : QStringList errors = DeviceCommand::processOptions(parser);
56 4851 : if (!errors.isEmpty()) {
57 256 : return errors;
58 256 : }
59 :
60 : // Parse the (required) mode option.
61 6346 : if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
62 7676 : mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
63 231 : settings.mode = DataLoggerService::Mode::AcVoltage;
64 231 : minRangeFunc = minVoltageRange;
65 7272 : } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
66 3003 : settings.mode = DataLoggerService::Mode::DcVoltage;
67 3003 : minRangeFunc = minVoltageRange;
68 3044 : } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
69 231 : settings.mode = DataLoggerService::Mode::AcCurrent;
70 231 : minRangeFunc = minCurrentRange;
71 1616 : } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
72 231 : settings.mode = DataLoggerService::Mode::DcCurrent;
73 231 : minRangeFunc = minCurrentRange;
74 903 : } else if (mode.startsWith(u"temp"_s)) {
75 462 : settings.mode = DataLoggerService::Mode::Temperature;
76 462 : minRangeFunc = nullptr;
77 256 : } else {
78 231 : minRangeFunc = nullptr;
79 361 : errors.append(tr("Unknown logger mode: %1").arg(parser.value(u"mode"_s)));
80 128 : return errors;
81 755 : }
82 :
83 : // Parse the range option.
84 4158 : rangeOptionValue = 0;
85 5418 : if (parser.isSet(u"range"_s)) {
86 3927 : const QString value = parser.value(u"range"_s);
87 3927 : switch (settings.mode) {
88 2875 : case DataLoggerService::Mode::DcVoltage:
89 1664 : case DataLoggerService::Mode::AcVoltage:
90 3003 : rangeOptionValue = parseNumber<std::milli>(value, u"V"_s, 50); // mV.
91 3003 : break;
92 334 : case DataLoggerService::Mode::DcCurrent:
93 256 : case DataLoggerService::Mode::AcCurrent:
94 462 : rangeOptionValue = parseNumber<std::milli>(value, u"A"_s, 5); // mA.
95 462 : break;
96 462 : default:
97 918 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
98 2176 : }
99 3927 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
100 301 : errors.append(tr("Invalid range value: %1").arg(value));
101 128 : }
102 2840 : } else if (settings.mode != DataLoggerService::Mode::Temperature) {
103 361 : errors.append(tr("Missing required option for logger mode '%1': range").arg(parser.value(u"mode"_s)));
104 128 : }
105 :
106 : // Parse the interval option.
107 5418 : if (parser.isSet(u"interval"_s)) {
108 1550 : const QString value = parser.value(u"interval"_s);
109 1155 : const quint32 interval = parseNumber<std::milli>(value, u"s"_s, 500);
110 1155 : if (interval == 0) {
111 602 : errors.append(tr("Invalid interval value: %1").arg(value));
112 384 : } else {
113 693 : settings.updateInterval = interval;
114 384 : }
115 805 : }
116 :
117 : // Parse the timestamp option.
118 4158 : settings.timestamp = (quint32)QDateTime::currentSecsSinceEpoch(); // Note, subject to Y2038 epochalypse.
119 5418 : if (parser.isSet(u"timestamp"_s)) {
120 1155 : const QString value = parser.value(u"timestamp"_s);
121 1155 : QLocale locale; bool ok;
122 640 : static_assert(sizeof(uint) == sizeof(settings.timestamp), "QLocale has no toUint32().");
123 805 : const int timestamp = locale.toUInt(value, &ok);
124 1155 : if (!ok) {
125 602 : errors.append(tr("Invalid timestamp value: %1").arg(value));
126 384 : } else {
127 693 : settings.timestamp = timestamp;
128 384 : }
129 1155 : }
130 2304 : return errors;
131 2304 : }
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 309 : void LoggerStartCommand::settingsWritten()
182 384 : {
183 858 : qCDebug(lc).noquote() << tr("Settings written; data logger has started.");
184 693 : switch (format) {
185 231 : case OutputFormat::Csv:
186 245 : std::cout << qUtf8Printable(tr("logger_start_result\nsuccess\n"));
187 231 : break;
188 231 : case OutputFormat::Json:
189 245 : std::cout << qUtf8Printable(u"true\n"_s);
190 231 : break;
191 231 : case OutputFormat::Text:
192 245 : std::cout << qUtf8Printable(tr("Done.\n"));
193 231 : break;
194 384 : }
195 693 : if (device) disconnect(); // Will exit the application once disconnected.
196 693 : }
|